From 879e5af4eb1a8972fc944853a515e1003f94bd7c Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 11 Apr 2026 12:27:23 -0700 Subject: Fix simloop divergence --- simloop/test/simloop_test.c | 72 ++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 20 deletions(-) (limited to 'simloop/test') diff --git a/simloop/test/simloop_test.c b/simloop/test/simloop_test.c index 3f2aa46..c79ee32 100644 --- a/simloop/test/simloop_test.c +++ b/simloop/test/simloop_test.c @@ -3,57 +3,89 @@ #include #include -/// An initial render should always trigger on frame 0. +/// At time/frame 0: +/// 1. An initial render is always triggered. +/// 2. No update is triggered (not enough time passed). TEST_CASE(simloop_initial_render) { - constexpr int UPDATE_FPS = 10; + Timer timer = {}; + Simloop simloop = simloop_make( + &(SimloopArgs){.update_fps = 10, .max_render_fps = 0, .timer = &timer}); + SimloopOut simout; - Timer timer = {}; - Simloop simloop = simloop_make(&(SimloopArgs){ - .update_fps = UPDATE_FPS, .max_render_fps = 0, .timer = &timer}); + simloop_update(&simloop, &simout); + + TEST_TRUE(simout.should_render); + TEST_TRUE(!simout.should_update); + TEST_EQUAL(simout.frame, 0); +} + +/// The initial render is not re-triggered if there is a render frame rate cap +/// and time does not advance. +TEST_CASE(simloop_initial_render_not_retriggered) { + Timer timer = {}; + Simloop simloop = simloop_make( + &(SimloopArgs){.update_fps = 10, .max_render_fps = 10, .timer = &timer}); SimloopOut simout; simloop_update(&simloop, &simout); + TEST_TRUE(simout.should_render); + TEST_TRUE(!simout.should_update); + TEST_EQUAL(simout.frame, 0); + + for (int i = 0; i < 10; i++) { + // Note that time does not advance. + simloop_update(&simloop, &simout); + TEST_TRUE(!simout.should_render); + TEST_TRUE(!simout.should_update); + TEST_EQUAL(simout.frame, 0); + } } -/// A simulation loop with no render frame cap. -TEST_CASE(simloop_test_no_render_frame_cap) { - constexpr int UPDATE_FPS = 10; - const time_delta STEP = sec_to_time_delta(1); - const time_delta SIM_TIME_SEC = sec_to_time_delta(30); +/// A simulation loop with no render frame cap: +/// 1. Updates based on the desired update frame rate. +/// 2. Renders at every loop. +TEST_CASE(simloop_no_render_frame_cap) { + constexpr int UPDATE_FPS = 10; + const time_delta EXPECT_UPDATE = sec_to_time_delta(1.0 / (double)UPDATE_FPS); + const time_delta STEP = sec_to_time_delta(1); + const time_delta SIM_TIME_SEC = sec_to_time_delta(30); Timer timer = {}; Simloop simloop = simloop_make(&(SimloopArgs){ .update_fps = UPDATE_FPS, .max_render_fps = 0, .timer = &timer}); SimloopOut simout; - for (time_delta t = STEP; t < SIM_TIME_SEC; t += STEP) { + for (time_delta t = 0; t < SIM_TIME_SEC; t += STEP) { timer_advance(&timer, t); simloop_update(&simloop, &simout); TEST_TRUE(simout.should_render); - TEST_EQUAL(simout.updates_pending, UPDATE_FPS); + TEST_EQUAL((t > 0) && ((t % EXPECT_UPDATE) == 0), simout.should_update); } } -/// A simulation loop with a render frame cap. -TEST_CASE(simloop_test_with_render_frame_cap) { +/// A simulation loop with a render frame cap: +/// 1. Updates based on the desired update frame rate. +/// 2. Renders based on the desired render frame rate. +TEST_CASE(simloop_with_render_frame_cap) { constexpr int UPDATE_FPS = 10; constexpr int RENDER_FPS = 5; - const time_delta STEP = sec_to_time_delta(0.1); - const time_delta SIM_TIME_SEC = sec_to_time_delta(30); const time_delta EXPECT_UPDATE = sec_to_time_delta(1.0 / (double)UPDATE_FPS); const time_delta EXPECT_RENDER = sec_to_time_delta(1.0 / (double)RENDER_FPS); + const time_delta STEP = sec_to_time_delta(0.1); + const time_delta SIM_TIME_SEC = sec_to_time_delta(30); Timer timer = {}; Simloop simloop = simloop_make(&(SimloopArgs){ - .update_fps = UPDATE_FPS, .max_render_fps = 0, .timer = &timer}); + .update_fps = UPDATE_FPS, .max_render_fps = RENDER_FPS, .timer = &timer}); SimloopOut simout; - for (time_delta t = STEP; t < SIM_TIME_SEC; t += STEP) { + for (time_delta t = 0; t < SIM_TIME_SEC; t += STEP) { timer_advance(&timer, t); simloop_update(&simloop, &simout); - TEST_TRUE(((STEP % EXPECT_RENDER) == 0) ? simout.should_render : true); - TEST_TRUE(((STEP % EXPECT_UPDATE) == 0) ? simout.updates_pending : true); + // Also expecting initial render at t=0. + TEST_EQUAL((t % EXPECT_RENDER) == 0, simout.should_render); + TEST_EQUAL((t > 0) && ((t % EXPECT_UPDATE) == 0), simout.should_update); } } -- cgit v1.2.3