C++ API Reference

All C++ types live in the horus namespace. Message types are in horus::msg.

horus::Scheduler

The central orchestrator — creates, configures, and runs nodes.

#include <horus/scheduler.hpp>

Constructor

MethodDescription
Scheduler()Create with default configuration

Builder Methods (chainable)

MethodDescription
tick_rate(Frequency)Set main loop rate (100_hz)
name(string_view)Set scheduler name
prefer_rt()Prefer RT scheduling (degrade gracefully)
require_rt()Require RT scheduling (fail if unavailable)
deterministic(bool)Enable deterministic mode (SimClock + seeded RNG)
verbose(bool)Enable verbose logging
watchdog(Duration)Set global watchdog timeout
blackbox(size_t)Set flight recorder size in MB
network(bool)Enable/disable LAN replication (on by default)

Topic Methods

MethodDescription
advertise<T>(name)Create a Publisher<T> for the named topic
subscribe<T>(name)Create a Subscriber<T> for the named topic

Node Methods

MethodDescription
add(name)Add a node, returns NodeBuilder

Lifecycle

MethodDescription
spin()Run until stopped (blocks)
tick_once()Execute one tick of all nodes
stop()Stop the scheduler (thread-safe)
is_running()Check if still running
get_name()Get scheduler name
status()Human-readable status string
has_full_rt()Check RT capabilities
node_list()Get registered node names

horus::Node (struct-based)

Base class for nodes with built-in pub/sub and lifecycle hooks. Like Rust's impl Node for T.

#include <horus/node.hpp>

class Controller : public horus::Node {
public:
    Controller() : Node("controller") {
        scan_ = subscribe<msg::CmdVel>("lidar.scan");
        cmd_  = advertise<msg::CmdVel>("motor.cmd");
    }

    void tick() override {
        auto scan = scan_->recv();
        if (!scan) return;
        msg::CmdVel cmd{}; cmd.linear = 0.3f;
        cmd_->send(cmd);
    }

    void init() override { /* called once before first tick */ }
    void enter_safe_state() override { /* stop motors */ }

private:
    Subscriber<msg::CmdVel>* scan_;
    Publisher<msg::CmdVel>*  cmd_;
};

// Register with scheduler:
Controller ctrl;
sched.add(ctrl).rate(100_hz).order(10).build();
MethodDescription
tick()Called every scheduler tick (pure virtual)
init()Called once before first tick
enter_safe_state()Called on safety events
on_shutdown()Called on scheduler shutdown
advertise<T>(topic)Create publisher, returns Publisher<T>*
subscribe<T>(topic)Create subscriber, returns Subscriber<T>*
name()Node name
publishers()List of published topic names
subscriptions()List of subscribed topic names

horus::LambdaNode (Python-style)

Declarative node with builder pattern. Like Python's horus.Node().

#include <horus/node.hpp>

auto node = horus::LambdaNode("controller")
    .sub<msg::CmdVel>("lidar.scan")
    .pub<msg::CmdVel>("motor.cmd")
    .on_tick([](horus::LambdaNode& self) {
        auto scan = self.recv<msg::CmdVel>("lidar.scan");
        if (!scan) return;
        self.send<msg::CmdVel>("motor.cmd", msg::CmdVel{0, 0.3f, 0.0f});
    });

sched.add(node).order(10).build();
MethodDescription
pub<T>(topic)Declare publisher (builder, chainable)
sub<T>(topic)Declare subscriber (builder, chainable)
on_tick(fn)Set tick callback: void(LambdaNode&)
on_init(fn)Set init callback: void(LambdaNode&)
send<T>(topic, msg)Send message to topic (call from tick)
recv<T>(topic)Receive from topic (call from tick)
has_msg<T>(topic)Check if message available

horus::NodeBuilder

Configures scheduling for a node. Returned by Scheduler::add().

Builder Methods (chainable)

MethodDescriptionDefault
rate(Frequency)Tick rate. Auto-derives budget (80%) and deadline (95%)BestEffort
budget(Duration)Max tick time. Overrides auto-derived80% of period
deadline(Duration)Absolute latest a tick can finish95% of period
on_miss(Miss)Policy on deadline missMiss::Warn
compute()Parallel thread pool execution
async_io()Tokio blocking pool
on(topic)Event-triggered by topic update
order(uint32_t)Execution order (lower = earlier). 0-9: critical, 10-49: high, 50-99: normal, 100+: low0
pin_core(size_t)Pin to CPU coreAuto
priority(int32_t)SCHED_FIFO priority (1-99)Auto
watchdog(Duration)Per-node watchdogGlobal
tick(function)Set tick callback (lambda or Node/LambdaNode)Required

Finalize

MethodDescription
build()Register the node with the scheduler

Three Ways to Add Nodes

// 1. Lambda (quick)
sched.add("ctrl").tick([&]{ /* ... */ }).build();

// 2. Struct-based (stateful, lifecycle hooks)
class MyNode : public horus::Node { /* ... */ };
MyNode node;
sched.add(node).rate(100_hz).build();

// 3. LambdaNode (Python-style, declarative)
auto node = horus::LambdaNode("ctrl").sub<T>("in").pub<T>("out").on_tick(fn);
sched.add(node).build();

horus::Publisher<T>

Publishes messages to a topic. Create via Node::advertise<T>() or directly.

MethodDescription
loan()Get a writable LoanedSample<T> (zero-copy from SHM)
publish(LoanedSample<T>&&)Publish the sample (move-only)
send(const T&)Send by copy (simpler, slight overhead for large types)
name()Topic name

horus::Subscriber<T>

Receives messages from a topic. Create via Scheduler::subscribe<T>().

MethodDescription
recv()Receive next message → std::optional<BorrowedSample<T>>
has_msg()Check if message available (non-consuming)
name()Topic name

