aboutsummaryrefslogtreecommitdiff
path: root/simloop/src
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/src
parent5125d6788f7765a14fbcdeb6d4f6f67742c98596 (diff)
Percent frame interpolation factor for smooth animation
Diffstat (limited to 'simloop/src')
-rw-r--r--simloop/src/simloop.c49
1 files changed, 36 insertions, 13 deletions
diff --git a/simloop/src/simloop.c b/simloop/src/simloop.c
index bd5a72d..b8547fd 100644
--- a/simloop/src/simloop.c
+++ b/simloop/src/simloop.c
@@ -2,6 +2,8 @@
2 2
3#include <assert.h> 3#include <assert.h>
4 4
5static double min(double a, double b) { return a <= b ? a : b; }
6
5static simloop_time_t ddt_from_fps(int fps) { 7static simloop_time_t ddt_from_fps(int fps) {
6 static constexpr double NANOSECONDS = 1e9; 8 static constexpr double NANOSECONDS = 1e9;
7 return (fps == 0) ? 0 : (simloop_time_t)(NANOSECONDS / (double)fps); 9 return (fps == 0) ? 0 : (simloop_time_t)(NANOSECONDS / (double)fps);
@@ -23,8 +25,8 @@ Simloop simloop_make(const SimloopArgs* args) {
23 .ddt = ddt_from_fps(args->max_render_fps), 25 .ddt = ddt_from_fps(args->max_render_fps),
24 .time = 0, 26 .time = 0,
25 }, 27 },
26 .first_iter = true, 28 .percent_frame = 0.,
27 .updates_since_last_render = false, 29 .first_iter = true,
28 }; 30 };
29} 31}
30 32
@@ -33,6 +35,12 @@ static bool step_update(const Simloop* sim, SimloopTimeline* timeline) {
33 assert(timeline); 35 assert(timeline);
34 assert(timeline->ddt > 0); 36 assert(timeline->ddt > 0);
35 37
38 // If the update falls behind the clock, we advance by a single ddt increment
39 // per loop iteration here and give it a chance to catch up over subsequent
40 // iterations.
41 // This has the implication that percent_frame can fall out of range (>1) if
42 // we are not careful with how it is defined. See the general update function
43 // below.
36 const simloop_time_t dt = sim->clock - timeline->time; 44 const simloop_time_t dt = sim->clock - timeline->time;
37 const bool should_step = dt >= timeline->ddt; 45 const bool should_step = dt >= timeline->ddt;
38 timeline->time += should_step ? timeline->ddt : 0; 46 timeline->time += should_step ? timeline->ddt : 0;
@@ -47,8 +55,8 @@ static bool step_render(const Simloop* sim, SimloopTimeline* timeline) {
47 if (timeline->ddt > 0) { 55 if (timeline->ddt > 0) {
48 render = step_update(sim, timeline); 56 render = step_update(sim, timeline);
49 } else { 57 } else {
58 render = timeline->time < sim->clock;
50 timeline->time = sim->clock; 59 timeline->time = sim->clock;
51 render = true;
52 } 60 }
53 return render; 61 return render;
54} 62}
@@ -60,24 +68,39 @@ void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) {
60 sim->clock += dt; 68 sim->clock += dt;
61 69
62 // Simulation update. 70 // Simulation update.
63 const bool updated = step_update(sim, &sim->update); 71 const bool update_this_tick = step_update(sim, &sim->update);
64 sim->updates_since_last_render = sim->updates_since_last_render || updated;
65 72
66 // Simulation render. 73 // Simulation render.
67 const bool rendered = 74 const bool render_this_tick =
68 (sim->updates_since_last_render && step_render(sim, &sim->render)) || 75 step_render(sim, &sim->render) ||
69 (sim->first_iter); // Trigger an initial render on the first frame. 76 sim->first_iter; // Trigger an initial render on the first frame.
70 sim->updates_since_last_render = 77
71 sim->updates_since_last_render && !out->should_render; 78 // Interpolator for smooth animation.
79 // If rendering is not frame-rate capped, then its timeline should always be
80 // at least as recent as the update's. Otherwise, it is possible for the
81 // rendering timeline to be behind.
82 // If the update falls behind the clock, then percent_frame can fall out of
83 // range (>1) if we are not careful. We impose that it is strictly never >1
84 // to account for this case.
85 assert(sim->update.ddt > 0);
86 assert(
87 (sim->render.ddt == 0) ? (sim->update.time <= sim->render.time) : true);
88 sim->percent_frame =
89 (sim->render.time >= sim->update.time)
90 ? min(1., ((double)(sim->render.time - sim->update.time) /
91 (double)sim->update.ddt))
92 : sim->percent_frame;
93 assert((0. <= sim->percent_frame) && (sim->percent_frame <= 1.));
72 94
73 // Loop state update. 95 // Loop state update.
74 sim->frame += (updated ? 1 : 0); 96 sim->frame += (update_this_tick ? 1 : 0);
75 sim->first_iter = false; 97 sim->first_iter = false;
76 98
77 out->frame = sim->frame; 99 out->frame = sim->frame;
78 out->render_elapsed = sim->render.time; 100 out->render_elapsed = sim->render.time;
79 out->update_elapsed = sim->update.time; 101 out->update_elapsed = sim->update.time;
80 out->update_dt = sim->update.ddt; 102 out->update_dt = sim->update.ddt;
81 out->should_update = updated; 103 out->percent_frame = sim->percent_frame;
82 out->should_render = rendered; 104 out->should_update = update_this_tick;
105 out->should_render = render_this_tick;
83} 106}