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:
- Calls
init()on every node (once) - Calls
tick()on every node (repeatedly, in order) - 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
Lower order number = runs first. This is how you ensure data flows correctly:
| Order | Node | Why This Order |
|---|---|---|
| 0 | SensorNode | Read data first |
| 1 | ControlNode | Process the data |
| 2 | LoggerNode | Log 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:
- Stops calling
tick() - Calls
shutdown()on every node (in reverse order) - 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
- Nodes: The Building Blocks — what nodes are and how they work
- Topics: How Nodes Talk — pub/sub communication
- Quick Start — build a complete working example
- Scheduler — Full Reference — real-time config, watchdog, deadline monitoring