What is HORUS?

HORUS is a framework for building applications with multiple independent components that communicate through ultra-low-latency shared memory. Each component handles one responsibility, and they connect together to form complex systems.

The Core Idea

Instead of writing one monolithic program, you build:

  • Independent Nodes - Each component is self-contained
  • Connected by Topics - Nodes communicate through named channels
  • Run by a Scheduler - HORUS manages execution order

Example: A robot control system might have:

  • SensorNode (reads camera)
  • VisionNode (detects objects)
  • ControlNode (moves motors)
  • SafetyNode (prevents collisions)

Each node runs independently, sharing data through topics like "camera.image", "detected.objects", "motor.commands".

Key Features

1. Low-Latency Communication

HORUS achieves 20 ns same-thread and 63 ns cross-thread for an uncontended 1:1 topic, and 151 ns one-way cross-process — roughly 33x lower than ROS 2's published REP 2014 reference for default DDS (~5µs). HORUS's figures are measured here by all_paths_latency; the ROS 2 figure is quoted, not measured. The system automatically selects the optimal shared-memory backend based on your topology (how many publishers and subscribers a topic has, and whether the message type is plain data). No configuration needed.

This performance enables tight control loops and high-frequency data processing without introducing significant delay.

2. Clean APIs

HORUS provides idiomatic Rust APIs:

use horus::prelude::*;

// Create a publisher
let publisher: Topic<f32> = Topic::new("temperature")?;

// Create a subscriber
let subscriber: Topic<f32> = Topic::new("temperature")?;

// Send data
publisher.send(25.0);

// Receive data
if let Some(temp) = subscriber.recv() {
    println!("Temperature: {:.1}C", temp);
}

With the node macro:

node! {
    SensorNode {
        pub {
            temperature: f32 -> "temperature",
        }
        tick {
            self.temperature.send(25.0);
        }
    }
}

3. Built-in Developer Tools

# Create a new project
horus new my_project

# Run your application
horus run

# Monitor in real-time (ships as a plugin — install once)
horus install horus-monitor
export PATH="$HOME/.config/horus/bin:$PATH"   # where the plugin lands;
                                              # horus monitor does not search it
horus monitor

horus monitor is a built-in command, but the viewer it launches ships as a separate plugin — the first run tells you to install it with horus install horus-monitor.

The monitor displays:

  • Running nodes
  • Message flow between components
  • Performance metrics (latency, throughput)
  • Debugging information

4. Multi-Language Support

Python:

import horus

def sensor_tick(node):
    node.send("temperature", 25.0)

sensor = horus.Node(
    name="sensor",
    pubs=["temperature"],
    tick=sensor_tick,
    rate=10
)

horus.run(sensor)

Rust: Full framework capabilities for performance-critical code.

C++: C++17 bindings (horus_cpp) — Node, Scheduler, and Publisher/Subscriber over the same shared-memory topics. Scaffold with horus new --cpp my_project. See the C++ API Reference.

Rust, Python, and C++ nodes can communicate in the same application.

Core Concepts

Nodes

A Node is a component that performs a specific task. Examples:

  • Read data from a sensor
  • Process information
  • Control a motor
  • Display data on screen
  • Monitor system health

Node lifecycle:

  1. init() - Start up (optional, run once)
  2. tick() - Do work (runs repeatedly)
  3. shutdown() - Clean up (optional, run once)
impl Node for MySensor {
    fn name(&self) -> &str { "MySensor" }

    fn init(&mut self) -> Result<()> {
        hlog!(info, "Sensor starting up");
        Ok(())
    }

    fn tick(&mut self) {
        // Read sensor, send data - runs repeatedly
    }

    fn shutdown(&mut self) -> Result<()> {
        hlog!(info, "Sensor shutting down");
        Ok(())
    }
}

Topics

A Topic is a named channel for sending messages. Multiple publishers can send to a topic, and multiple subscribers can receive from it.

Topic naming conventions:

  • Use descriptive names: "temperature", "camera.image", "motor.speed"
  • Use dots for hierarchy: "sensors.imu.accel", "actuators.left_wheel"
// Node A publishes temperature
let pub_a: Topic<f32> = Topic::new("temperature")?;
pub_a.send(25.0);

// Node B also publishes temperature
let pub_b: Topic<f32> = Topic::new("temperature")?;
pub_b.send(30.0);

// Node C receives from both
let sub: Topic<f32> = Topic::new("temperature")?;
if let Some(temp) = sub.recv() {
    println!("Got: {}", temp);
}

Type safety: The type parameter (<f32>) ties the topic to one message type. Within a single program the compiler enforces it. Across nodes, processes or languages there is no compile step to catch a disagreement, so it is enforced when the topic is opened: the second process to open "temp" with a different type gets Failed to create topic 'temp': type mismatch.

Note: HORUS supports both typed messages (Pose2D, CmdVel, etc.) and generic messages (dicts/JSON in Python). Typed messages provide better performance and type safety. See Message Types for details.

Scheduler

The Scheduler runs nodes in priority order:

let mut scheduler = Scheduler::new();

// Add nodes with order (lower number = runs first)
scheduler.add(SensorNode::new()?).order(0).done();   // Runs first
scheduler.add(ProcessNode::new()?).order(1).done();  // Runs second
scheduler.add(DisplayNode::new()?).order(2).done();  // Runs third

// Run (Ctrl+C to stop)
scheduler.run()?;

Execution order: .order() is a fallback, not an absolute rule. As soon as any node in the program has pub/sub topic metadata, the scheduler builds a dependency graph from it and dispatches from the graph — a subscriber runs after its publisher even when it has the lower .order(). When no node registered a topic during init() — the usual case — .order() tiers decide the sequence instead: all order 0 nodes, then all order 1, then all order 2, repeat. See Execution Classes.

