| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 | 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;        // Deprecated 2021-03-10        [Obsolete("NetworkTime.timeVar was renamed to timeVariance")]        public static double timeVar => timeVariance;        /// <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);        // Deprecated 2021-03-10        [Obsolete("NetworkTime.timeSd was renamed to timeStandardDeviation")]        public static double timeSd => timeStandardDeviation;        /// <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;        // Deprecated 2021-03-02        [Obsolete("NetworkTime.rttVar was renamed to rttVariance")]        public static double rttVar => rttVariance;        /// <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);        // Deprecated 2021-03-02        [Obsolete("NetworkTime.rttSd was renamed to rttStandardDeviation")]        public static double rttSd => rttStandardDeviation;        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);            }        }    }}
 |