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,
FrameLoopis 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_framereturns noResult— 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::droppedis the exact, non-optional record of by how much. alphais 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§
- Frame
Loop - The fixed-timestep schedule: a passive integer state machine.
- Frame
Plan - What one frame must do: the steps to execute, the steps the budget refused, and how far past the last step the renderer stands.
- Frame
Stats - The deterministic per-run tally: counts plus the schedule digest.
- Frame
Stats Json FrameStatsas one JSON object, for the machine-readable half of a tool’s output.- Frame
Timing - The measured per-run timing summary: never gated, only recorded.
- Frame
Timing Json FrameTimingas 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.
- State
Hash - 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.
- Step
Budget - 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.