Execution Modes

ℹ️Quick Answer: Which Mode Should I Use?

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 SituationRecommended Setup
Learning HORUSScheduler::new() with defaults
PrototypingScheduler::new()
Need maximum speedScheduler::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

MetricValue
DeterministicYes
Multi-coreNo (single thread)
Best ForSafety-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

MetricValue
DeterministicNo
Multi-coreYes
Best ForMulti-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

FeatureSequentialParallel
DeterministicYesNo
Multi-coreNoYes
Reproducible for audit/replayYesNo

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