aboutsummaryrefslogtreecommitdiff
path: root/simloop/test/simloop_test.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2026-04-20 18:03:09 -0700
committer3gg <3gg@shellblade.net>2026-04-20 18:03:09 -0700
commitdadaf61c45d675f0e8b88fbc231748ad8247a736 (patch)
treee37af6f9accaa6b68ab76344ee4caa0ef6056a5d /simloop/test/simloop_test.c
parent5125d6788f7765a14fbcdeb6d4f6f67742c98596 (diff)
Percent frame interpolation factor for smooth animation
Diffstat (limited to 'simloop/test/simloop_test.c')
-rw-r--r--simloop/test/simloop_test.c108
1 files changed, 79 insertions, 29 deletions
diff --git a/simloop/test/simloop_test.c b/simloop/test/simloop_test.c
index 603a38c..50e0852 100644
--- a/simloop/test/simloop_test.c
+++ b/simloop/test/simloop_test.c
@@ -43,11 +43,14 @@ TEST_CASE(simloop_initial_render) {
43 TEST_EQUAL(simout.frame, 0); 43 TEST_EQUAL(simout.frame, 0);
44} 44}
45 45
46/// The initial render is not re-triggered if there is a render frame rate cap 46/// A frame is not re-rendered if time does not advance.
47/// and time does not advance. 47/// This applies whether rendering is frame-rate capped or unlimited, and
48TEST_CASE(simloop_initial_render_not_retriggered) { 48/// whether we are in the initial frame or a subsequent one.
49 Simloop simloop = 49void simloop_render_not_retriggered(
50 simloop_make(&(SimloopArgs){.update_fps = 10, .max_render_fps = 10}); 50 struct test_case_metadata* metadata, int max_render_fps,
51 bool initial_frame) {
52 Simloop simloop = simloop_make(
53 &(SimloopArgs){.update_fps = 10, .max_render_fps = max_render_fps});
51 SimloopOut simout; 54 SimloopOut simout;
52 55
53 simloop_update(&simloop, 0, &simout); 56 simloop_update(&simloop, 0, &simout);
@@ -56,40 +59,58 @@ TEST_CASE(simloop_initial_render_not_retriggered) {
56 TEST_TRUE(!simout.should_update); 59 TEST_TRUE(!simout.should_update);
57 TEST_EQUAL(simout.frame, 0); 60 TEST_EQUAL(simout.frame, 0);
58 61
62 if (!initial_frame) {
63 // Advance time beyond the initial frame.
64 simloop_update(&simloop, 1, &simout);
65 }
66
59 for (int i = 0; i < 10; i++) { 67 for (int i = 0; i < 10; i++) {
60 // Note that time does not advance. 68 // Note that time does not advance here.
61 simloop_update(&simloop, 0, &simout); 69 simloop_update(&simloop, 0, &simout);
62 TEST_TRUE(!simout.should_render); 70 TEST_TRUE(!simout.should_render);
63 TEST_TRUE(!simout.should_update); 71 TEST_TRUE(!simout.should_update);
64 TEST_EQUAL(simout.frame, 0); 72 TEST_EQUAL(simout.frame, 0);
65 } 73 }
66} 74}
75TEST_CASE(simloop_render_not_retriggered_capped_initial_frame) {
76 simloop_render_not_retriggered(metadata, 10, true);
77}
78TEST_CASE(simloop_render_not_retriggered_unlimited_initial_frame) {
79 simloop_render_not_retriggered(metadata, 0, true);
80}
81TEST_CASE(simloop_render_not_retriggered_capped_subsequent_frame) {
82 simloop_render_not_retriggered(metadata, 10, false);
83}
84TEST_CASE(simloop_render_not_retriggered_unlimited_subsequent_frame) {
85 simloop_render_not_retriggered(metadata, 0, false);
86}
67 87
68/// A simulation loop with no render frame cap: 88/// A simulation loop with no render frame cap:
69/// 1. Updates based on the desired update frame rate. 89/// 1. Updates based on the desired update frame rate.
70/// 2. Renders at every loop (provided there are updates). 90/// 2. Renders at every step.
71TEST_CASE(simloop_no_render_frame_cap) { 91TEST_CASE(simloop_no_render_frame_cap) {
72 constexpr int UPDATE_FPS = 10; // 100ms delta 92 constexpr int UPDATE_FPS = 10; // 100ms delta
73 const simloop_time_t UPDATE_DDT = 93 const simloop_time_t UPDATE_DDT =
74 time_delta_from_sec(1.0 / (double)UPDATE_FPS); 94 time_delta_from_sec(1.0 / (double)UPDATE_FPS);
75 const simloop_time_t STEP = time_delta_from_sec(1); 95 const simloop_time_t STEP = time_delta_from_sec(0.05); // 50ms
76 const simloop_time_t SIM_TIME_SEC = time_delta_from_sec(30); 96 const simloop_time_t SIM_DURATION_SEC = time_delta_from_sec(30);
77 97
78 // We need simulation time to be an exact multiple of the desired deltas for 98 // We need simulation time to be an exact multiple of the desired deltas for
79 // the modulo comparisons below. 99 // the modulo comparison below.
80 TEST_TRUE((STEP % UPDATE_DDT) == 0); 100 TEST_TRUE((UPDATE_DDT % STEP) == 0);
81 101
82 simloop_time_t dt = 0;
83 Simloop simloop = simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS}); 102 Simloop simloop = simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS});
84 SimloopOut simout; 103 SimloopOut simout;
85 104
86 for (simloop_time_t t = 0; t <= SIM_TIME_SEC; t += STEP) { 105 simloop_update(&simloop, 0, &simout);
87 simloop_update(&simloop, dt, &simout); 106 TEST_TRUE(!simout.should_update); // Time has not advanced.
88 const bool expect_update = (t > 0) && ((t % UPDATE_DDT) == 0); 107 TEST_TRUE(simout.should_render); // Initial render.
89 // A render is still expected at time 0. 108
90 TEST_EQUAL(simout.should_render, (t == 0) || expect_update); 109 for (simloop_time_t t = STEP; t <= SIM_DURATION_SEC; t += STEP) {
110 simloop_update(&simloop, STEP, &simout);
111 const bool expect_update = (t % UPDATE_DDT) == 0;
91 TEST_EQUAL(simout.should_update, expect_update); 112 TEST_EQUAL(simout.should_update, expect_update);
92 dt = STEP; 113 TEST_TRUE(simout.should_render); // Always renders.
93 } 114 }
94} 115}
95 116
@@ -103,25 +124,54 @@ TEST_CASE(simloop_with_render_frame_cap) {
103 time_delta_from_sec(1.0 / (double)UPDATE_FPS); 124 time_delta_from_sec(1.0 / (double)UPDATE_FPS);
104 const simloop_time_t RENDER_DDT = 125 const simloop_time_t RENDER_DDT =
105 time_delta_from_sec(1.0 / (double)RENDER_FPS); 126 time_delta_from_sec(1.0 / (double)RENDER_FPS);
106 const simloop_time_t STEP = time_delta_from_sec(0.1); // 100ms 127 const simloop_time_t STEP = time_delta_from_sec(0.1); // 100ms
107 const simloop_time_t SIM_TIME_SEC = time_delta_from_sec(30); 128 const simloop_time_t SIM_DURATION_SEC = time_delta_from_sec(30);
108 129
109 // We need simulation time to be an exact multiple of the desired deltas for 130 // We need simulation time to be an exact multiple of the desired deltas for
110 // the modulo comparisons below. 131 // the modulo comparisons below.
111 TEST_TRUE((UPDATE_DDT % STEP) == 0); 132 TEST_TRUE((UPDATE_DDT % STEP) == 0);
112 TEST_TRUE((RENDER_DDT % STEP) == 0); 133 TEST_TRUE((RENDER_DDT % STEP) == 0);
113 134
114 simloop_time_t dt = 0; 135 Simloop simloop = simloop_make(
115 Simloop simloop = simloop_make(
116 &(SimloopArgs){.update_fps = UPDATE_FPS, .max_render_fps = RENDER_FPS}); 136 &(SimloopArgs){.update_fps = UPDATE_FPS, .max_render_fps = RENDER_FPS});
117 SimloopOut simout; 137 SimloopOut simout;
118 138
119 for (simloop_time_t t = 0; t <= SIM_TIME_SEC; t += STEP) { 139 simloop_update(&simloop, 0, &simout);
120 simloop_update(&simloop, dt, &simout); 140 TEST_TRUE(!simout.should_update); // Time has not advanced.
141 TEST_TRUE(simout.should_render); // Initial render.
142
143 for (simloop_time_t t = STEP; t <= SIM_DURATION_SEC; t += STEP) {
144 simloop_update(&simloop, STEP, &simout);
121 // A render is still expected at time 0. 145 // A render is still expected at time 0.
122 TEST_EQUAL(simout.should_render, (t % RENDER_DDT) == 0); 146 TEST_EQUAL(simout.should_render, (t % RENDER_DDT) == 0);
123 TEST_EQUAL(simout.should_update, (t > 0) && ((t % UPDATE_DDT) == 0)); 147 TEST_EQUAL(simout.should_update, (t % UPDATE_DDT) == 0);
124 dt = STEP; 148 }
149}
150
151/// If the update falls behind the clock, then percent_frame can fall out of
152/// range (>1) if we are not careful. This tests for this condition.
153TEST_CASE(simloop_percent_frame_01_large_jump) {
154 constexpr int UPDATE_FPS = 10; // 100ms delta
155 const simloop_time_t UPDATE_DDT =
156 time_delta_from_sec(1.0 / (double)UPDATE_FPS);
157 const simloop_time_t STEP = time_delta_from_sec(1);
158 const simloop_time_t SIM_DURATION_SEC = time_delta_from_sec(30);
159
160 // We need simulation time to be an exact multiple of the desired deltas for
161 // the modulo comparison below.
162 TEST_TRUE((STEP % UPDATE_DDT) == 0);
163
164 Simloop simloop = simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS});
165 SimloopOut simout;
166
167 simloop_update(&simloop, 0, &simout);
168 TEST_TRUE(!simout.should_update); // Time has not advanced.
169 TEST_TRUE(simout.should_render); // Initial render.
170
171 for (simloop_time_t t = STEP; t <= SIM_DURATION_SEC; t += STEP) {
172 simloop_update(&simloop, STEP, &simout);
173 TEST_TRUE(simout.should_update); // Tries to catch up to clock.
174 TEST_TRUE(simout.should_render);
125 } 175 }
126} 176}
127 177
@@ -144,8 +194,8 @@ TEST_CASE(simloop_determinism) {
144 }; 194 };
145 constexpr uint64_t NUM_RANDOM_STEPS = 195 constexpr uint64_t NUM_RANDOM_STEPS =
146 sizeof(RANDOM_STEPS) / sizeof(RANDOM_STEPS[0]); 196 sizeof(RANDOM_STEPS) / sizeof(RANDOM_STEPS[0]);
147 const simloop_time_t SIM_TIME_SEC = time_delta_from_sec(10); 197 const simloop_time_t SIM_DURATION_SEC = time_delta_from_sec(10);
148 constexpr float ADD = 0.123f; 198 constexpr float ADD = 0.123f;
149 199
150 typedef struct Simulation { 200 typedef struct Simulation {
151 int iter_count; 201 int iter_count;
@@ -167,7 +217,7 @@ TEST_CASE(simloop_determinism) {
167 Simloop simloop = simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS}); 217 Simloop simloop = simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS});
168 SimloopOut simout; 218 SimloopOut simout;
169 219
170 for (simloop_time_t t = 0; t <= SIM_TIME_SEC;) { 220 for (simloop_time_t t = 0; t <= SIM_DURATION_SEC;) {
171 simloop_update(&simloop, dt, &simout); 221 simloop_update(&simloop, dt, &simout);
172 222
173 if (simout.should_update) { 223 if (simout.should_update) {