horus::LoanedSample<T>

Writable handle to shared memory. Move-only, RAII release.

MethodDescription
operator->()Direct SHM pointer (0ns)
operator*()Dereference
get()Raw T* pointer

Move-only: copy constructor and copy assignment are deleted.


horus::BorrowedSample<T>

Read-only received message. Move-only, RAII release.

MethodDescription
operator->()Read pointer (const)
operator*()Dereference (const)
get()Raw const T* pointer

horus::Frequency

Represents a frequency in Hertz. Created via _hz literal.

MethodDescription
value()Get Hz as double
period()Get period as std::chrono::microseconds
budget_default()80% of period
deadline_default()95% of period

horus::Miss

Deadline miss policy enum.

ValueDescription
Miss::WarnLog warning, continue
Miss::SkipSkip this tick
Miss::SafeModeEnter safe state
Miss::StopStop scheduler

Duration Literals

using namespace horus::literals;
LiteralTypeExample
_hzFrequency100_hz
_msmicroseconds5_ms (5000us)
_usmicroseconds200_us
_nsnanoseconds500_ns
_smicroseconds3_s (3000000us)

horus::TensorPool

SHM-backed memory pool for zero-copy large data (images, point clouds, tensors).

#include <horus/pool.hpp>
MethodDescription
TensorPool(pool_id, size, max_slots)Create pool with given capacity
stats()Get Stats{allocated, used_bytes, free_bytes}

horus::Tensor

Pool-allocated N-dimensional tensor. Move-only, RAII.

MethodDescription
Tensor(pool, shape, ndim, dtype)Allocate from pool
data()Raw uint8_t* to SHM memory
nbytes()Size in bytes
release()Return to pool early

horus::Dtype: F32, F64, U8, I32


horus::Image

Pool-backed image. Move-only, RAII.

auto pool = horus::TensorPool(1, 16*1024*1024, 64);
auto img = horus::Image(pool, 640, 480, horus::Encoding::Rgb8);
MethodDescription
Image(pool, w, h, encoding)Allocate from pool
width() / height()Dimensions
data_size()Total bytes (w * h * channels)

horus::Encoding: Rgb8, Rgba8, Gray8, Bgr8


horus::PointCloud

Pool-backed point cloud. Move-only, RAII.

auto pc = horus::PointCloud(pool, 1000, 3); // 1000 XYZ points
MethodDescription
PointCloud(pool, n, fields)Allocate from pool. fields: 3=XYZ, 4=XYZI, 6=XYZRGB
num_points()Number of points
fields_per_point()Fields per point

horus::Params

Dynamic runtime parameters (key-value store).

#include <horus/params.hpp>

auto params = horus::Params();
params.set("max_speed", 1.5);
double speed = params.get<double>("max_speed", 0.0);
MethodDescription
set(key, value)Set parameter (overloaded for double, int64_t, bool, string)
get<T>(key, default)Get with default. T: double, int64_t, int, bool, std::string
get_f64(key) / get_i64(key) / get_bool(key) / get_string(key)Typed getters returning std::optional
has(key)Check if parameter exists

horus::ServiceClient

Request/response RPC client.

#include <horus/service.hpp>

auto client = horus::ServiceClient("add_two_ints");
auto resp = client.call(R"({"a":3,"b":4})", 1000ms);
MethodDescription
ServiceClient(name)Create client for named service
call(json, timeout)Call with JSON request, returns std::optional<std::string>

horus::ServiceServer

Request/response RPC server.

MethodDescription
ServiceServer(name)Create server for named service
set_handler(fn)Set handler: bool(const uint8_t* req, size_t len, uint8_t* res, size_t* res_len)

horus::ActionClient

Long-running task client with progress feedback.

#include <horus/action.hpp>

auto client = horus::ActionClient("navigate");
auto goal = client.send_goal(R"({"x":5.0})");
while (goal.is_active()) { /* poll */ }
MethodDescription
ActionClient(name)Create client for named action
send_goal(json)Send goal, returns GoalHandle

horus::GoalHandle

Tracks a sent goal. Move-only.

MethodDescription
status()GoalStatus enum
id()Goal ID
is_active()Still running?
cancel()Request cancellation

horus::GoalStatus: Pending, Active, Succeeded, Aborted, Canceled, Rejected


horus::ActionServer

Long-running task server.

MethodDescription
ActionServer(name)Create server for named action
set_accept_handler(fn)uint8_t(const uint8_t* goal, size_t len) → 0=accept, 1=reject
set_execute_handler(fn)void(uint64_t goal_id, const uint8_t* goal, size_t len)
is_ready()Both handlers set?

horus::TransformFrame

Coordinate frame system (TF tree).

#include <horus/transform.hpp>

auto tf = horus::TransformFrame();
tf.register_frame("world");
tf.register_frame("base_link", "world");
tf.update("base_link", {1,2,0}, {0,0,0,1}, timestamp);
auto t = tf.lookup("base_link", "world");
MethodDescription
TransformFrame()Create with default capacity (256 frames)
TransformFrame(max)Create with custom max frames
register_frame(name, parent)Register frame. parent=nullptr for root
update(frame, pos, rot, ts)Update transform. rot=[qx,qy,qz,qw]
lookup(source, target)Returns std::optional<Transform>
can_transform(source, target)Check if path exists

Message Types

All in horus::msg:: namespace. Include via <horus/messages.hpp> or individual headers.

See Getting Started: C++ for the full type table.


Performance

FFI boundary: 15-17ns. Scheduler tick (1 node): 250ns median. Throughput: 2.84M ticks/sec. Zero ASAN errors.

See Benchmarks: C++ Binding Performance for full results with percentiles and scalability analysis.