diff options
Diffstat (limited to 'simloop/src/simloop.c')
| -rw-r--r-- | simloop/src/simloop.c | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/simloop/src/simloop.c b/simloop/src/simloop.c new file mode 100644 index 0000000..4fa5f62 --- /dev/null +++ b/simloop/src/simloop.c | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | #include <simloop.h> | ||
| 2 | |||
| 3 | #include <assert.h> | ||
| 4 | |||
| 5 | static double min(double a, double b) { return a <= b ? a : b; } | ||
| 6 | |||
| 7 | static simloop_time_t ddt_from_fps(int fps) { | ||
| 8 | static constexpr double NANOSECONDS = 1e9; | ||
| 9 | return (fps == 0) ? 0 : (simloop_time_t)(NANOSECONDS / (double)fps); | ||
| 10 | } | ||
| 11 | |||
| 12 | Simloop simloop_make(const SimloopArgs* args) { | ||
| 13 | assert(args); | ||
| 14 | assert(args->update_fps > 0); | ||
| 15 | |||
| 16 | return (Simloop){ | ||
| 17 | .frame = 0, | ||
| 18 | .update = | ||
| 19 | (SimloopTimeline){ | ||
| 20 | .ddt = ddt_from_fps(args->update_fps), | ||
| 21 | .time = 0, | ||
| 22 | }, | ||
| 23 | .render_ddt = ddt_from_fps(args->max_render_fps), | ||
| 24 | }; | ||
| 25 | } | ||
| 26 | |||
| 27 | void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) { | ||
| 28 | assert(sim); | ||
| 29 | assert(out); | ||
| 30 | |||
| 31 | sim->clock += dt; | ||
| 32 | |||
| 33 | // Simulation update. | ||
| 34 | // If the simulation falls behind the clock, we advance by a single ddt | ||
| 35 | // increment per loop iteration here and give it a chance to catch up over | ||
| 36 | // subsequent iterations. | ||
| 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 logic below. | ||
| 39 | // If the delta is too large, then we simply warp the simulation to the wall | ||
| 40 | // clock. This avoids the appearance of the simulation playing in fast-forward | ||
| 41 | // as it tries to catch up. Large time spikes can typically occur at the start | ||
| 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)); | ||
| 51 | |||
| 52 | // Loop-state update. | ||
| 53 | sim->frame += (update_this_tick ? 1 : 0); | ||
| 54 | |||
| 55 | // Interpolator for smooth animation. | ||
| 56 | // If the update falls behind the clock, then percent_frame can fall out of | ||
| 57 | // range (>1) if we are not careful. We impose that it is strictly never >1 | ||
| 58 | // to account for this case. | ||
| 59 | assert(sim->update.ddt > 0); | ||
| 60 | assert(sim->update.time <= sim->clock); | ||
| 61 | out->percent_frame = min( | ||
| 62 | 1., (double)(sim->clock - sim->update.time) / (double)sim->update.ddt); | ||
| 63 | assert((0. <= out->percent_frame) && (out->percent_frame <= 1.)); | ||
| 64 | |||
| 65 | // Render frame rate throttle. | ||
| 66 | // Note that if no max render fps is given, then render_ddt is 0. The logic | ||
| 67 | // works for both render_ddt>0 and =0. | ||
| 68 | // Need to be careful with subtraction since the quantities are unsigned. | ||
| 69 | // Subtract an epsilon to account for delays in thread scheduling. | ||
| 70 | static const simloop_time_t eps = 50'000; // 50us | ||
| 71 | out->throttle = | ||
| 72 | (sim->render_ddt > (dt - eps)) ? (sim->render_ddt - eps - dt) : 0; | ||
| 73 | |||
| 74 | out->frame = sim->frame; | ||
| 75 | out->update_elapsed = sim->update.time; | ||
| 76 | out->update_dt = sim->update.ddt; | ||
| 77 | out->should_update = update_this_tick; | ||
| 78 | } | ||
