/* 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, an interpolation factor is computed for smooth animation between * updates. * The implementation also guarantees that the same frame is not rendered twice * if time does not advance. * * 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, 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 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. double percent_frame; ///< Percent progress between this frame and ///< the next. Used for smooth animation. 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. double percent_frame; bool first_iter; } 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*);