using System; using UnityEngine; using Stopwatch = System.Diagnostics.Stopwatch; namespace Mirror { /// /// Synchronize time between the server and the clients /// public static class NetworkTime { /// /// how often are we sending ping messages /// used to calculate network time and RTT /// public static float PingFrequency = 2.0f; /// /// average out the last few results from Ping /// public static int PingWindowSize = 10; static double lastPingTime; // Date and time when the application started 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; // returns the clock time _in this system_ static double LocalTime() { return stopwatch.Elapsed.TotalSeconds; } public static void Reset() { _rtt = new ExponentialMovingAverage(PingWindowSize); _offset = new ExponentialMovingAverage(PingWindowSize); offsetMin = double.MinValue; offsetMax = double.MaxValue; } internal static void UpdateClient() { if (Time.time - lastPingTime >= PingFrequency) { NetworkPingMessage pingMessage = new NetworkPingMessage(LocalTime()); NetworkClient.Send(pingMessage); lastPingTime = Time.time; } } // 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 msg) { if (LogFilter.Debug) Debug.Log("OnPingServerMessage conn=" + conn); NetworkPongMessage pongMsg = new NetworkPongMessage { clientTime = msg.clientTime, serverTime = LocalTime() }; conn.Send(pongMsg); } // 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(NetworkConnection _, NetworkPongMessage msg) { double now = LocalTime(); // how long did this message take to come back double newRtt = now - msg.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 - msg.serverTime; double newOffsetMin = now - newRtt - msg.serverTime; double newOffsetMax = now - msg.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); } } /// /// The time in seconds since the server started. /// /// /// /// Note this value works in the client and the server /// the value is synchronized accross the network with high accuracy /// /// You should not cast this down to a float because the it loses too much accuracy /// when the server is up for a while /// 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. /// public static double time => LocalTime() - _offset.Value; /// /// Measurement of the variance of time. /// The higher the variance, the less accurate the time is /// public static double timeVar => _offset.Var; /// /// standard deviation of time. /// The higher the variance, the less accurate the time is /// public static double timeSd => Math.Sqrt(timeVar); /// /// Clock difference in seconds between the client and the server /// /// /// Note this value is always 0 at the server /// public static double offset => _offset.Value; /// /// how long in seconds does it take for a message to go /// to the server and come back /// public static double rtt => _rtt.Value; /// /// measure variance of rtt /// the higher the number, the less accurate rtt is /// public static double rttVar => _rtt.Var; /// /// Measure the standard deviation of rtt /// the higher the number, the less accurate rtt is /// public static double rttSd => Math.Sqrt(rttVar); } }