Clock & Time API
HORUS provides a unified time system that transparently switches between real time, simulation time, and replay time. Your node code is identical in all three modes — the scheduler selects the clock backend.
Clock Backends
| Backend | Activated By | Behavior |
|---|---|---|
| WallClock | Default (normal mode) | Passthrough to Instant::now(). Zero overhead. |
| SimClock | .deterministic(true) | Virtual time, advances by exact dt per tick. |
| ReplayClock | Scheduler::replay_from(path) | Steps through the recording's tick timeline at its average tick period. |
use horus::prelude::*;
use std::path::PathBuf;
// Normal mode — uses WallClock (default)
let mut scheduler = Scheduler::new();
// Deterministic mode — uses SimClock
let mut scheduler = Scheduler::new().deterministic(true);
// Replay mode — uses ReplayClock
let mut scheduler = Scheduler::replay_from(PathBuf::from("recording.horus"))
.expect("failed to load scheduler recording");
Accessing Time in Nodes
Inside your tick() function, use the horus::time:: functions. These read from whichever clock backend is active:
use horus::prelude::*;
struct MyNode { acceleration: f64 }
impl Node for MyNode {
fn name(&self) -> &str { "my_node" }
fn tick(&mut self) {
// Current time (TimeStamp)
let now = horus::time::now();
// Timestep for this tick — nominal 1/rate, not measured elapsed (Duration)
let dt = horus::time::dt();
// Total elapsed since scheduler start (Duration)
let elapsed = horus::time::elapsed();
// Use dt for physics integration
let velocity = self.acceleration * dt.as_secs_f64();
}
}
Deterministic Mode
When .deterministic(true) is set, the scheduler uses SimClock:
- Time advances by one tick period per dependency-graph step, so it equals
1/tick_rateper tick only when every node collapses into a single step. Nodes spread across several.order()tiers advance virtual time once per tier - Two runs with the same inputs produce identical clock values
- No dependency on CPU speed or system load
- Essential for: reproducible tests, sim-to-real parity, CI determinism
use horus::prelude::*;
let mut scheduler = Scheduler::new()
.tick_rate(100.hz()) // 100 Hz → dt = 10ms
.deterministic(true); // SimClock: virtual time
scheduler.add(PhysicsNode::new()).build();
// After 100 ticks: elapsed = exactly 1.000000000 second
// Regardless of how long the ticks actually took on CPU
scheduler.run();
Replay Mode
A whole recording is replayed with Scheduler::replay_from(path); a single recorded node is added to a live scheduler with .add_replay(path, priority). Both take a PathBuf and return a HorusResult.
Scheduler::replay_fromsteps time through an evenly-spaced tick clock derived from the.horusrecording's average tick period — its total wall-clock span divided by its tick count- Nodes see the original average cadence; per-tick wall-clock jitter is not reproduced — replay preserves recorded inputs and execution order, not timing
- Can mix live nodes with replay nodes in the same scheduler (
.add_replayalone leaves the scheduler onWallClock)
use horus::prelude::*;
use std::path::PathBuf;
// Full replay — all nodes from recording
let mut scheduler = Scheduler::replay_from(PathBuf::from("session.horus"))
.expect("failed to load scheduler recording");
// Mixed — 1 replay node + 1 live node
let mut scheduler = Scheduler::new();
// replay sensor data
scheduler.add_replay(PathBuf::from("sensor_recording.horus"), 0)
.expect("failed to load recording");
// live planner
scheduler.add(PlannerNode::new()).build();
scheduler.run();
Measuring Elapsed Time
horus::time::now() returns a TimeStamp. Subtract two timestamps to get elapsed duration:
let t1 = horus::time::now();
// ... do work ...
let t2 = horus::time::now();
let work_time: Duration = t2 - t1;
// Or, if you only need the duration up to now:
let work_time: Duration = horus::time::since(t1); // or t1.elapsed()
Clock Trait (Internal)
The Clock trait is internal (#[doc(hidden)]) — users should use horus::time::now(), horus::time::dt(), horus::time::elapsed() instead. It's documented here for framework contributors:
| Method | Description |
|---|---|
now() | Current ClockInstant |
advance(dt) | Advance by one tick's duration (no-op for WallClock) |
reset() | Reset to initial state (used by replay seek(0)) |
elapsed() | Total time since clock construction |
See Also
- Deterministic Execution — Reproducible simulations
- Record & Replay — Recording and playback
- Real-Time Nodes — Budgets, deadlines, and scheduling