Scheduler: Running Your Nodes

For the full reference with real-time configuration, watchdog, deadline monitoring, and composable builders, see Scheduler — Full Reference.

What is the Scheduler?

The scheduler is the engine that runs your nodes. It:

  1. Calls init() on every node (once)
  2. Calls tick() on every node (repeatedly, in order)
  3. Calls shutdown() on every node when you press Ctrl+C

You don't write loops or manage threads. You add nodes, set their order, and let the scheduler handle the rest.

Basic Usage

use horus::prelude::*;

fn main() -> Result<()> {
    let mut scheduler = Scheduler::new();

    // Add nodes with execution order
    scheduler.add(SensorNode::new()?)
        .order(0)       // Runs first
        .build()?;

    scheduler.add(ControlNode::new()?)
        .order(1)       // Runs second
        .build()?;

    scheduler.add(LoggerNode::new()?)
        .order(2)       // Runs third
        .build()?;

    // Run until Ctrl+C
    scheduler.run()?;
    Ok(())
}

In Python:

import horus

sensor = horus.Node(name="Sensor", tick=sensor_tick, order=0)
control = horus.Node(name="Control", tick=control_tick, order=1)
logger = horus.Node(name="Logger", tick=logger_tick, order=2)
horus.run(sensor, control, logger)

Execution Order

Loading diagram...
Nodes execute in order every tick, then the cycle repeats

Lower order number = runs first. This is how you ensure data flows correctly:

OrderNodeWhy This Order
0SensorNodeRead data first
1ControlNodeProcess the data
2LoggerNodeLog the results

If two nodes have the same order, they run in the order they were added.

Setting Tick Rate

The default tick rate is 100 Hz. You can change it:

scheduler.tick_rate(100.hz());  // 100 ticks per second

Or set per-node rates:

scheduler.add(FastSensor::new()?)
    .order(0)
    .rate(1000.hz())  // This node ticks at 1kHz
    .build()?;

scheduler.add(SlowLogger::new()?)
    .order(1)
    .rate(10.hz())    // This node ticks at 10Hz
    .build()?;

The .hz() syntax comes from HORUS's DurationExt trait — 1000.hz() means 1000 times per second.

Graceful Shutdown

When you press Ctrl+C, the scheduler:

  1. Stops calling tick()
  2. Calls shutdown() on every node (in reverse order)
  3. Exits cleanly

This is critical for robots — you want motors to stop and connections to close properly, even if the program is interrupted.

impl Node for MotorController {
    fn shutdown(&mut self) -> Result<()> {
        self.motor.set_velocity(0.0);  // Stop the motor!
        println!("Motor safely stopped");
        Ok(())
    }
}

A Complete Example

Putting it all together — a sensor that publishes data and a monitor that displays it:

use horus::prelude::*;

struct Sensor {
    publisher: Topic<f32>,
    value: f32,
}

impl Node for Sensor {
    fn name(&self) -> &str { "Sensor" }
    fn tick(&mut self) {
        self.value += 0.1;
        self.publisher.send(self.value);
    }
}

struct Monitor {
    subscriber: Topic<f32>,
}

impl Node for Monitor {
    fn name(&self) -> &str { "Monitor" }
    fn tick(&mut self) {
        if let Some(v) = self.subscriber.recv() {
            println!("Value: {:.1}", v);
        }
    }
}

fn main() -> Result<()> {
    let mut scheduler = Scheduler::new()
        .tick_rate(1.hz());  // 1 Hz for readability
    scheduler.add(Sensor { publisher: Topic::new("data")?, value: 0.0 })
        .order(0).build()?;
    scheduler.add(Monitor { subscriber: Topic::new("data")? })
        .order(1).build()?;
    scheduler.run()
}

Key Takeaways

  • The scheduler runs your nodes — you don't write loops
  • .order(n) controls execution sequence (lower = first)
  • .rate(n.hz()) sets tick frequency
  • Ctrl+C triggers graceful shutdown on all nodes
  • Shutdown runs in reverse order — dependent nodes stop first

Next Steps