76 lines
3 KiB
C#
76 lines
3 KiB
C#
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
using System;
|
|
|
|
// The one relation between simulation time and real time. Real seconds go in; whole fixed steps come out, each
|
|
// handed to the caller's step action with the same Step. Nothing else in the game converts frame time to sim time,
|
|
// and nothing integrates a sim quantity on this clock: presenters read Lead to draw ahead of the latest step
|
|
// (DrawAhead) and decide no sim fact.
|
|
public sealed class SimClock
|
|
{
|
|
// The longest real time one frame may feed. A hitch (a load, a breakpoint) is a slow frame, not a burst of
|
|
// catch-up steps.
|
|
public const float MaxFrameSeconds = .1f;
|
|
|
|
// Game steps per real second at design speed 1, and the sim seconds one step carries. The one owner of the game's
|
|
// tick: every driver of a game history steps Zone.Update by GameStep. Design speed (ruling sim-speed-surface) will
|
|
// set Step = DesignSpeed / TickRate in ForGame and nowhere else.
|
|
public const int TickRate = 30;
|
|
public const float GameStep = 1f / TickRate;
|
|
|
|
public static SimClock ForGame() => new SimClock(GameStep, TickRate);
|
|
|
|
private readonly double _period;
|
|
private double _accumulator;
|
|
private bool _advancing;
|
|
|
|
public SimClock(float step, float stepsPerRealSecond)
|
|
{
|
|
if (!(step > 0)) throw new ArgumentOutOfRangeException(nameof(step));
|
|
if (!(stepsPerRealSecond > 0)) throw new ArgumentOutOfRangeException(nameof(stepsPerRealSecond));
|
|
Step = step;
|
|
StepsPerRealSecond = stepsPerRealSecond;
|
|
_period = 1.0 / stepsPerRealSecond;
|
|
}
|
|
|
|
// Sim seconds per step, fixed for the clock's life.
|
|
public float Step { get; }
|
|
|
|
public float StepsPerRealSecond { get; }
|
|
|
|
public long Steps { get; private set; }
|
|
|
|
// Sim seconds since the last step, 0 <= Lead < Step.
|
|
public float Lead => (float) (_accumulator * StepsPerRealSecond * Step);
|
|
|
|
// Forgets the real time paid toward the next step (a new zone starts between steps). A step cannot do this to the
|
|
// clock that is running it.
|
|
public void Reset()
|
|
{
|
|
if (_advancing) throw new InvalidOperationException("A step cannot reset the clock that is running it.");
|
|
_accumulator = 0;
|
|
}
|
|
|
|
// Feeds real seconds (negative counts as none, one frame counts at most MaxFrameSeconds) and runs the whole
|
|
// steps they pay for. Returns how many ran.
|
|
public int Advance(float realSeconds, Action<float> step)
|
|
{
|
|
_accumulator += Math.Min(Math.Max(realSeconds, 0f), MaxFrameSeconds);
|
|
var ran = 0;
|
|
_advancing = true;
|
|
try
|
|
{
|
|
while (_accumulator >= _period)
|
|
{
|
|
_accumulator -= _period;
|
|
step(Step);
|
|
Steps++;
|
|
ran++;
|
|
}
|
|
}
|
|
finally { _advancing = false; }
|
|
return ran;
|
|
}
|
|
}
|