// 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; } } }