diff options
| author | 3gg <3gg@shellblade.net> | 2026-05-02 14:58:00 -0700 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2026-05-02 14:58:00 -0700 |
| commit | b03c941b8b0ab51227aa52d10616dbd47f75e5d9 (patch) | |
| tree | 0dcfdf2396804814c0d5d818c460874ced4b30b2 /simloop | |
| parent | 6482f3995baf9515158d999db925ef35158cfba5 (diff) | |
Diffstat (limited to 'simloop')
| -rw-r--r-- | simloop/src/simloop.c | 24 | ||||
| -rw-r--r-- | simloop/test/simloop_test.c | 38 |
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. | ||
| 227 | static void simloop_catch_up( | 233 | static 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. | ||
| 287 | TEST_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 | |||
| 278 | int main() { return 0; } | 306 | int main() { return 0; } |
