AccurateInterval.cs 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // accurate interval from Mirror II.
  2. // for sync / send intervals where it matters.
  3. // does not(!) do catch-up.
  4. //
  5. // first, let's understand the problem.
  6. // say we need an interval of 10 Hz, so every 100ms in Update we do:
  7. // if (Time.time >= lastTime + interval)
  8. // {
  9. // lastTime = Time.time;
  10. // ...
  11. // }
  12. //
  13. // this seems fine, but actually Time.time will always be a few ms beyond
  14. // the interval. but since lastTime is reset to Time.time, the remainder
  15. // is always ignored away.
  16. // with fixed tickRate servers (say 30 Hz), the remainder is significant!
  17. //
  18. // in practice if we have a 30 Hz tickRate server with a 30 Hz sendRate,
  19. // the above way to measure the interval would result in a 18-19 Hz sendRate!
  20. // => this is not just a little off. this is _way_ off, by almost half.
  21. // => displaying actual + target tick/send rate will show this very easily.
  22. //
  23. // we need an accurate way to measure intervals for where it matters.
  24. // and it needs to be testable to guarantee results.
  25. using System.Runtime.CompilerServices;
  26. namespace Mirror
  27. {
  28. public static class AccurateInterval
  29. {
  30. // static func instead of storing interval + lastTime struct.
  31. // + don't need to initialize struct ctor with interval in Awake
  32. // + allows for interval changes at runtime too
  33. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  34. public static bool Elapsed(double time, double interval, ref double lastTime)
  35. {
  36. // enough time elapsed?
  37. if (time < lastTime + interval)
  38. return false;
  39. // naive implementation:
  40. //lastTime = time;
  41. // accurate but doesn't handle heavy load situations:
  42. //lastTime += interval;
  43. // heavy load edge case:
  44. // * interval is 100ms
  45. // * server is under heavy load, Updates slow down to 1/s
  46. // * Elapsed(1.000) returns true.
  47. // technically 10 intervals have elapsed.
  48. // * server recovers to normal, Updates are every 10ms again
  49. // * Elapsed(1.010) should return false again until 1.100.
  50. //
  51. // increasing lastTime by interval would require 10 more calls
  52. // to ever catch up again:
  53. // lastTime += interval
  54. //
  55. // as result, the next 10 calls to Elapsed would return true.
  56. // Elapsed(1.001) => true
  57. // Elapsed(1.002) => true
  58. // Elapsed(1.003) => true
  59. // ...
  60. // even though technically the delta was not >= interval.
  61. //
  62. // this would keep the server under heavy load, and it may never
  63. // catch-up. this is not ideal for large virtual worlds.
  64. //
  65. // instead, we want to skip multiples of 'interval' and only
  66. // keep the remainder.
  67. //
  68. // see also: AccurateIntervalTests.Slowdown()
  69. // easy to understand:
  70. //double elapsed = time - lastTime;
  71. //double remainder = elapsed % interval;
  72. //lastTime = time - remainder;
  73. // easier: set to rounded multiples of interval (fholm).
  74. // long to match double time.
  75. long multiplier = (long)(time / interval);
  76. lastTime = multiplier * interval;
  77. return true;
  78. }
  79. }
  80. }