Execution Modes
Use the default. Scheduler::new() runs your nodes in parallel, driven by their dependencies, which works for most robots — add .deterministic(true) when you need a reproducible execution order.
| Your Situation | Recommended Setup |
|---|---|
| Learning HORUS | Scheduler::new() with defaults |
| Prototyping | Scheduler::new() |
| Need maximum speed | Scheduler::new() with .compute() execution class on heavy nodes |
| Safety-critical (medical, aerospace) | Scheduler::new().deterministic(true).tick_rate(1000_u64.hz()) with .rate() on each node (promotes to RT) |
Don't overthink this. Start with Scheduler::new() and configure per-node execution classes as needed.
HORUS supports two execution modes to optimize for different robotics scenarios. You pick the mode on the scheduler (Scheduler::new() is parallel; .deterministic(true) is sequential) and tune individual nodes with per-node execution classes (.compute(), .on(topic), .async_io(), plus .rate()/.budget()/.deadline(), which promote a node to the RT class) — the scheduler handles the rest.
Sequential Mode
The deterministic choice for safety-critical systems.
Sequential mode is opt-in via Scheduler::new().deterministic(true). With it, all nodes run one-by-one on the main thread in dependency/priority order, producing the same execution order every tick.
How It Works
- Nodes execute one-by-one in priority order
- Same execution order every tick
- Identical execution order across runs, on a virtual clock (
SimClock)
Characteristics
| Metric | Value |
|---|---|
| Deterministic | Yes |
| Multi-core | No (single thread) |
| Best For | Safety-critical control, replayable tests |
When to Use
- Medical/surgical robots and other safety-critical control loops
- Systems needing reproducible behavior
- Debugging complex timing issues
- Formal verification scenarios
Enable sequential mode with Scheduler::new().deterministic(true). Add real-time constraints per node with .rate() (or .budget()/.deadline()), which promotes the node to the RT execution class automatically.
use horus::prelude::*;
// Safety-critical robot controller — 1 kHz tick, RT execution class
let mut scheduler = Scheduler::new().deterministic(true).tick_rate(1000_u64.hz());
scheduler.add(safety_monitor).order(0).rate(1000_u64.hz()).done();
scheduler.add(controller).order(1).rate(1000_u64.hz()).done();
scheduler.run()?;
Parallel Mode
Multi-core execution for maximum throughput.
Parallel mode is what Scheduler::new() gives you by default: independent nodes are scheduled on different CPU cores and execute concurrently, while nodes that depend on each other still run in order.
How It Works
- Schedules independent nodes on different cores
- Respects dependency order (topic graph, or
.order()tiers as fallback) - Uses thread pool for concurrent execution
Characteristics
| Metric | Value |
|---|---|
| Deterministic | No |
| Multi-core | Yes |
| Best For | Multi-sensor fusion, compute-heavy pipelines |
When to Use
- Multi-sensor robots
- Compute-heavy pipelines
- Systems with many independent nodes
- When you have multiple CPU cores available
Parallelism is decided per node, not by .order() alone. .compute() nodes always run concurrently in the compute thread pool regardless of order. Default (BestEffort) nodes are parallelised by the dependency graph: it is built from pub/sub topic metadata when any exists, and falls back to .order() tiers when none does — same order value means one parallel step, different order values run sequentially:
use horus::prelude::*;
let mut scheduler = Scheduler::new();
// These sensor nodes can run in parallel (same order number)
scheduler.add(lidar_node).order(0).done();
scheduler.add(camera_node).order(0).done();
scheduler.add(imu_node).order(0).done();
// Fusion runs after all sensors (higher order number = runs later)
scheduler.add(fusion_node).order(1).done();
scheduler.run()?;
Mode Comparison
| Feature | Sequential | Parallel |
|---|---|---|
| Deterministic | Yes | No |
| Multi-core | No | Yes |
| Reproducible for audit/replay | Yes | No |
Examples
Safety-Critical System
use horus::prelude::*;
// Surgical robot — deterministic, 1 kHz tick with RT execution class
let mut scheduler = Scheduler::new().deterministic(true).tick_rate(1000_u64.hz());
scheduler.add(safety_monitor).order(0).rate(1000_u64.hz()).done();
scheduler.add(force_feedback).order(1).rate(1000_u64.hz()).done();
scheduler.add(motion_controller).order(1).rate(1000_u64.hz()).done();
scheduler.run()?;
Racing Robot
use horus::prelude::*;
// Competition robot — maximum speed
let mut scheduler = Scheduler::new();
// .compute() nodes run concurrently in the compute pool — order is not what parallelises them
scheduler.add(vision_pipeline).compute().done();
scheduler.add(path_planner).compute().done();
// The control loop gets .rate(), which promotes it to the RT execution class
scheduler.add(motor_controller).rate(1000_u64.hz()).done();
scheduler.run()?;
Multi-Sensor Robot
use horus::prelude::*;
// Research robot with many sensors — parallel sensor processing
let mut scheduler = Scheduler::new();
// These run in parallel (same order number)
scheduler.add(lidar).order(0).done();
scheduler.add(camera).order(0).done();
scheduler.add(radar).order(0).done();
scheduler.add(imu).order(0).done();
// Fusion runs after all sensors (higher order number = runs later)
scheduler.add(fusion).order(1).done();
scheduler.run()?;
Next Steps
- Learn about Deterministic Execution for reproducible runs
- Explore Scheduler Configuration for all configuration options