diff options
Diffstat (limited to 'simloop/test')
| -rw-r--r-- | simloop/test/simloop_test.c | 106 |
1 files changed, 100 insertions, 6 deletions
diff --git a/simloop/test/simloop_test.c b/simloop/test/simloop_test.c index 9f11e86..41f9ac2 100644 --- a/simloop/test/simloop_test.c +++ b/simloop/test/simloop_test.c | |||
| @@ -3,13 +3,32 @@ | |||
| 3 | #include <test.h> | 3 | #include <test.h> |
| 4 | #include <timer.h> | 4 | #include <timer.h> |
| 5 | 5 | ||
| 6 | #include <stdint.h> | ||
| 7 | |||
| 8 | // ----------------------------------------------------------------------------- | ||
| 9 | // Randomness. | ||
| 10 | |||
| 11 | typedef struct { | ||
| 12 | uint64_t a; | ||
| 13 | } XorShift64State; | ||
| 14 | |||
| 15 | uint64_t xorshift64(XorShift64State* state) { | ||
| 16 | uint64_t x = state->a; | ||
| 17 | x ^= x << 7; | ||
| 18 | x ^= x >> 9; | ||
| 19 | return state->a = x; | ||
| 20 | } | ||
| 21 | |||
| 22 | // ----------------------------------------------------------------------------- | ||
| 23 | // Tests. | ||
| 24 | |||
| 6 | /// At time/frame 0: | 25 | /// At time/frame 0: |
| 7 | /// 1. An initial render is always triggered. | 26 | /// 1. An initial render is always triggered. |
| 8 | /// 2. No update is triggered (not enough time passed). | 27 | /// 2. No update is triggered (not enough time passed). |
| 9 | TEST_CASE(simloop_initial_render) { | 28 | TEST_CASE(simloop_initial_render) { |
| 10 | Timer timer = {}; | 29 | Timer timer = {}; |
| 11 | Simloop simloop = simloop_make( | 30 | Simloop simloop = |
| 12 | &(SimloopArgs){.update_fps = 10, .max_render_fps = 0, .timer = &timer}); | 31 | simloop_make(&(SimloopArgs){.update_fps = 10, .timer = &timer}); |
| 13 | SimloopOut simout; | 32 | SimloopOut simout; |
| 14 | 33 | ||
| 15 | simloop_update(&simloop, &simout); | 34 | simloop_update(&simloop, &simout); |
| @@ -51,9 +70,9 @@ TEST_CASE(simloop_no_render_frame_cap) { | |||
| 51 | const time_delta STEP = sec_to_time_delta(1); | 70 | const time_delta STEP = sec_to_time_delta(1); |
| 52 | const time_delta SIM_TIME_SEC = sec_to_time_delta(30); | 71 | const time_delta SIM_TIME_SEC = sec_to_time_delta(30); |
| 53 | 72 | ||
| 54 | Timer timer = {}; | 73 | Timer timer = {}; |
| 55 | Simloop simloop = simloop_make(&(SimloopArgs){ | 74 | Simloop simloop = |
| 56 | .update_fps = UPDATE_FPS, .max_render_fps = 0, .timer = &timer}); | 75 | simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS, .timer = &timer}); |
| 57 | SimloopOut simout; | 76 | SimloopOut simout; |
| 58 | 77 | ||
| 59 | for (time_delta t = 0; t < SIM_TIME_SEC; t += STEP) { | 78 | for (time_delta t = 0; t < SIM_TIME_SEC; t += STEP) { |
| @@ -91,4 +110,79 @@ TEST_CASE(simloop_with_render_frame_cap) { | |||
| 91 | } | 110 | } |
| 92 | } | 111 | } |
| 93 | 112 | ||
| 113 | /// One benefit of fixed over variable time deltas is determinism. Test for | ||
| 114 | /// this by getting to t=10 by different clock time increments. | ||
| 115 | /// | ||
| 116 | /// Note that the time increments must be able to keep up with the desired frame | ||
| 117 | /// delta, otherwise determinism is not maintained. We can guarantee determinism | ||
| 118 | /// at the expense of re-introducing divergence. | ||
| 119 | /// TODO: Perhaps the API should return an update count instead of a boolean, | ||
| 120 | /// advance simulation time per the number of updates, then leave it up to | ||
| 121 | /// the client to decide whether to update just once or as many times as | ||
| 122 | /// requested, depending on whether they want determinism or convergence. | ||
| 123 | TEST_CASE(simloop_determinism) { | ||
| 124 | constexpr int UPDATE_FPS = 100; // 10ms delta | ||
| 125 | const time_delta RANDOM_STEPS[] = { | ||
| 126 | sec_to_time_delta(0.007), // 7ms | ||
| 127 | sec_to_time_delta(0.005), // 5ms | ||
| 128 | sec_to_time_delta(0.003), // 3ms | ||
| 129 | }; | ||
| 130 | constexpr uint64_t NUM_RANDOM_STEPS = | ||
| 131 | sizeof(RANDOM_STEPS) / sizeof(RANDOM_STEPS[0]); | ||
| 132 | const time_delta SIM_TIME_SEC = sec_to_time_delta(10); | ||
| 133 | constexpr float ADD = 0.123f; | ||
| 134 | |||
| 135 | typedef struct Simulation { | ||
| 136 | int iter_count; | ||
| 137 | float sum; | ||
| 138 | } Simulation; | ||
| 139 | |||
| 140 | #define UPDATE_SIMULATION(SIM) \ | ||
| 141 | { \ | ||
| 142 | SIM.sum += ADD; \ | ||
| 143 | SIM.iter_count++; \ | ||
| 144 | } | ||
| 145 | |||
| 146 | Simulation sim[2] = {0}; | ||
| 147 | XorShift64State xss = (XorShift64State){12069019817132197873}; | ||
| 148 | |||
| 149 | // Perform two simulations with random clock-time steps. | ||
| 150 | for (int s = 0; s < 2; ++s) { | ||
| 151 | Timer timer = {}; | ||
| 152 | Simloop simloop = | ||
| 153 | simloop_make(&(SimloopArgs){.update_fps = UPDATE_FPS, .timer = &timer}); | ||
| 154 | SimloopOut simout; | ||
| 155 | |||
| 156 | for (time_delta t = 0; t < SIM_TIME_SEC;) { | ||
| 157 | timer_advance(&timer, t); | ||
| 158 | simloop_update(&simloop, &simout); | ||
| 159 | |||
| 160 | if (simout.should_update) { | ||
| 161 | UPDATE_SIMULATION(sim[s]); | ||
| 162 | } | ||
| 163 | |||
| 164 | // Advance time with a random step. | ||
| 165 | const time_delta step = RANDOM_STEPS[xorshift64(&xss) % NUM_RANDOM_STEPS]; | ||
| 166 | t += step; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | // Make sure the simulations have advanced by the same number of updates so | ||
| 171 | // that we can compare them. They may not have had the same update count | ||
| 172 | // depending on the clock-time steps. | ||
| 173 | while (sim[0].iter_count < sim[1].iter_count) { | ||
| 174 | UPDATE_SIMULATION(sim[0]); | ||
| 175 | } | ||
| 176 | while (sim[1].iter_count < sim[0].iter_count) { | ||
| 177 | UPDATE_SIMULATION(sim[1]); | ||
| 178 | } | ||
| 179 | TEST_EQUAL(sim[0].iter_count, sim[1].iter_count); | ||
| 180 | |||
| 181 | // The sums should be exactly equal if determinism holds. | ||
| 182 | // Check also that they are non-zero to make sure the simulation actually | ||
| 183 | // advanced. | ||
| 184 | TEST_TRUE(sim[0].sum > 0.f); | ||
| 185 | TEST_EQUAL(sim[0].sum, sim[1].sum); | ||
| 186 | } | ||
| 187 | |||
| 94 | int main() { return 0; } | 188 | int main() { return 0; } |
