Mirror Networking
NetworkTime.cs
1using System;
2using System.Runtime.CompilerServices;
3using UnityEngine;
4#if !UNITY_2020_3_OR_NEWER
5using Stopwatch = System.Diagnostics.Stopwatch;
6#endif
7
8namespace Mirror
9{
11 public static class NetworkTime
12 {
14 public static float PingFrequency = 2.0f;
15
17 public static int PingWindowSize = 10;
18
19 static double lastPingTime;
20
23
24 // the true offset guaranteed to be in this range
25 static double offsetMin = double.MinValue;
26 static double offsetMax = double.MaxValue;
27
29#if UNITY_2020_3_OR_NEWER
30 public static double localTime
31 {
32 [MethodImpl(MethodImplOptions.AggressiveInlining)]
33 get => Time.timeAsDouble;
34 }
35#else
36 // need stopwatch for older Unity versions, but it's quite slow.
37 // CAREFUL: unlike Time.time, this is not a FRAME time.
38 // it changes during the frame too.
39 static readonly Stopwatch stopwatch = new Stopwatch();
40 static NetworkTime() => stopwatch.Start();
41 public static double localTime => stopwatch.Elapsed.TotalSeconds;
42#endif
43
45 //
46 // I measured the accuracy of float and I got this:
47 // for the same day, accuracy is better than 1 ms
48 // after 1 day, accuracy goes down to 7 ms
49 // after 10 days, accuracy is 61 ms
50 // after 30 days , accuracy is 238 ms
51 // after 60 days, accuracy is 454 ms
52 // in other words, if the server is running for 2 months,
53 // and you cast down to float, then the time will jump in 0.4s intervals.
54 //
55 // TODO consider using Unbatcher's remoteTime for NetworkTime
56 public static double time
57 {
58 [MethodImpl(MethodImplOptions.AggressiveInlining)]
59 get => localTime - _offset.Value;
60 }
61
63 // TODO does this need to be public? user should only need NetworkTime.time
64 public static double timeVariance => _offset.Variance;
65
67 // TODO does this need to be public? user should only need NetworkTime.time
68 public static double timeStandardDeviation => Math.Sqrt(timeVariance);
69
71 public static double offset => _offset.Value;
72
74 public static double rtt => _rtt.Value;
75
77 // TODO does this need to be public? user should only need NetworkTime.time
78 public static double rttVariance => _rtt.Variance;
79
81 // TODO does this need to be public? user should only need NetworkTime.time
82 public static double rttStandardDeviation => Math.Sqrt(rttVariance);
83
84 // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
85 [UnityEngine.RuntimeInitializeOnLoadMethod]
86 public static void ResetStatics()
87 {
88 PingFrequency = 2.0f;
89 PingWindowSize = 10;
90 lastPingTime = 0;
93 offsetMin = double.MinValue;
94 offsetMax = double.MaxValue;
95#if !UNITY_2020_3_OR_NEWER
96 stopwatch.Restart();
97#endif
98 }
99
100 internal static void UpdateClient()
101 {
102 // localTime (double) instead of Time.time for accuracy over days
103 if (localTime - lastPingTime >= PingFrequency)
104 {
105 NetworkPingMessage pingMessage = new NetworkPingMessage(localTime);
106 NetworkClient.Send(pingMessage, Channels.Unreliable);
107 lastPingTime = localTime;
108 }
109 }
110
111 // executed at the server when we receive a ping message
112 // reply with a pong containing the time from the client
113 // and time from the server
114 internal static void OnServerPing(NetworkConnectionToClient conn, NetworkPingMessage message)
115 {
116 // Debug.Log($"OnPingServerMessage conn:{conn}");
117 NetworkPongMessage pongMessage = new NetworkPongMessage
118 {
119 clientTime = message.clientTime,
120 serverTime = localTime
121 };
122 conn.Send(pongMessage, Channels.Unreliable);
123 }
124
125 // Executed at the client when we receive a Pong message
126 // find out how long it took since we sent the Ping
127 // and update time offset
128 internal static void OnClientPong(NetworkPongMessage message)
129 {
130 double now = localTime;
131
132 // how long did this message take to come back
133 double newRtt = now - message.clientTime;
134 _rtt.Add(newRtt);
135
136 // the difference in time between the client and the server
137 // but subtract half of the rtt to compensate for latency
138 // half of rtt is the best approximation we have
139 double newOffset = now - newRtt * 0.5f - message.serverTime;
140
141 double newOffsetMin = now - newRtt - message.serverTime;
142 double newOffsetMax = now - message.serverTime;
143 offsetMin = Math.Max(offsetMin, newOffsetMin);
144 offsetMax = Math.Min(offsetMax, newOffsetMax);
145
146 if (_offset.Value < offsetMin || _offset.Value > offsetMax)
147 {
148 // the old offset was offrange, throw it away and use new one
149 _offset = new ExponentialMovingAverage(PingWindowSize);
150 _offset.Add(newOffset);
151 }
152 else if (newOffset >= offsetMin || newOffset <= offsetMax)
153 {
154 // new offset looks reasonable, add to the average
155 _offset.Add(newOffset);
156 }
157 }
158 }
159}
NetworkClient with connection to server.
Synchronizes server time to clients.
Definition: NetworkTime.cs:12
static double offset
Clock difference in seconds between the client and the server. Always 0 on server.
Definition: NetworkTime.cs:71
static double rtt
Round trip time (in seconds) that it takes a message to go client->server->client.
Definition: NetworkTime.cs:74
static double rttVariance
Round trip time variance. The higher, the less accurate the rtt is.
Definition: NetworkTime.cs:78
static double timeStandardDeviation
Time standard deviation. The highe, the less accurate the time is.
Definition: NetworkTime.cs:68
static int PingWindowSize
Average out the last few results from Ping
Definition: NetworkTime.cs:17
static double rttStandardDeviation
Round trip time standard deviation. The higher, the less accurate the rtt is.
Definition: NetworkTime.cs:82
static double time
The time in seconds since the server started.
Definition: NetworkTime.cs:57
static double timeVariance
Time measurement variance. The higher, the less accurate the time is.
Definition: NetworkTime.cs:64
static float PingFrequency
Ping message frequency, used to calculate network time and RTT
Definition: NetworkTime.cs:14