Skip to main content

Crate renew_frame

Crate renew_frame 

Source
Expand description

Fixed-timestep frame scheduling: the deterministic accumulator, the step budget that bounds a stall, and the interpolation factor for rendering between steps.

The loop is a passive integer state machine. It owns no loop, drives no application, knows nothing of rendering, GPUs or windows, and never reads a clock — it cannot, having no dependency that offers one. Its whole job is one total function: FrameLoop::begin_frame answers given the schedule so far and this instant, how many fixed steps are due, how many did the budget refuse, and how far between steps is the renderer. The caller reads the one clock, executes the steps, and renders.

use renew_frame::{FrameLoop, FrameStats, StepBudget, Timestamp, Timestep};

let mut frame = FrameLoop::new(
    Timestep::HZ_60,
    StepBudget::DEFAULT,
    Timestamp::from_nanos(0),
);
let mut stats = FrameStats::new();

// A headless driver: no clock is read, so the whole run is a pure
// function of the timestamp sequence and is byte-comparable across
// runs, processes and machines.
for k in 1..=600u64 {
    let now = Timestamp::from_nanos(k.saturating_mul(16_666_667));
    let plan = frame.begin_frame(now);
    for step in plan.steps() {
        let _ = (step.tick, step.dt, step.sim_time); // advance the world here
    }
    // Render between steps with `renew_math::Alpha::new(...)`,
    // built from `plan.remainder()` and `plan.timestep()`.
    stats.absorb(&plan);
}

assert_eq!(stats.frames(), 600);
assert_eq!(stats.ticks(), 600);
assert_eq!(stats.steps_dropped(), 0);

§Contract

  • Deterministic. For a fixed build and platform, FrameLoop is a pure function of (timestep, budget, start, the sequence of timestamps passed to begin_frame). It reads no clock, allocates nothing, spawns nothing, and holds no iteration-order-dependent state. A headless run supplies that sequence synthetically and is reproducible; a realtime run supplies a measured one, which is a different input trace, not nondeterministic code.
  • Nothing can fail. Non-zero types, a saturating bank and a saturating delta between them leave no error to report, so begin_frame returns no Result — an uninhabitable error variant would be a lie about the API. Nothing here panics and nothing unwinds.
  • The plan must be executed. The one available contract violation — a caller that ignores its plan — is unobservable from inside, so it is contract text with #[must_use] as the mitigation rather than an assertion. A skipped plan silently desynchronizes the simulation from the tick counter.
  • Clamp and discard, always reported. Steps beyond the budget are discarded, never banked: keeping the surplus is the spiral of death. Simulation time therefore falls permanently behind the wall clock, and FramePlan::dropped is the exact, non-optional record of by how much.
  • alpha is never an input to simulation. It is a render-side hint in [0, 1), and it is deliberately excluded from the schedule digest.
  • Zero dependencies, and this crate never logs. A dropped step is reported through the returned plan; whether that is a log line is the caller’s decision.

§Extension points

None. There is no trait, no dyn, and no runtime polymorphism here — the manifest says so and CI holds the crate to it. The growth point is named rather than pre-built: a trait arrives when a second implementation exists.

Structs§

FrameLoop
The fixed-timestep schedule: a passive integer state machine.
FramePlan
What one frame must do: the steps to execute, the steps the budget refused, and how far past the last step the renderer stands.
FrameStats
The deterministic per-run tally: counts plus the schedule digest.
FrameStatsJson
FrameStats as one JSON object, for the machine-readable half of a tool’s output.
FrameTiming
The measured per-run timing summary: never gated, only recorded.
FrameTimingJson
FrameTiming as one JSON object. Everything here varies between runs and machines, which is exactly why it is a separate document section from the digest.
Nanos
A span of time in whole nanoseconds.
StateHash
An in-progress fingerprint. Absorption is by value and returns the new state, so the order of a digest is written out as an expression and can be read off the page.
Step
One simulation step.
StepBudget
The most simulation steps one frame may execute. Everything beyond it is discarded and reported, never banked.
Steps
The steps of one FramePlan, in tick order.
Timestamp
A point on a monotonic timeline, in nanoseconds since an origin the caller chooses. Only differences between timestamps mean anything; the origin itself never enters the schedule.
Timestep
The fixed simulation timestep, in nanoseconds. Non-zero by type, so no division in the schedule can trap and no constructor can fail: the type carries the guarantee, so nothing has to check for it.