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
| Method | Description |
|---|---|
Scheduler() | Create with default configuration |
Builder Methods (chainable)
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
advertise<T>(name) | Create a Publisher<T> for the named topic |
subscribe<T>(name) | Create a Subscriber<T> for the named topic |
Node Methods
| Method | Description |
|---|---|
add(name) | Add a node, returns NodeBuilder |
Lifecycle
| Method | Description |
|---|---|
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();
| Method | Description |
|---|---|
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();
| Method | Description |
|---|---|
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)
| Method | Description | Default |
|---|---|---|
rate(Frequency) | Tick rate. Auto-derives budget (80%) and deadline (95%) | BestEffort |
budget(Duration) | Max tick time. Overrides auto-derived | 80% of period |
deadline(Duration) | Absolute latest a tick can finish | 95% of period |
on_miss(Miss) | Policy on deadline miss | Miss::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+: low | 0 |
pin_core(size_t) | Pin to CPU core | Auto |
priority(int32_t) | SCHED_FIFO priority (1-99) | Auto |
watchdog(Duration) | Per-node watchdog | Global |
tick(function) | Set tick callback (lambda or Node/LambdaNode) | Required |
Finalize
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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>().
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
operator->() | Read pointer (const) |
operator*() | Dereference (const) |
get() | Raw const T* pointer |
horus::Frequency
Represents a frequency in Hertz. Created via _hz literal.
| Method | Description |
|---|---|
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.
| Value | Description |
|---|---|
Miss::Warn | Log warning, continue |
Miss::Skip | Skip this tick |
Miss::SafeMode | Enter safe state |
Miss::Stop | Stop scheduler |
Duration Literals
using namespace horus::literals;
| Literal | Type | Example |
|---|---|---|
_hz | Frequency | 100_hz |
_ms | microseconds | 5_ms (5000us) |
_us | microseconds | 200_us |
_ns | nanoseconds | 500_ns |
_s | microseconds | 3_s (3000000us) |
horus::TensorPool
SHM-backed memory pool for zero-copy large data (images, point clouds, tensors).
#include <horus/pool.hpp>
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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);
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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);
| Method | Description |
|---|---|
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);
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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 */ }
| Method | Description |
|---|---|
ActionClient(name) | Create client for named action |
send_goal(json) | Send goal, returns GoalHandle |
horus::GoalHandle
Tracks a sent goal. Move-only.
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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");
| Method | Description |
|---|---|
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.