NetworkTime.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. using System;
  2. using Stopwatch = System.Diagnostics.Stopwatch;
  3. namespace Mirror
  4. {
  5. /// <summary>Synchronizes server time to clients.</summary>
  6. public static class NetworkTime
  7. {
  8. /// <summary>Ping message frequency, used to calculate network time and RTT</summary>
  9. public static float PingFrequency = 2.0f;
  10. /// <summary>Average out the last few results from Ping</summary>
  11. public static int PingWindowSize = 10;
  12. static double lastPingTime;
  13. // Date and time when the application started
  14. // TODO Unity 2020 / 2021 supposedly has double Time.time now?
  15. static readonly Stopwatch stopwatch = new Stopwatch();
  16. static NetworkTime()
  17. {
  18. stopwatch.Start();
  19. }
  20. static ExponentialMovingAverage _rtt = new ExponentialMovingAverage(10);
  21. static ExponentialMovingAverage _offset = new ExponentialMovingAverage(10);
  22. // the true offset guaranteed to be in this range
  23. static double offsetMin = double.MinValue;
  24. static double offsetMax = double.MaxValue;
  25. /// <summary>Returns double precision clock time _in this system_, unaffected by the network.</summary>
  26. // useful until we have Unity's 'double' Time.time
  27. //
  28. // CAREFUL: unlike Time.time, this is not a FRAME time.
  29. // it changes during the frame too.
  30. public static double localTime => stopwatch.Elapsed.TotalSeconds;
  31. /// <summary>The time in seconds since the server started.</summary>
  32. //
  33. // I measured the accuracy of float and I got this:
  34. // for the same day, accuracy is better than 1 ms
  35. // after 1 day, accuracy goes down to 7 ms
  36. // after 10 days, accuracy is 61 ms
  37. // after 30 days , accuracy is 238 ms
  38. // after 60 days, accuracy is 454 ms
  39. // in other words, if the server is running for 2 months,
  40. // and you cast down to float, then the time will jump in 0.4s intervals.
  41. //
  42. // TODO consider using Unbatcher's remoteTime for NetworkTime
  43. public static double time => localTime - _offset.Value;
  44. /// <summary>Time measurement variance. The higher, the less accurate the time is.</summary>
  45. // TODO does this need to be public? user should only need NetworkTime.time
  46. public static double timeVariance => _offset.Var;
  47. // Deprecated 2021-03-10
  48. [Obsolete("NetworkTime.timeVar was renamed to timeVariance")]
  49. public static double timeVar => timeVariance;
  50. /// <summary>Time standard deviation. The highe, the less accurate the time is.</summary>
  51. // TODO does this need to be public? user should only need NetworkTime.time
  52. public static double timeStandardDeviation => Math.Sqrt(timeVariance);
  53. // Deprecated 2021-03-10
  54. [Obsolete("NetworkTime.timeSd was renamed to timeStandardDeviation")]
  55. public static double timeSd => timeStandardDeviation;
  56. /// <summary>Clock difference in seconds between the client and the server. Always 0 on server.</summary>
  57. public static double offset => _offset.Value;
  58. /// <summary>Round trip time (in seconds) that it takes a message to go client->server->client.</summary>
  59. public static double rtt => _rtt.Value;
  60. /// <summary>Round trip time variance. The higher, the less accurate the rtt is.</summary>
  61. // TODO does this need to be public? user should only need NetworkTime.time
  62. public static double rttVariance => _rtt.Var;
  63. // Deprecated 2021-03-02
  64. [Obsolete("NetworkTime.rttVar was renamed to rttVariance")]
  65. public static double rttVar => rttVariance;
  66. /// <summary>Round trip time standard deviation. The higher, the less accurate the rtt is.</summary>
  67. // TODO does this need to be public? user should only need NetworkTime.time
  68. public static double rttStandardDeviation => Math.Sqrt(rttVariance);
  69. // Deprecated 2021-03-02
  70. [Obsolete("NetworkTime.rttSd was renamed to rttStandardDeviation")]
  71. public static double rttSd => rttStandardDeviation;
  72. public static void Reset()
  73. {
  74. stopwatch.Restart();
  75. _rtt = new ExponentialMovingAverage(PingWindowSize);
  76. _offset = new ExponentialMovingAverage(PingWindowSize);
  77. offsetMin = double.MinValue;
  78. offsetMax = double.MaxValue;
  79. lastPingTime = 0;
  80. }
  81. internal static void UpdateClient()
  82. {
  83. // localTime (double) instead of Time.time for accuracy over days
  84. if (localTime - lastPingTime >= PingFrequency)
  85. {
  86. NetworkPingMessage pingMessage = new NetworkPingMessage(localTime);
  87. NetworkClient.Send(pingMessage, Channels.Unreliable);
  88. lastPingTime = localTime;
  89. }
  90. }
  91. // executed at the server when we receive a ping message
  92. // reply with a pong containing the time from the client
  93. // and time from the server
  94. internal static void OnServerPing(NetworkConnection conn, NetworkPingMessage message)
  95. {
  96. // Debug.Log("OnPingServerMessage conn=" + conn);
  97. NetworkPongMessage pongMessage = new NetworkPongMessage
  98. {
  99. clientTime = message.clientTime,
  100. serverTime = localTime
  101. };
  102. conn.Send(pongMessage, Channels.Unreliable);
  103. }
  104. // Executed at the client when we receive a Pong message
  105. // find out how long it took since we sent the Ping
  106. // and update time offset
  107. internal static void OnClientPong(NetworkPongMessage message)
  108. {
  109. double now = localTime;
  110. // how long did this message take to come back
  111. double newRtt = now - message.clientTime;
  112. _rtt.Add(newRtt);
  113. // the difference in time between the client and the server
  114. // but subtract half of the rtt to compensate for latency
  115. // half of rtt is the best approximation we have
  116. double newOffset = now - newRtt * 0.5f - message.serverTime;
  117. double newOffsetMin = now - newRtt - message.serverTime;
  118. double newOffsetMax = now - message.serverTime;
  119. offsetMin = Math.Max(offsetMin, newOffsetMin);
  120. offsetMax = Math.Min(offsetMax, newOffsetMax);
  121. if (_offset.Value < offsetMin || _offset.Value > offsetMax)
  122. {
  123. // the old offset was offrange, throw it away and use new one
  124. _offset = new ExponentialMovingAverage(PingWindowSize);
  125. _offset.Add(newOffset);
  126. }
  127. else if (newOffset >= offsetMin || newOffset <= offsetMax)
  128. {
  129. // new offset looks reasonable, add to the average
  130. _offset.Add(newOffset);
  131. }
  132. }
  133. }
  134. }