Use order to control data flow:

  • Sensors should run before processors (lower order number)
  • Processors should run before actuators
  • Safety checks should run first (order 0)

When to Use HORUS

Suitable Applications

Multi-component applications - Isolated components that communicate:

  • Robot control systems
  • Real-time data processing pipelines
  • Multi-sensor fusion systems
  • Parallel processing workflows

Real-time systems - When latency matters:

  • Control loops (motor control, flight control)
  • High-frequency data processing
  • Live audio/video processing

Single-machine distributed systems - Multiple processes on one machine:

  • Embedded Linux systems (Raspberry Pi, Jetson)
  • Edge computing devices
  • Multi-core applications

Multi-machine on a LAN - The opt-in net feature mirrors topics across machines over UDP with the same Topic<T> API (see Network Backends):

  • Multi-robot fleets on one network
  • Offloading compute to a workstation on the same subnet

Hardware integration - Combining multiple devices/languages:

  • Mix Rust (performance) + Python (ease of use)
  • Integrate Python prototypes with production Rust

Less Suitable Applications

Simple single-script programs - If your program fits in 100 lines, HORUS adds unnecessary complexity.

Wide-area / internet-scale distribution - HORUS LAN replication discovers peers with UDP multicast on a single subnet; it is not a WAN transport. For communication across the internet, use gRPC, HTTP, or message queues.

CRUD web applications - Use web frameworks (Axum, Actix, Django, Flask) instead.

Bare-metal embedded systems - HORUS requires an operating system with shared memory support. For microcontrollers, use RTIC or Embassy.

Comparison with Other Frameworks

vs Monolithic Programs

Traditional approach:

fn main() {
    loop {
        let temp = read_sensor();
        let filtered = process(temp);
        display(filtered);
    }
}

Issues:

  • Difficult to test individual parts
  • Changes can break everything
  • Components cannot be reused
  • No parallelization

HORUS approach:

// SensorNode - reusable, testable, independent
struct SensorNode { data_pub: Topic<f32> }

// ProcessNode - can swap implementation
struct ProcessNode { data_sub: Topic<f32>, processed_pub: Topic<f32> }

// DisplayNode - can replace with LogNode, etc.
struct DisplayNode { data_sub: Topic<f32> }

Benefits:

  • Test each node independently
  • Change one node without affecting others
  • Reuse nodes across projects
  • Nodes can run in parallel

vs ROS (Robot Operating System)

AspectHORUSROS
Typical latency~0.151 µs (one-way cross-process)~5 µs (ROS 2 default DDS, REP 2014)
ConfigurationCode-basedXML files
Target use caseSingle-machine performance + LAN replicationMulti-machine robotics
EcosystemGrowingLarge

Use ROS when: You need extensive robotics libraries, or communication beyond a local network.

Use HORUS when: You need high performance on a single machine, or across a few machines on the same local network.

vs Message Queues (RabbitMQ, Kafka)

AspectHORUSMessage Queues
Latency~0.151 µs (one-way cross-process)1-10 ms
ScopeSingle machine + LANMulti-machine
ConfigurationMinimalComplex
PersistenceNoYes

Use message queues when: You need WAN/internet-scale communication, persistence, or reliability guarantees.

Use HORUS when: You need high speed on a single machine or local network.

Architecture Overview

Loading diagram...
Everything a node sends or logs lands in shared memory; the monitor is a separate process that reads it

Data flow:

  1. Nodes communicate via Topics
  2. Topics write/read from shared memory
  3. Scheduler orchestrates node execution
  4. Monitor displays system status in real-time

Technical Details

Performance Characteristics

IPC Latency: every topic is cross-process shared memory, and HORUS picks the backend automatically from the topic's publisher/subscriber counts and whether the message type is plain data.

  • Uncontended 1:1 — 20 ns same-thread, 63 ns cross-thread, 151 ns cross-process
  • One publisher to many subscribers — 74 ns cross-thread, 191 ns cross-process
  • Contended multi-producer — 226 ns same-process, 279 ns cross-process
  • All send-only (one direction), on an Intel i7-10750H; the raw shared-memory floor on the same machine is 79 ns, so cross-process 1:1 pays 72 ns of framework overhead — see Benchmarks

Throughput:

  • Small messages (<1KB): 2M+ msgs/sec
  • Large messages (1MB): Limited by memory bandwidth

Memory Usage:

  • Framework overhead: ~2MB
  • Per topic: Auto-sized based on message type (~5KB to ~8MB)
  • Per node: Depends on implementation

Built in Rust

HORUS leverages Rust for:

Safety - Compile-time guarantees:

  • No null pointer dereferences
  • No data races
  • No use-after-free bugs

Performance - Zero-cost abstractions:

  • No garbage collection pauses
  • Predictable memory layout
  • LLVM optimizations

Concurrency - Fearless concurrency:

  • Send/Sync traits prevent data races
  • Ownership prevents sharing mutable state

Learning Path

Start here:

  1. Installation - Get HORUS installed
  2. Quick Start - Build your first app
  3. Basic Examples - Working examples

Core concepts: 4. Nodes - Build components 5. Topic - Pub/sub communication 6. Scheduler - Run your application

Practical features: 7. node! Macro - Reduce boilerplate 8. Monitor - Monitor and debug

Advanced: 9. Multi-Language - Python and C++ integration 10. Performance - Optimization 11. Examples - Real projects

Next Steps

  1. Install HORUS - Get up and running
  2. Quick Start Tutorial - Build your first application
  3. See Examples - Learn from real projects

For command reference, see CLI Reference. For architecture details, see Architecture.