aboutsummaryrefslogtreecommitdiff
path: root/simloop
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2026-05-02 14:58:00 -0700
committer3gg <3gg@shellblade.net>2026-05-02 14:58:00 -0700
commitb03c941b8b0ab51227aa52d10616dbd47f75e5d9 (patch)
tree0dcfdf2396804814c0d5d818c460874ced4b30b2 /simloop
parent6482f3995baf9515158d999db925ef35158cfba5 (diff)
Handle large time spikesHEADmain
Diffstat (limited to 'simloop')
-rw-r--r--simloop/src/simloop.c24
-rw-r--r--simloop/test/simloop_test.c38
2 files changed, 49 insertions, 13 deletions
diff --git a/simloop/src/simloop.c b/simloop/src/simloop.c
index d231ab3..4fa5f62 100644
--- a/simloop/src/simloop.c
+++ b/simloop/src/simloop.c
@@ -31,15 +31,23 @@ void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) {
31 sim->clock += dt; 31 sim->clock += dt;
32 32
33 // Simulation update. 33 // Simulation update.
34 // If the update falls behind the clock, we advance by a single ddt increment 34 // If the simulation falls behind the clock, we advance by a single ddt
35 // per loop iteration here and give it a chance to catch up over subsequent 35 // increment per loop iteration here and give it a chance to catch up over
36 // iterations. 36 // subsequent iterations.
37 // This has the implication that percent_frame can fall out of range (>1) if 37 // This has the implication that percent_frame can fall out of range (>1) if
38 // we are not careful with how it is defined. See the general update function 38 // we are not careful with how it is defined. See the logic below.
39 // below. 39 // If the delta is too large, then we simply warp the simulation to the wall
40 const simloop_time_t delta = sim->clock - sim->update.time; 40 // clock. This avoids the appearance of the simulation playing in fast-forward
41 const bool update_this_tick = delta >= sim->update.ddt; 41 // as it tries to catch up. Large time spikes can typically occur at the start
42 sim->update.time += update_this_tick ? sim->update.ddt : 0; 42 // of the simulation when the application loads assets, compiles shaders, etc.
43 static const uint64_t max_catchup_frames = 10;
44 const simloop_time_t delta = sim->clock - sim->update.time;
45 const uint64_t delta_frames = delta / sim->update.ddt;
46 const bool update_this_tick = delta >= sim->update.ddt;
47 const bool warp = delta_frames > max_catchup_frames;
48 sim->update.time =
49 warp ? sim->clock
50 : (sim->update.time + (update_this_tick ? sim->update.ddt : 0));
43 51
44 // Loop-state update. 52 // Loop-state update.
45 sim->frame += (update_this_tick ? 1 : 0); 53 sim->frame += (update_this_tick ? 1 : 0);
diff --git a/simloop/test/simloop_test.c b/simloop/test/simloop_test.c
index 61e7dff..bcf9d57 100644
--- a/simloop/test/simloop_test.c
+++ b/simloop/test/simloop_test.c
@@ -222,8 +222,14 @@ TEST_CASE(simloop_determinism) {
222} 222}
223 223
224/// The simulation loop attempts to catch up with the clock in the event of a 224/// The simulation loop attempts to catch up with the clock in the event of a
225/// time spike. This is possible only if the simulation loops with a frequency 225/// time spike.
226/// higher than the requested update frequency given by the update delta time. 226///
227/// Catch-up is possible only if the simulation loops with a frequency higher
228/// than the requested update frequency given by the update delta time.
229///
230/// Catch-up is performed only for sufficiently small time spikes. For large
231/// time spikes, the simulation clock is warped. This test is for the small
232/// time spike case.
227static void simloop_catch_up( 233static void simloop_catch_up(
228 struct test_case_metadata* metadata, int update_ddt_ms, int loop_step_ms, 234 struct test_case_metadata* metadata, int update_ddt_ms, int loop_step_ms,
229 bool expect_catchup) { 235 bool expect_catchup) {
@@ -241,9 +247,10 @@ static void simloop_catch_up(
241 int frames = 0; 247 int frames = 0;
242 248
243 // Simulate a time spike. 249 // Simulate a time spike.
244 // Advance time to t=10s. That is a lag of 10,000ms / 100ms = 100 frames. 250 // Advance time to t=1s. That is a lag of 1,000ms / 100ms = 10 frames.
245 // The simulation now has 20s to catch up. 251 // 10 frames is the maximum allowed catch-up.
246 simloop_time_t dt = time_delta_from_sec(10); 252 // The simulation now has 29s to catch up.
253 simloop_time_t dt = time_delta_from_sec(1);
247 for (simloop_time_t t = dt; t <= SIM_DURATION_SEC;) { 254 for (simloop_time_t t = dt; t <= SIM_DURATION_SEC;) {
248 simloop_update(&simloop, dt, &simout); 255 simloop_update(&simloop, dt, &simout);
249 256
@@ -275,4 +282,25 @@ TEST_CASE(simloop_catch_up_failure) {
275 simloop_catch_up(metadata, UPDATE_DDT_MS, LOOP_DDT_MS, false); 282 simloop_catch_up(metadata, UPDATE_DDT_MS, LOOP_DDT_MS, false);
276} 283}
277 284
285/// This tests the large time spike case, where the simulation clock is warped
286/// to the wall clock.
287TEST_CASE(simloop_warp) {
288 const int UPDATE_FPS = 50;
289 const simloop_time_t UPDATE_DDT =
290 time_delta_from_sec(1.0 / (double)UPDATE_FPS);
291
292 Simloop simloop = simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS});
293 SimloopOut simout;
294
295 // The maximum allowed catch-up is 10 frames. Simulate a time spike larger
296 // than that.
297 const simloop_time_t TIME_SPIKE = UPDATE_DDT * 20;
298 simloop_update(&simloop, TIME_SPIKE, &simout);
299 TEST_TRUE(simout.should_update); // Warp should still request update.
300
301 // Now "advance" by 0.
302 simloop_update(&simloop, 0, &simout);
303 TEST_TRUE(!simout.should_update); // No more updates after warp.
304}
305
278int main() { return 0; } 306int main() { return 0; }