Time API

The horus:: time functions are THE standard way to get time, timestep, and random numbers in HORUS nodes — same pattern as hlog!() for logging. The scheduler sets the ambient context before each tick() call; these functions read from it.

Quick Reference

FunctionReturnsNormal ModeDeterministic Mode
horus::now()TimeStampWall clockVirtual SimClock
horus::since(ts)DurationElapsed since tsElapsed (virtual)
horus::dt()DurationReal elapsed since last tickFixed 1/rate
horus::elapsed()DurationWall time since scheduler startAccumulated virtual time
horus::tick()u64Current tick numberCurrent tick number
horus::rng(f)RSystem entropyTick-seeded (deterministic)
horus::budget_remaining()DurationTime left in budgetTime left (SimClock)

All functions are safe to call outside tick() — they return sensible fallback values.

horus::now() — Current Time

Returns a TimeStamp representing the current framework time.

fn tick(&mut self) {
    let start = horus::now();
    self.do_expensive_work();
    let elapsed = horus::since(start);
    hlog!(debug, "work took {:?}", elapsed);
}

TimeStamp Type

TimeStamp is an opaque wrapper with nanosecond precision:

let a = horus::now();
let b = horus::now();

// Subtraction produces Duration
let diff: Duration = b - a;

// Elapsed since a timestamp
let elapsed: Duration = a.elapsed();

// Comparison
assert!(b > a);

// Display
println!("{}", a);  // "1.500000s"

// Serialization
let nanos: u64 = a.as_nanos();
let restored = TimeStamp::from_nanos(nanos);

horus::dt() — Timestep

Returns the timestep for the current tick. Use this for physics integration:

fn tick(&mut self) {
    let dt = horus::dt();
    self.position += self.velocity * dt.as_secs_f64();
    self.velocity += self.acceleration * dt.as_secs_f64();
}

In normal mode, dt() returns the actual elapsed time since the last tick. In deterministic mode, it returns a fixed value of 1/rate (e.g., 10ms for 100Hz, 1ms for 1kHz).

horus::elapsed() — Time Since Start

Total time since the scheduler started:

fn tick(&mut self) {
    if horus::elapsed() > Duration::from_secs(30) {
        hlog!(info, "Running for 30 seconds, switching to cruise mode");
        self.mode = Mode::Cruise;
    }
}

horus::tick() — Tick Number

Zero-based tick counter:

fn tick(&mut self) {
    if horus::tick() % 100 == 0 {
        hlog!(info, "Checkpoint at tick {}", horus::tick());
    }
}

horus::rng() — Deterministic Random Numbers

Returns random values via a closure. In deterministic mode, the RNG is seeded from the tick number and node name — same sequence every run.

fn tick(&mut self) {
    // Random float in range
    let noise: f64 = horus::rng(|r| {
        use rand::Rng;
        r.gen_range(-0.01..0.01)
    });
    self.measurement += noise;

    // Random bool
    let should_explore: bool = horus::rng(|r| {
        use rand::Rng;
        r.gen_bool(0.1) // 10% chance
    });

    // Random integer
    let index: usize = horus::rng(|r| {
        use rand::Rng;
        r.gen_range(0..self.candidates.len())
    });
}

horus::budget_remaining() — Anytime Algorithms

Returns the time remaining in the current tick's budget. Use this for anytime algorithms that improve their result until time runs out:

fn tick(&mut self) {
    let mut plan = self.current_plan.clone();

    loop {
        plan = self.improve_plan(plan);

        if horus::budget_remaining() < 50_u64.us() {
            break; // stop before budget runs out
        }
    }

    self.path_topic.send(plan);
}

Returns Duration::MAX if no budget is configured or outside tick().

Fallback Behavior

All functions are safe to call outside tick():

FunctionOutside tick()
horus::now()Wall clock (fallback)
horus::dt()Duration::ZERO
horus::elapsed()Duration::ZERO
horus::tick()0
horus::rng(f)System entropy (non-deterministic)
horus::budget_remaining()Duration::MAX

Python API

The time functions are available as module-level functions in Python:

import horus

# In a node's tick():
now = horus.time_now()              # float (seconds)
dt = horus.time_dt()                # float (seconds)
elapsed = horus.time_elapsed()      # float (seconds)
tick = horus.time_tick()             # int
budget = horus.time_budget_remaining()  # float (seconds, inf if no budget)
rng_val = horus.time_rng_float()    # float in [0, 1)