SafiTime

#include <safi/core/time.h>

SafiTime is a singleton component the engine updates once per frame, just before running the variable-rate pipeline. Systems read it via ecs_singleton_get(world, SafiTime).

Type

typedef struct SafiTime {
    float    delta;           /* seconds since last frame (wall-clock)     */
    float    elapsed;         /* seconds since app start  (wall-clock)     */
    uint64_t frame_count;     /* monotonic frame counter                   */
    float    fixed_delta;     /* seconds per fixed step   (default 1/60)   */
    float    fixed_elapsed;   /* accumulated fixed-step time               */
    float    fixed_overshoot; /* accumulator remainder in [0, fixed_delta) */
} SafiTime;

Two clocks live in a single struct:

  • Variable-ratedelta, elapsed, frame_count. Wall-clock. Read this from gameplay, camera, input-driven, and rendering systems.
  • Fixed-ratefixed_delta, fixed_elapsed, fixed_overshoot. Driven by the SafiFixedUpdate phase. Read this from physics and any deterministic simulation.

The engine ticks the fixed stage with a classic accumulator — each frame, delta is added to an accumulator, and SafiFixedUpdate runs as many times as needed to drain it in fixed_delta slices. fixed_overshoot is the unconsumed remainder, always in [0, fixed_delta), and is useful as an interpolation alpha when rendering between fixed steps.

Configuration

fixed_delta and the spiral-of-death cap are set in SafiAppDesc:

safi_app_init(&app, &(SafiAppDesc){
    .title           = "Demo",
    .fixed_dt        = 1.0f / 120.0f, // 120 Hz physics; default is 1/60
    .fixed_max_steps = 8,             // cap per frame; default is 4
});

Leaving them zero keeps the defaults (1/60 s, 4 max steps per frame).

Example — variable-rate system

static void spin_system(ecs_iter_t *it) {
    const SafiTime *t = ecs_singleton_get(it->world, SafiTime);
    SafiTransform  *x = ecs_field(it, SafiTransform, 0);
    for (int i = 0; i < it->count; ++i) {
        float angle = t->delta * 1.5f;
        versor q; glm_quatv(q, angle, (vec3){0, 1, 0});
        glm_quat_mul(q, x[i].rotation, x[i].rotation);
    }
}
Prefer

it->delta_time flecs already passes delta_time on the iterator. SafiTime is there for systems that need elapsed, frame_count, or the fixed-rate fields.

Example — fixed-rate system

static void physics_step(ecs_iter_t *it) {
    const SafiTime *t = ecs_singleton_get(it->world, SafiTime);
    // dt is stable: it is always t->fixed_delta here
    (void)t;
    // ...integrate bodies...
}

ecs_system(world, {
    .entity = ecs_entity(world, { .name = "physics_step",
                                  .add  = ecs_ids(ecs_dependson(SafiFixedUpdate)) }),
    .callback = physics_step,
});

See also