The built-in time-stepping support for the upcoming release of SmartMS [1] got an overhaul in the form of a now frame-rate oriented Game View component, and a simulation-oriented TMetronome time-stepper.
This article quickly discusses the theory, changes, and gives an introduction for use in games and simulations.
Time-Stepping in Simulated Worlds
Time stepping in simulated worlds is a general problem not restricted to games.
Basically, you have to deal with screen updates that take a variable amount of times, not just because what you need to draw (render) on the screen can vary (which would be bad enough), but because the environment introduces latencies (other applications taking CPU time, network lag, storage access times, etc.).
While you sometimes want to render at a high frame-rate, outside of benchmarks, there is no point rendering more frames than the screen can display, as it’ll only waste energy.
On the simulation side, you usually want some reproduce-ability (if only for debugging and testing), and you want to ensure that while the visuals can depend on the capability of the hardware you’re running on, the simulation shouldn’t depend on the hardware, which means fixed time-steps.
So the general problem is to adapt a variable time-step problem (rendering) with desired synchronization (with screen refresh) and a simulation with fixed time-steps.
A good discussion of the pros and cons can be found in Fixed time step vs Variable time step [2].
Next: Decoupling Refresh Rate from Simulation [3]
Previous: Time-Stepping in Simulated Worlds. [4]
Decoupling Refresh Rate from Simulation
That’s usually accomplished by rendering as fast as the hardware can, ideally at the refresh rate [5] (or “VSYNC”, in reference to old CRT display), measuring the time elapsed since the last frame, accumulating that time and advancing the simulation in fixed time-steps.
This also means that you can safely decouple the time-stepping of visual effects and rendering from that of the simulation. The time-stepping of your simulation should be tied to the needs of the physics, while the rendering should be tied to the needs of the eye (or the capability of the hardware).
In practice, for some rendered frames, the simulation may not advance, and for others, it may advance several steps.
This can be the case if your simulation is fairly complex, and is only realistically stepped at 15 Hz, while visually you may want a smoother 60 FPS (or more). In those situations:
- the visual rendering will typically rely on “interpolation” to add the extra smoothness.
- each simulation progression step (at 60 Hz) will perform
- 1/4 of the complete simulation step if your engine is single-threaded
- a synchronization attempt if the simulation is multi-threaded
A simulation may (should?) involve multiple sub-simulations, that will run at various time-steps, for instance in a game you could have:
- collision-detection and physics for near objects evolving at 100 or 200 Hz
- AI and game scripts running at 30 Hz
- distant objects evolving at 10 Hz
- weather/environment evolving at 0.1 Hz
This is a simple way to focus processing resources on simulated aspects the protagonist(s) will see and interact with.
Previous: Decoupling Refresh Rate from Simulation. [3]
Updated Game View
[7]Previously the TW3GameView was simply trying to issue screen repaints at fixed intervals, what happened if the hardware couldn’t keep up (or could have rendered faster) was your problem.
The new TW3GameView now has several modes:
- the “VSYNC” mode (just set the Delay to zero), where it will try to refresh the screen as fast as the display can refresh it (and not faster), while minimizing screen tearing
- the FrameRate mode, where you specify the target frame-rate (in Hz)
- the old Delay mode, where you specify the delay between frames in milliseconds
Additionally, the view will now automatically suspend repaints if the view goes off-screen (application going to background in a mobile app, tab switch in a browser). I you don’t want that and still want the repaints to occur, just set the AutoPause property to False.
In addition, the overhead of the game view got reduced and the accuracy of frame-rate measurement got improved, and a LastFrameTime provides high-precision measurement for the time since the last frame was rendered.
In terms of Web Standards, the new game view leverages the following APIs:
- Performance API [8] for sub-millisecond precision
- RequestAnimationFrame [9] for VSYNC
- PageVisibility [10] for automatic pause and suspensions
These API have widespread support, and if not available, the view will automatically fallback to legacy approaches.
Next: Fix Your Steps with a Metronome [11]
Previous: A Smarter Game View. [6]
The new TMetronome
A new class TMetronome in System.Metronome allows to obtain fixed time steps from the variable time-steps of the game view repaints (or any other variable time-steps, TMetronome is an independent class).
You just need to create a TMetronome class, initialize it, feed it your variable time steps, and you’ll get fixed-time steps (“ticks”) in it OnProgress event. A typical use case will look like:
procedure TYourApplication.ApplicationStarting; begin ... FMetronome := TMetronome.Create; FMetronome.TickFrequency := 100; // run simulation at 100 Hz FMetronome.OnProgress := DoSimulationProgress; // fixed time-steps events there end; procedure TYourApplication.PaintView(Canvas:TW3Canvas); begin // we come here in variable time-steps // we pass the variable time elapsed since the last frame to the metronome // which will generate fixed time steps for DoSimulationProgress FMetronome.Progress(GameView.LastFrameTime); ... // paint your view here end;
Sometimes the hardware may just not be able to keep up (if just temporarily), and you may be faced with having to either stall the simulation, or just skip ahead.
The threshold for that is determined by the MaxTicksPerProgress property, and what to do by the ProgressPolicy, which can be used to determine if you’ll Stall the simulation (Ticks won’t advance by more than MaxTicksPerProgress), or just Skip ahead.
You can have as many metronomes as you want, and then you can let every simulation progress at its own rythm:
FMetronomes : array of TMetronome; ... for var metronome in FMetronomes do metronome.Progress(lastFrameTime);
This will hopefully simplify mix’n’matching different simulations in the same world, and you can easily and independently adjust the frequency at which everything progresses.