aboutsummaryrefslogtreecommitdiff
path: root/simloop/include/simloop.h
blob: c5a03727e0da90f1a4db8b99138d4353bb9e2e47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* Simulation loop module.
 *
 * This implements a simulation loop but in a way that the client retains
 * control flow. The client steps the loop and then checks whether the
 * simulation must be updated and/or the result rendered.
 *
 * The simulation is updated at a fixed time step given a desired frame rate.
 * Rendering frame rate can likewise be capped or be unlimited. In any case, the
 * loop guarantees that the same frame is not rendered twice.
 *
 * Generally, the simulation's update logic should be able to keep up with the
 * requested frame rate; it is the application's responsibility to ensure this.
 * Should the update logic not be able to keep up, then the loop requests a
 * single update per iteration, effectively "degrading" to match the update
 * logic frame rate, and giving the update logic a chance to catch up with
 * subsequent loop iterations.
 *
 * Under a variable time delta, the loop could simply update the simulation
 * with a large delta that puts the simulation back into the current clock
 * time. Under a fixed time delta this isn't possible, and we seem to have two
 * choices instead:
 *
 *   a) Queue as many updates as necessary to bring the simulation back to the
 *      current clock time (time_difference / fixed_delta).
 *
 *   b) Queue a single update.
 *
 * The issue with (a) is that, if the simulation is never able to catch up, then
 * the number of requested updates at every loop iteration diverges and
 * eventually the simulation appears to freeze. Example:
 *
 *   desired delta = 10ms (100 fps)
 *   actual  delta = 20ms ( 50 fps)
 *   ---------------------------
 *   iter, sim time, clock time
 *   ---------------------------
 *      0,        0,          0, initial state
 *      1,        0,         10, queue 1 update
 *      2,       10,         30, queue (30-10)/10  = 2 updates
 *      3,       30,         70, queue (70-30)/10  = 4 updates
 *      4,       70,        150, queue (150-70)/10 = 8 updates
 *      ...
 */
#pragma once

#include <stdint.h>

typedef uint64_t simloop_time_t;

typedef struct SimloopArgs {
  int update_fps;     ///< Update frame rate. Must be >0.
  int max_render_fps; ///< Render frame rate cap. 0 to disable.
} SimloopArgs;

typedef struct SimloopOut {
  uint64_t       frame;          ///< Frame counter.
  simloop_time_t render_elapsed; ///< Amount of time elapsed in the rendering.
  simloop_time_t update_elapsed; ///< Amount of time elapsed in the simulation.
  simloop_time_t update_dt;      ///< Delta time for simulation updates.
  bool           should_update;  ///< Whether the simulation should update.
  bool           should_render;  ///< Whether the simulation should be rendered.
} SimloopOut;

typedef struct SimloopTimeline {
  simloop_time_t ddt;  ///< Desired delta time.
  simloop_time_t time; ///< Time point of the last simulation step.
} SimloopTimeline;

typedef struct Simloop {
  simloop_time_t  clock;  ///< Tracks simulation time.
  uint64_t        frame;  ///< Frame counter, number of updates done.
  SimloopTimeline update; ///< Update timeline.
  SimloopTimeline render; ///< Render timeline.
  bool            first_iter;
  bool            updates_since_last_render;
} Simloop;

/// Create a simulation loop.
Simloop simloop_make(const SimloopArgs*);

/// Step the simulation loop.
///
/// The simulation always triggers a render of the initial state of simulation.
void simloop_update(Simloop*, simloop_time_t dt, SimloopOut*);