From 48422e313b31b79d76dd8f027b4d934994168859 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sun, 26 Apr 2026 13:06:44 -0700 Subject: Test for catch-up. Document --- simloop/README.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 simloop/README.md (limited to 'simloop/README.md') diff --git a/simloop/README.md b/simloop/README.md new file mode 100644 index 0000000..f45ca05 --- /dev/null +++ b/simloop/README.md @@ -0,0 +1,99 @@ +# Simulation loop module + +Simulation loop for games and graphics applications. + +## Features + +- Client retains control flow. +- Client-controlled time axis. +- Updates are frame-rate capped and use a fixed time step for determinism. +- Rendering is optionally frame-rate capped. +- Interpolation factor for smooth animation and rendering between frames. + +Control flow: the client steps the loop and then checks whether the simulation +must be updated and/or the result rendered. Time readings are external to the +library and provided by the client. + +## Invariants + +- An initial render of the initial application state is always triggered. +- The same frame is not re-rendered if time does not advance. +- Animation interpolation factor in [0,1]. + +## Handling Time Spikes + +Generally, the simulation's update logic should be able to keep up with the +requested frame rate; it is the application's responsibility to ensure this. +Specifically, the frequency with which the application loops must be higher +than the requested update frequency, given by the update delta time. + +However, occasional time spikes may occur, for example when switching to the +desktop or when pausing the application in a debugger. The library handles this +simply by requesting an update from the application. Under the assumption that +the loop frequency is higher than the update frequency, the simulation will +catch up with the real-time clock. + +### Time Spikes in Detail + +When a time spike occurs, the simulation clock falls significantly behind the +real-time clock. Ideally, the simulation should be able to recover and catch up +to the real-time clock when this occurs. + +Under a variable time delta, the loop could simply update the simulation with +a large delta that puts the simulation back into the current clock time. +Under a fixed time delta, this isn't possible, and we seem to have the +following choices instead: + +- a) Queue as many updates as necessary to bring the simulation back to the + current clock time (time_difference / fixed_delta). +- b) Queue a single update. +- c) Some middle ground between the two. + +The issue with (a) is that, if the simulation is never able to catch up, then +the number of requested updates at every loop iteration diverges and the +simulation eventually appears to freeze. + +(b) only works if: + +- clock time added per iter < desired update delta time + +Where: + +- clock time added per iter = update time + render time + vsync + etc +- desired delta time = 1 / update frequency + +If the clock time added per iteration is greater or equal to the desired delta, +then the simulation can never "catch up" and recover from the spike. + +The middle ground is to perform only some number of updates in each loop +iteration N. The simulation catches up only if: + +- clock time added per iter < N * desired update delta time + +The ideal value of N depends on how many frames the application can actually +render. For example, if the application is vsync'ed to a 240hz monitor and is +able to render that many frames, then: + +- N = ceil(1/240hz / desired update delta time) + +Realistically, the actual frame rate will be variable. Moreover, if we queued +as many frames as possible, then we would risk the freeze in option (a) if the +actual update time were too large for the application to catch up. So the +library can only guess the value of N. + +The library picks a small constant value of N, implementation-defined, that the +application can override. + +### Example: Spike Handling with Option (A) + +- desired delta = 10ms (100 fps) +- actual delta = 20ms ( 50 fps) + +| iter | sim time | clock time | comment | +|------|----------|------------|-------------------------------| +| 0 | 0 | 0 | initial state | +| 1 | 0 | 10 | queue 1 update | +| 2 | 10 | 30 | queue (30-10)/10 = 2 updates | +| 3 | 30 | 70 | queue (70-30)/10 = 4 updates | +| 4 | 70 | 150 | queue (150-70)/10 = 8 updates | +| ... | -- cgit v1.2.3