mirror of
				https://github.com/DerTyp7/defrain-shooter-unity.git
				synced 2025-10-31 05:27:07 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			146 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using Stopwatch = System.Diagnostics.Stopwatch;
 | |
| 
 | |
| namespace Mirror
 | |
| {
 | |
|     /// <summary>Synchronizes server time to clients.</summary>
 | |
|     public static class NetworkTime
 | |
|     {
 | |
|         /// <summary>Ping message frequency, used to calculate network time and RTT</summary>
 | |
|         public static float PingFrequency = 2.0f;
 | |
| 
 | |
|         /// <summary>Average out the last few results from Ping</summary>
 | |
|         public static int PingWindowSize = 10;
 | |
| 
 | |
|         static double lastPingTime;
 | |
| 
 | |
|         // Date and time when the application started
 | |
|         // TODO Unity 2020 / 2021 supposedly has double Time.time now?
 | |
|         static readonly Stopwatch stopwatch = new Stopwatch();
 | |
| 
 | |
|         static NetworkTime()
 | |
|         {
 | |
|             stopwatch.Start();
 | |
|         }
 | |
| 
 | |
|         static ExponentialMovingAverage _rtt = new ExponentialMovingAverage(10);
 | |
|         static ExponentialMovingAverage _offset = new ExponentialMovingAverage(10);
 | |
| 
 | |
|         // the true offset guaranteed to be in this range
 | |
|         static double offsetMin = double.MinValue;
 | |
|         static double offsetMax = double.MaxValue;
 | |
| 
 | |
|         /// <summary>Returns double precision clock time _in this system_, unaffected by the network.</summary>
 | |
|         // useful until we have Unity's 'double' Time.time
 | |
|         //
 | |
|         // CAREFUL: unlike Time.time, this is not a FRAME time.
 | |
|         //          it changes during the frame too.
 | |
|         public static double localTime => stopwatch.Elapsed.TotalSeconds;
 | |
| 
 | |
|         /// <summary>The time in seconds since the server started.</summary>
 | |
|         //
 | |
|         // I measured the accuracy of float and I got this:
 | |
|         // for the same day,  accuracy is better than 1 ms
 | |
|         // after 1 day,  accuracy goes down to 7 ms
 | |
|         // after 10 days, accuracy is 61 ms
 | |
|         // after 30 days , accuracy is 238 ms
 | |
|         // after 60 days, accuracy is 454 ms
 | |
|         // in other words,  if the server is running for 2 months,
 | |
|         // and you cast down to float,  then the time will jump in 0.4s intervals.
 | |
|         //
 | |
|         // TODO consider using Unbatcher's remoteTime for NetworkTime
 | |
|         public static double time => localTime - _offset.Value;
 | |
| 
 | |
|         /// <summary>Time measurement variance. The higher, the less accurate the time is.</summary>
 | |
|         // TODO does this need to be public? user should only need NetworkTime.time
 | |
|         public static double timeVariance => _offset.Var;
 | |
| 
 | |
|         /// <summary>Time standard deviation. The highe, the less accurate the time is.</summary>
 | |
|         // TODO does this need to be public? user should only need NetworkTime.time
 | |
|         public static double timeStandardDeviation => Math.Sqrt(timeVariance);
 | |
| 
 | |
|         /// <summary>Clock difference in seconds between the client and the server. Always 0 on server.</summary>
 | |
|         public static double offset => _offset.Value;
 | |
| 
 | |
|         /// <summary>Round trip time (in seconds) that it takes a message to go client->server->client.</summary>
 | |
|         public static double rtt => _rtt.Value;
 | |
| 
 | |
|         /// <summary>Round trip time variance. The higher, the less accurate the rtt is.</summary>
 | |
|         // TODO does this need to be public? user should only need NetworkTime.time
 | |
|         public static double rttVariance => _rtt.Var;
 | |
| 
 | |
|         /// <summary>Round trip time standard deviation. The higher, the less accurate the rtt is.</summary>
 | |
|         // TODO does this need to be public? user should only need NetworkTime.time
 | |
|         public static double rttStandardDeviation => Math.Sqrt(rttVariance);
 | |
| 
 | |
|         public static void Reset()
 | |
|         {
 | |
|             stopwatch.Restart();
 | |
|             _rtt = new ExponentialMovingAverage(PingWindowSize);
 | |
|             _offset = new ExponentialMovingAverage(PingWindowSize);
 | |
|             offsetMin = double.MinValue;
 | |
|             offsetMax = double.MaxValue;
 | |
|             lastPingTime = 0;
 | |
|         }
 | |
| 
 | |
|         internal static void UpdateClient()
 | |
|         {
 | |
|             // localTime (double) instead of Time.time for accuracy over days
 | |
|             if (localTime - lastPingTime >= PingFrequency)
 | |
|             {
 | |
|                 NetworkPingMessage pingMessage = new NetworkPingMessage(localTime);
 | |
|                 NetworkClient.Send(pingMessage, Channels.Unreliable);
 | |
|                 lastPingTime = localTime;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // executed at the server when we receive a ping message
 | |
|         // reply with a pong containing the time from the client
 | |
|         // and time from the server
 | |
|         internal static void OnServerPing(NetworkConnection conn, NetworkPingMessage message)
 | |
|         {
 | |
|             // Debug.Log($"OnPingServerMessage conn:{conn}");
 | |
|             NetworkPongMessage pongMessage = new NetworkPongMessage
 | |
|             {
 | |
|                 clientTime = message.clientTime,
 | |
|                 serverTime = localTime
 | |
|             };
 | |
|             conn.Send(pongMessage, Channels.Unreliable);
 | |
|         }
 | |
| 
 | |
|         // Executed at the client when we receive a Pong message
 | |
|         // find out how long it took since we sent the Ping
 | |
|         // and update time offset
 | |
|         internal static void OnClientPong(NetworkPongMessage message)
 | |
|         {
 | |
|             double now = localTime;
 | |
| 
 | |
|             // how long did this message take to come back
 | |
|             double newRtt = now - message.clientTime;
 | |
|             _rtt.Add(newRtt);
 | |
| 
 | |
|             // the difference in time between the client and the server
 | |
|             // but subtract half of the rtt to compensate for latency
 | |
|             // half of rtt is the best approximation we have
 | |
|             double newOffset = now - newRtt * 0.5f - message.serverTime;
 | |
| 
 | |
|             double newOffsetMin = now - newRtt - message.serverTime;
 | |
|             double newOffsetMax = now - message.serverTime;
 | |
|             offsetMin = Math.Max(offsetMin, newOffsetMin);
 | |
|             offsetMax = Math.Min(offsetMax, newOffsetMax);
 | |
| 
 | |
|             if (_offset.Value < offsetMin || _offset.Value > offsetMax)
 | |
|             {
 | |
|                 // the old offset was offrange,  throw it away and use new one
 | |
|                 _offset = new ExponentialMovingAverage(PingWindowSize);
 | |
|                 _offset.Add(newOffset);
 | |
|             }
 | |
|             else if (newOffset >= offsetMin || newOffset <= offsetMax)
 | |
|             {
 | |
|                 // new offset looks reasonable,  add to the average
 | |
|                 _offset.Add(newOffset);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | 
