87 lines
3.3 KiB
C#
87 lines
3.3 KiB
C#
// 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;
|
|
}
|
|
}
|
|
}
|