aboutsummaryrefslogtreecommitdiff
path: root/simloop/src
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2026-05-02 14:35:43 -0700
committer3gg <3gg@shellblade.net>2026-05-02 14:35:43 -0700
commit6482f3995baf9515158d999db925ef35158cfba5 (patch)
tree17d274b78038c7a06ce0afe9f30d7416fa9f07ef /simloop/src
parent48422e313b31b79d76dd8f027b4d934994168859 (diff)
Throttle rendering to reduce CPU usage
Diffstat (limited to 'simloop/src')
-rw-r--r--simloop/src/simloop.c84
1 files changed, 24 insertions, 60 deletions
diff --git a/simloop/src/simloop.c b/simloop/src/simloop.c
index b8547fd..d231ab3 100644
--- a/simloop/src/simloop.c
+++ b/simloop/src/simloop.c
@@ -20,87 +20,51 @@ Simloop simloop_make(const SimloopArgs* args) {
20 .ddt = ddt_from_fps(args->update_fps), 20 .ddt = ddt_from_fps(args->update_fps),
21 .time = 0, 21 .time = 0,
22 }, 22 },
23 .render = 23 .render_ddt = ddt_from_fps(args->max_render_fps),
24 (SimloopTimeline){
25 .ddt = ddt_from_fps(args->max_render_fps),
26 .time = 0,
27 },
28 .percent_frame = 0.,
29 .first_iter = true,
30 }; 24 };
31} 25}
32 26
33static bool step_update(const Simloop* sim, SimloopTimeline* timeline) { 27void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) {
34 assert(sim); 28 assert(sim);
35 assert(timeline); 29 assert(out);
36 assert(timeline->ddt > 0); 30
31 sim->clock += dt;
37 32
33 // Simulation update.
38 // If the update falls behind the clock, we advance by a single ddt increment 34 // 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 35 // per loop iteration here and give it a chance to catch up over subsequent
40 // iterations. 36 // iterations.
41 // This has the implication that percent_frame can fall out of range (>1) if 37 // 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 38 // we are not careful with how it is defined. See the general update function
43 // below. 39 // below.
44 const simloop_time_t dt = sim->clock - timeline->time; 40 const simloop_time_t delta = sim->clock - sim->update.time;
45 const bool should_step = dt >= timeline->ddt; 41 const bool update_this_tick = delta >= sim->update.ddt;
46 timeline->time += should_step ? timeline->ddt : 0; 42 sim->update.time += update_this_tick ? sim->update.ddt : 0;
47 return should_step;
48}
49
50static bool step_render(const Simloop* sim, SimloopTimeline* timeline) {
51 assert(sim);
52 assert(timeline);
53
54 bool render = false;
55 if (timeline->ddt > 0) {
56 render = step_update(sim, timeline);
57 } else {
58 render = timeline->time < sim->clock;
59 timeline->time = sim->clock;
60 }
61 return render;
62}
63
64void simloop_update(Simloop* sim, simloop_time_t dt, SimloopOut* out) {
65 assert(sim);
66 assert(out);
67
68 sim->clock += dt;
69
70 // Simulation update.
71 const bool update_this_tick = step_update(sim, &sim->update);
72 43
73 // Simulation render. 44 // Loop-state update.
74 const bool render_this_tick = 45 sim->frame += (update_this_tick ? 1 : 0);
75 step_render(sim, &sim->render) ||
76 sim->first_iter; // Trigger an initial render on the first frame.
77 46
78 // Interpolator for smooth animation. 47 // 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 48 // 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 49 // range (>1) if we are not careful. We impose that it is strictly never >1
84 // to account for this case. 50 // to account for this case.
85 assert(sim->update.ddt > 0); 51 assert(sim->update.ddt > 0);
86 assert( 52 assert(sim->update.time <= sim->clock);
87 (sim->render.ddt == 0) ? (sim->update.time <= sim->render.time) : true); 53 out->percent_frame = min(
88 sim->percent_frame = 54 1., (double)(sim->clock - sim->update.time) / (double)sim->update.ddt);
89 (sim->render.time >= sim->update.time) 55 assert((0. <= out->percent_frame) && (out->percent_frame <= 1.));
90 ? min(1., ((double)(sim->render.time - sim->update.time) / 56
91 (double)sim->update.ddt)) 57 // Render frame rate throttle.
92 : sim->percent_frame; 58 // Note that if no max render fps is given, then render_ddt is 0. The logic
93 assert((0. <= sim->percent_frame) && (sim->percent_frame <= 1.)); 59 // works for both render_ddt>0 and =0.
94 60 // Need to be careful with subtraction since the quantities are unsigned.
95 // Loop state update. 61 // Subtract an epsilon to account for delays in thread scheduling.
96 sim->frame += (update_this_tick ? 1 : 0); 62 static const simloop_time_t eps = 50'000; // 50us
97 sim->first_iter = false; 63 out->throttle =
64 (sim->render_ddt > (dt - eps)) ? (sim->render_ddt - eps - dt) : 0;
98 65
99 out->frame = sim->frame; 66 out->frame = sim->frame;
100 out->render_elapsed = sim->render.time;
101 out->update_elapsed = sim->update.time; 67 out->update_elapsed = sim->update.time;
102 out->update_dt = sim->update.ddt; 68 out->update_dt = sim->update.ddt;
103 out->percent_frame = sim->percent_frame;
104 out->should_update = update_this_tick; 69 out->should_update = update_this_tick;
105 out->should_render = render_this_tick;
106} 70}