| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 | // accurate interval from Mirror II.// for sync / send intervals where it matters.// does not(!) do catch-up.//// first, let's understand the problem.//   say we need an interval of 10 Hz, so every 100ms in Update we do://     if (Time.time >= lastTime + interval)//     {//         lastTime = Time.time;//         ...//     }////   this seems fine, but actually Time.time will always be a few ms beyond//   the interval. but since lastTime is reset to Time.time, the remainder//   is always ignored away.//   with fixed tickRate servers (say 30 Hz), the remainder is significant!////   in practice if we have a 30 Hz tickRate server with a 30 Hz sendRate,//   the above way to measure the interval would result in a 18-19 Hz sendRate!//   => this is not just a little off. this is _way_ off, by almost half.//   => displaying actual + target tick/send rate will show this very easily.//// we need an accurate way to measure intervals for where it matters.// and it needs to be testable to guarantee results.using System.Runtime.CompilerServices;namespace Mirror{    public static class AccurateInterval    {        // static func instead of storing interval + lastTime struct.        // + don't need to initialize struct ctor with interval in Awake        // + allows for interval changes at runtime too        [MethodImpl(MethodImplOptions.AggressiveInlining)]        public static bool Elapsed(double time, double interval, ref double lastTime)        {            // enough time elapsed?            if (time < lastTime + interval)                return false;            // naive implementation:            //lastTime = time;            // accurate but doesn't handle heavy load situations:            //lastTime += interval;            // heavy load edge case:            // * interval is 100ms            // * server is under heavy load, Updates slow down to 1/s            // * Elapsed(1.000) returns true.            //   technically 10 intervals have elapsed.            // * server recovers to normal, Updates are every 10ms again            // * Elapsed(1.010) should return false again until 1.100.            //            // increasing lastTime by interval would require 10 more calls            // to ever catch up again:            //   lastTime += interval            //            // as result, the next 10 calls to Elapsed would return true.            //   Elapsed(1.001) => true            //   Elapsed(1.002) => true            //   Elapsed(1.003) => true            //   ...            // even though technically the delta was not >= interval.            //            // this would keep the server under heavy load, and it may never            // catch-up. this is not ideal for large virtual worlds.            //            // instead, we want to skip multiples of 'interval' and only            // keep the remainder.            //            // see also: AccurateIntervalTests.Slowdown()            // easy to understand:            //double elapsed = time - lastTime;            //double remainder = elapsed % interval;            //lastTime = time - remainder;            // easier: set to rounded multiples of interval (fholm).            // long to match double time.            long multiplier = (long)(time / interval);            lastTime = multiplier * interval;            return true;        }    }}
 |