Skip to main content

Executor

Struct Executor 

Source
pub struct Executor { /* private fields */ }

Implementations§

Source§

impl Executor

Source

pub fn open(config: &ExecutorConfig<'_>) -> Result<Self, NodeError>

Open a new executor session using the active RMW backend.

Phase 115.M.4 — auto-registers the cffi vtable for whichever backend the build was configured for, mirroring the C++ side’s #ifdef NROS_RMW_<NAME> fan-out in <nros/node.hpp>. The runtime’s atomic vtable slot is idempotent: a re-call of any backend’s register() is a no-op, so the fan-out below is safe to invoke on every Executor::open (cheaper than a Once and doesn’t pull in std::sync for no_std targets).

Connects to the middleware at the locator specified in config.

§Example
let config = ExecutorConfig::from_env().node_name("my_node");
let mut executor = Executor::open(&config)?;
Source

pub fn open_multi(specs: &[SessionSpec<'_>]) -> Result<Self, NodeError>

Phase 128.F.1 — explicit per-backend session declaration for bridge mode. specs[0] becomes the primary session; specs[1..] open as extras keyed by RMW name. After construction, every create_node_on(name, rmw) call dispatches to whichever session was opened under that RMW name (or, when the rmw name matches the primary, the primary session itself).

Single-backend callers should keep using open — this entry costs an extra open_with_rmw per spec and adds no value when only one backend is linked.

$NROS_RMW env is ignored: bridge mode wants explicit names.

Source

pub fn open_with_rmw( rmw_name: &str, config: &ExecutorConfig<'_>, ) -> Result<Self, NodeError>

Phase 104.C.1 — open the Executor against a specific RMW backend by name. Selects from the named registry (Phase 104.B.2). rmw_name must match one of the names a backend registered under ("zenoh", "cyclonedds", "xrce", …).

Equivalent to Executor::open when the registry has exactly one backend (the default-backend fast path). Use this entry point in multi-backend builds where Executor::open would pick the first-registered slot.

Single-Executor multi-Node multi-RMW (the long-term Design X from docs/roadmap/phase-104-multi-backend-bridges.md) is follow-up work — Phase 104.C.2 + C.3.

Source§

impl Executor

Source

pub fn from_session(session: CffiSession) -> Self

Create an executor from an already-opened session.

Source

pub unsafe fn from_session_ptr(session_ptr: *mut CffiSession) -> Self

Create an executor from a borrowed session pointer.

§Safety
  • session_ptr must point to a valid, initialized session that lives at least as long as this executor.
  • The caller must not move or drop the session while the executor exists.
Source

pub unsafe fn open_with_session(session: *mut CffiSession) -> Self

Phase 228.B (RFC-0015) — construct a tier task’s executor that shares a session opened once by the orchestration main().

In the per-tier execution model main() opens one RMW session, then spawns one RTOS task per priority tier; each task calls this to get an Executor over the same session (the Borrowed session store — this executor neither owns nor closes it), registers its tier’s callback groups, and spins. Thin alias over Executor::from_session_ptr.

§Safety

session must outlive every executor/task built from it (the orchestration main() holds it and never returns / WFIs), and must not be mutated except through these executors’ spin calls.

Source

pub fn session_ptr(&mut self) -> *mut CffiSession

Raw pointer to this executor’s RMW session, for the per-tier model: the boot task opens the one session via Executor::open (the RMW session is a process-wide singleton — opening twice fails), then hands this pointer to each spawned tier task’s Executor::open_with_session. The boot task’s executor owns the session and outlives every borrower, so the pointer stays valid for the program’s life. Works for both Owned and Borrowed stores.

§Safety

The returned pointer aliases self.session. Callers must keep self alive (not moved/dropped) for as long as any tier executor uses the pointer, and must only touch the session through executor spin calls (the RMW backend serializes concurrent access through its own locks).

Source

pub fn session_handle(&mut self) -> SessionHandle

Opaque, Send form of session_ptr — the per-tier model hands this to each spawned tier task (it can cross the RTOS task / thread boundary, which a bare *mut cannot). See SessionHandle.

§Safety

Same contract as session_ptr: self (the session owner) must outlive every executor built from the handle.

Source

pub unsafe fn open_with_session_handle(handle: SessionHandle) -> Self

Open an Executor over the session a SessionHandle refers to (the Borrowed store — neither owns nor closes it). The tier-task counterpart to session_handle.

§Safety

The handle’s session must still be alive (its owning executor not moved or dropped); access only through executor spin calls.

Source

pub fn set_active_groups(&mut self, groups: &[&str])

Phase 228.C — set this tier executor’s active callback-group filter. The generated per-tier task calls this before registering nodes; afterwards only callbacks whose .callback_group() is in groups register here. An empty slice (or never calling it) leaves the wildcard — register all callbacks (the single-tier degenerate case + today’s behaviour).

Source

pub fn group_active(&self, group: &str) -> bool

Phase 228.C — whether a callback in group should register in this executor under the current filter. Wildcard (None) accepts everything.

Source

pub fn set_primary_identity(&mut self, rmw_name: &str, locator: &str)

Set the node name and namespace used for liveliness tokens.

Called by open() to propagate config values. When register_subscription or register_service creates entities, these values are attached to the Phase 156 — record the primary session’s backend identity (rmw name + locator) so NodeBuilder::resolve_session_slot can detect when a .rmw(name) matches the primary instead of opening a SECOND backend session against the same singleton (zenoh-pico’s g_session is process-wide; opening twice fails). Executor::open* calls this automatically; the C surface (nros_executor_init) calls it manually because it constructs via from_session_ptr which doesn’t know the open metadata. Empty strings = “no primary identity tracked”; the cache check degrades to always-miss.

Source

pub fn set_node_identity(&mut self, node_name: &str, namespace: &str)

TopicInfo/ServiceInfo so the zenoh backend can declare liveliness.

Source

pub fn default_sched_context_id(&self) -> SchedContextId

Identifier of the auto-created default Fifo-class scheduling context. Every callback registered without an explicit [bind_handle_to_sched_context] binds to this SC.

Source

pub fn create_sched_context( &mut self, sc: SchedContext, ) -> Result<SchedContextId, NodeError>

Register a new scheduling context. Returns a [SchedContextId] callers pass to [bind_handle_to_sched_context] to attach callbacks. Phase 110.B.

Source

pub fn bind_handle_to_sched_context( &mut self, handle: HandleId, sc_id: SchedContextId, ) -> Result<(), NodeError>

Bind a registered callback to a scheduling context. The next spin_once cycle dispatches the callback through that SC’s queue (FIFO bitmap or EDF heap). Phase 110.B.

Source

pub fn register_time_triggered_dispatcher(&mut self, major_frame_us: u32)

Phase 110.G — enable time-triggered dispatch by setting the executor’s major-frame length. Once set, every spin_once cycle gates dispatch through each entry’s bound SC’s tt_window_offset_us / tt_window_duration_us fields: dispatch only fires when the current monotonic time falls inside the window [off, off + duration) mod major_frame.

major_frame_us = 0 disables the TT gate (default state). Setting a non-zero major frame after callbacks are already registered is allowed — TT gates take effect on the next spin_once cycle.

Source

pub fn apply_time_triggered_schedule<const N: usize>( &mut self, schedule: &TimeTriggeredSchedule<N>, ) -> Result<[SchedContextId; N], TimeTriggeredScheduleError>

Phase 110.G — apply a declarative cyclic schedule.

One-shot helper that wraps the underlying primitives: validates the schedule (major_frame > 0, no overlapping windows, every window fits inside the major frame), sets the executor’s major-frame length, then materialises one SchedContext per window with class = TimeTriggered + the window’s offset / duration. Returns the per-window [SchedContextId] array so callers can immediately bind_handle_to_sched_context(handle, sc_id) for their subscription / timer handles.

N is the schedule’s declared maximum window count; schedule.window_count gates how many SCs are actually created. Unused trailing slots return SchedContextId::default() (sentinel — callers must respect window_count).

Source

pub fn register_sporadic_timer( &mut self, sc_id: SchedContextId, timer: OpaqueTimerHandle, ) -> Result<Arc<AtomicSporadicState>, NodeError>

Phase 110.E.b — register an ISR-driven refill timer for an already-created Sporadic SC. The caller invokes their platform’s PlatformTimer::create_periodic with the returned Arc<AtomicSporadicState> as user_data and the atomic_sporadic_refill_thunk as the callback, then hands the resulting platform handle to this method via OpaqueTimerHandle::new(handle, destroy_fn).

The Executor stores both the Arc and the handle so Drop can clean them up. Calling this on a non-Sporadic SC returns Err(InvalidSchedContextBinding).

Source

pub fn sched_context(&self, sc_id: SchedContextId) -> Option<&SchedContext>

Inspect a registered scheduling context. Phase 110.B.

Source

pub fn node_builder<'a, 'cfg>( &'a mut self, name: &'cfg str, ) -> NodeBuilder<'a, 'cfg>

Phase 104.C.2 — start a rclcpp-style Node builder for this Executor. The returned NodeBuilder is chainable:

let id = exec.node_builder("ingress")
    .rmw("zenoh")
    .locator("tcp/127.0.0.1:7447")
    .sched(my_sc_id)
    .build()?;

In Phase 104.C.2 the Node table is storage-only — all registered Nodes share the Executor’s primary session. Per- Node session binding (the bridge feature) lands in Phase 104.C.3 when the session cache is wired.

Source

pub fn nodes(&self) -> &[NodeRecord]

Return the Node table — Phase 104.C.2 read accessor.

Source

pub fn node(&self, id: NodeId) -> Option<&NodeRecord>

Borrow a Node’s metadata by id, returning None if the id is out of range.

Source

pub fn node_mut(&mut self, id: NodeId) -> NodeCtx<'_>

Phase 189.M1 — an executor-borrowing node handle for the entity builders (exec.node_mut(id).subscription(t)... / .create_subscription(...)). A short-lived &mut Executor borrow — use one at a time; entity handles are owned and outlive it (see NodeCtx).

Source

pub fn node_session_mut(&mut self, node_id: NodeId) -> Option<&mut CffiSession>

Phase 104.C.9.b — resolve the per-Node session for direct entity creation paths (C++ FFI publisher / subscription / service that bypass the register_*_on arena dispatch). Returns None when node_id is out of range or the Node’s session_idx lands outside the executor’s session table.

Source

pub fn create_publisher_on<M: MessageForRmw>( &mut self, node_id: NodeId, topic_name: &str, qos: QosSettings, ) -> Result<EmbeddedPublisher<M>, NodeError>

Phase 189.M1 — create a typed publisher bound to a node’s session. Backs node.publisher(t).typed::<M>().build() on the executor-borrowing NodeCtx; the returned handle is owned and outlives the NodeCtx.

Source

pub fn create_publisher_raw_on( &mut self, node_id: NodeId, topic_name: &str, type_name: &str, type_hash: &str, qos: QosSettings, ) -> Result<EmbeddedRawPublisher, NodeError>

Phase 189.M1 — create a generic (type-erased) publisher bound to a node’s session. Backs node.publisher(t).generic(ty, hash).build(); the bridge re-publishes through this handle on the dest session.

Source

pub fn with_node_try<R>( &mut self, id: NodeId, f: impl FnOnce(&mut NodeHandle<'_>) -> Result<R, NodeError>, ) -> Result<R, NodeError>

Phase 104.C.3.2 — scoped Node-handle access. The closure receives a [Node] bound to the requested [NodeId]’s session + identity. Use the standard Node::create_publisher, create_subscription, etc. APIs inside.

rclcpp-aligned bridge pattern:

let node_in = exec.node_builder("ingress").rmw("zenoh").build()?;
let node_out = exec.node_builder("egress").rmw("xrce").build()?;

let pub_out = exec.with_node(node_out, |n| {
    n.create_publisher::<Int32>("/fwd")
})??;

exec.with_node(node_in, |n| {
    n.create_subscription_buffered::<Int32, _, 1024>(
        "/src", qos(), move |m| { let _ = pub_out.publish(m); }
    )
})??;

The closure can return any type; double-? unwraps the outer Result<R, NodeError> from with_node and the inner result returned by the closure. Phase 104.C.3.3.d — flat-Result variant of with_node. When the closure already returns Result<R, NodeError>, this avoids the double-?:

// Without `with_node_try`:
let pub_ = exec.with_node(id, |n| n.create_publisher(...))??;

// With `with_node_try`:
let pub_ = exec.with_node_try(id, |n| n.create_publisher(...))?;
Source

pub fn with_node<R>( &mut self, id: NodeId, f: impl FnOnce(&mut NodeHandle<'_>) -> R, ) -> Result<R, NodeError>

Source

pub fn node_id_by_name(&self, name: &str, namespace: &str) -> Option<NodeId>

Find a registered executor node by final name and namespace.

Source

pub fn create_node(&mut self, name: &str) -> Result<NodeHandle<'_>, NodeError>

Create a node on this executor.

Source

pub fn create_node_on( &mut self, name: &str, rmw: &str, ) -> Result<NodeHandle<'_>, NodeError>

Phase 128.F.2 — bridge-mode node factory. Registers a Node bound to the named RMW backend by opening (or reusing) an extra session via node_builder().rmw(rmw).build(), then returns a [Node] borrowing that session. Use when the binary intentionally links more than one backend and a Node must speak a specific one.

The single-backend common case should keep using create_node — this entry costs an extra session lookup and serves no purpose when only one backend is registered.

Source

pub fn close(&mut self) -> Result<(), NodeError>

Close the underlying session.

Source

pub fn register_dispatch_slot( &mut self, state: *mut c_void, on_callback: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut c_void), ) -> Result<(), ()>

Phase 216 follow-up — register a per-Node dispatch trampoline.

The board-side Entry pkg (or the macro-emitted register_dispatch(executor) wrapper, once wired) calls this once per deployed Node pkg, handing in the __nros_node_<pkg>_on_callback symbol + the Node’s per-pkg state blob. Executor::dispatch_callback then linear-scans the registered slots when the dispatch task hands off a SignaledCallback.

Returns Err(()) when the registry is full (MAX_NODES entries — raise via NROS_EXECUTOR_MAX_NODES at build time).

§Safety

state must outlive the executor (the typical shape is a *mut State produced by nros::__private_node_state_into_raw from the macro-emitted i(); that pointer’s lifetime IS the Executor’s by construction). on_callback must be safe to invoke with (state, cb_id_ptr, cb_id_len, ctx) matching the per-Node __nros_node_<pkg>_on_callback ABI emitted by the nros::node!() macro (Phase 216.A.5).

Source

pub fn dispatch_slot_count(&self) -> usize

Phase 216 follow-up — current registered dispatch-slot count. Diagnostic / test surface.

Source

pub unsafe fn enroll_component( &mut self, state: *mut c_void, tick: unsafe extern "C" fn(*mut c_void, *mut c_void), drop: unsafe extern "C" fn(*mut c_void), ) -> Result<(), ()>

Phase 258 (Track 2, 2a) — enroll a component into the executor-owned tick registry. Called by nros’s install/register_node_borrowed after it builds the Arc<ComponentCell>: state is the leaked Arc<ComponentCell> (the slot takes ownership), tick/drop are the nros-side trampolines (see [ComponentSlot]). The slot’s tick runs at the tail of every spin_once; its drop runs once on Executor::drop.

Returns Err(()) when the registry is full (MAX_NODES — raise via NROS_EXECUTOR_MAX_NODES at build time). On error the caller still owns state (the slot was not stored) and must drop it.

§Safety

state must be a *mut produced by leaking the component cell the tick/drop trampolines expect (an Arc<ComponentCell> via Arc::into_raw in the canonical nros caller), and must remain valid until the matching drop runs. tick must be safe to invoke with (state, exec_ctx = *mut Executor) each spin; drop must be safe to invoke exactly once with state.

Source

pub fn component_slot_count(&self) -> usize

Phase 258 (Track 2, 2a) — current enrolled component-slot count. Diagnostic / test surface.

Source

pub fn dispatch_callback(&mut self, cb_id: &str, ctx: *mut c_void)

Phase 216 final dispatch hook — stable entry point the framework’s dispatch task (RTIC __nros_run / Embassy __nros_run_task) calls for each SignaledCallback envelope it dequeues from the board-side SPSC / Embassy channel.

§Signature shape

nros-node sits below nros in the dep graph, so the typed nros::CallbackId<'_> / nros::CallbackCtx<'_> types referenced in the Phase 216 design notes cannot appear in the signature here. The macro emit translates the dequeued envelope to the layer-clean (cb_id: &str, ctx: *mut c_void) pair before calling this method; the per-Node on_callback trampoline ABI (Phase 216.A.5, __nros_node_<pkg>_on_callback(state, cb_id_ptr, cb_id_len, ctx)) uses the same untyped shape on the other side of the fence, so the round-trip stays type-consistent.

§Body — linear scan of the dispatch registry

Each registered [DispatchSlot] holds an __nros_node_<pkg>_on_callback fn pointer + the owning Node’s state blob. The macro-emitted trampoline body matches on CallbackId tags the Node declared and is a no-op for non-matching cb_ids — at most one Node per cb_id actually acts, the rest are cheap string-compare no-ops. This mirrors the strategy ExecutorNodeRuntime::dispatch_callback uses in packages/core/nros/src/node_runtime.rs:470.

§What’s NOT auto-wired today

The nros::node!() macro doesn’t yet emit a register_dispatch(executor) wrapper that pushes the per-pkg (state, on_callback) into this registry. Until that wiring lands (Phase 216 follow-up — see commit msg), downstream consumers (board’s init_hardware, or the codegen-emitted run_plan) must call Executor::register_dispatch_slot explicitly with the __nros_node_<pkg>_on_callback symbol + a state blob from the macro-emitted i().

Source

pub fn session(&self) -> &CffiSession

Get a reference to the underlying session.

Source

pub fn session_mut(&mut self) -> &mut CffiSession

Get a mutable reference to the underlying session.

Source

pub fn ping(&mut self, timeout_ms: i32) -> Result<(), NodeError>

Phase 124.F.3 — session-level connectivity probe. Wire-level round-trip “is the peer / agent / router still reachable?” — cheaper than the service-availability probe (no discovery state required).

Returns Ok(()) on reply within timeout_ms, Err(NodeError::Transport(Timeout)) on no reply, Err(NodeError::Transport(Unsupported)) when the active backend can’t probe.

Mirrors micro-ROS’s rmw_uros_ping_agent. Useful for reconnect-on-link-loss patterns: bare-metal code can call ping(100) periodically and tear down / re-open the session on timeout.

Source

pub unsafe fn action_client_core_mut( &mut self, entry_index: usize, ) -> Option<&mut ActionClientCore>

Get a mutable reference to an action client core in the arena by entry index.

§Safety

The caller must ensure that entry_index refers to an ActionClientRawArenaEntry.

Source

pub unsafe fn service_client_entry_mut( &mut self, entry_index: usize, ) -> Option<&mut ServiceClientRawArenaEntry<{ crate::config::DEFAULT_RX_BUF_SIZE }>>

Get a mutable reference to a service-client arena entry (Phase 82).

Returns None if entry_index doesn’t refer to a service client entry. The default reply buffer size is assumed because the C API always uses the default — the entry was registered via register_service_client_raw_sized::<DEFAULT_RX_BUF_SIZE>.

§Safety

entry_index must refer to a ServiceClientRawArenaEntry.

Source

pub fn set_trigger(&mut self, trigger: Trigger)

Set the executor-level trigger condition.

Controls which handles must be ready before spin_once dispatches callbacks. Defaults to Trigger::AnyReady.

Source

pub fn set_semantics(&mut self, semantics: ExecutorSemantics)

Set the executor data communication semantics.

Choose between Direct (process in place) and LET (snapshot-then-process) semantics. See ExecutorSemantics.

Source

pub fn set_invocation(&mut self, id: HandleId, mode: InvocationMode)

Set the invocation mode for a specific handle.

Controls whether the callback fires on every spin (Always) or only when new data arrives (OnNewData, the default).

Source

pub fn register_subscription_buffered_raw_info_on<F, const RX_BUF: usize>( &mut self, node_id: NodeId, topic_name: &str, type_name: &str, type_hash: &str, qos: QosSettings, callback: F, ) -> Result<HandleId, NodeError>
where F: FnMut(&[u8], &RawMessageInfo<'_>) + 'static,

Register a raw (type-erased) buffered subscription whose callback also receives a RawMessageInfo carrying the sample’s wire attachment (Phase 189.M1).

Backs the node.subscription(t).generic(..).message_info().build(cb) builder — the cross-RMW bridge reads the bridge_origin tag from info.attachment() for echo suppression. One sample per spin_once; the attachment is staged in a flat per-entry buffer (cap RAW_INFO_ATT_CAP).

Source

pub fn add_arena_subscription_callback<F, const RX_BUF: usize>( &mut self, handle: RmwSubscriber, qos: QosSettings, callback: F, ) -> Result<HandleId, NodeError>
where F: FnMut(&[u8]) + 'static,

Register a raw byte-shaped callback against a pre-built RmwSubscriber handle.

Backend-agnostic primitive — the caller is responsible for obtaining the handle by whatever route the active backend supports:

  • Generic ROS-typed flow: call Session::create_subscriber on self.session_mut() with a TopicInfo. The node_mut(id).subscription(t).generic(ty, hash) builder is the convenience wrapper for this path.
  • Backend-specific flow (e.g. uORB needs &'static orb_metadata): reach into the concrete session via Self::session_mut and call its backend-specific create method, then hand the handle here. nros-px4::uorb::create_subscription_with_callback is the example.

The arena-store + vtable wiring is identical to register_subscription_buffered_raw; the only thing that varies is where the handle came from. Callback fires on every message delivery during spin_once; bytes are passed as &[u8].

Source

pub fn register_service<Svc, F>( &mut self, service_name: &str, callback: F, ) -> Result<HandleId, NodeError>
where Svc: RosService + 'static, Svc::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Register a service callback with the default buffer size.

The callback is stored in the arena and invoked during spin_once().

Source

pub fn register_service_sized<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, service_name: &str, callback: F, ) -> Result<HandleId, NodeError>
where Svc: RosService + 'static, Svc::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Register a service callback with custom request/reply buffer sizes.

Source

pub fn register_service_sized_on<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, node_id: NodeId, service_name: &str, qos: QosSettings, callback: F, ) -> Result<HandleId, NodeError>
where Svc: RosService + 'static, Svc::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Phase 104.C.3.3.a — Node-aware variant of register_service_sized.

Source

pub fn register_service_on<Svc, F>( &mut self, node_id: NodeId, service_name: &str, callback: F, ) -> Result<HandleId, NodeError>
where Svc: RosService + 'static, Svc::Request: MessageForRmw, Svc::Reply: MessageForRmw, F: FnMut(&Svc::Request) -> Svc::Reply + 'static,

Phase 104.C.3.3.a — Node-aware variant of register_service.

Source

pub fn register_timer<F>( &mut self, period: TimerDuration, callback: F, ) -> Result<HandleId, NodeError>
where F: FnMut() + 'static,

Register a repeating timer callback.

The callback fires every period milliseconds during spin_once(). The timer delta is approximated by the timeout_ms argument to spin_once.

Source

pub fn register_timer_oneshot<F>( &mut self, delay: TimerDuration, callback: F, ) -> Result<HandleId, NodeError>
where F: FnMut() + 'static,

Register a one-shot timer callback.

The callback fires once after delay milliseconds, then becomes inert.

Source

pub fn add_arena_subscription_c_callback<const RX_BUF: usize>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QosSettings, callback: RawSubscriptionCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

The kept C-FFI subscription core (Phase 189.M2.b): registers a raw RawSubscriptionCallback fn-ptr + context against an optional node’s session. The Rust ergonomic surface is the node.subscription(t) builder (closures); this is the single primitive the nros-c thin wrapper lowers to. node_id == None is the legacy single-node path.

Source

pub fn add_arena_subscription_c_info_callback<const RX_BUF: usize>( &mut self, node_id: Option<NodeId>, topic_name: &str, type_name: &str, type_hash: &str, qos: QosSettings, callback: RawSubscriptionInfoCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

Phase 189.M3.4 — register a raw C-fn-ptr subscription whose callback also receives the sample’s wire attachment (RawSubscriptionInfoCallback: (data, len, attachment, att_len, context)) — the C analog of the Rust node.subscription(t).generic(..).message_info() builder. Backs the C FFI nros_executor_register_subscription_raw_with_info. Flat per-entry payload + attachment buffers (cap RAW_INFO_ATT_CAP); one sample per spin_once.

Source

pub fn register_service_raw( &mut self, service_name: &str, service_type: &str, service_hash: &str, callback: RawServiceCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

Register a raw (untyped) service callback.

Register a raw (untyped) service callback with the default buffer size.

The callback receives and produces CDR bytes without typed deserialization/serialization. Used by the C API wrapper.

Source

pub fn register_service_raw_sized<const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, service_name: &str, service_type: &str, service_hash: &str, qos: QosSettings, callback: RawServiceCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

Register a raw (untyped) service callback with custom buffer sizes + QoS.

REQ_BUF and REPLY_BUF set the stack-allocated CDR buffers for the request and reply respectively. Increase for services with large payloads (e.g., parameter services). qos applies to both the request + reply endpoints (Phase 193.2c).

Source

pub fn register_service_raw_sized_on<const REQ_BUF: usize, const REPLY_BUF: usize>( &mut self, node_id: NodeId, service_name: &str, service_type: &str, service_hash: &str, qos: QosSettings, callback: RawServiceCallback, context: *mut c_void, ) -> Result<HandleId, NodeError>

Phase 104.C.3.3.a — Node-aware variant of [register_service_raw_sized]. C-FFI path.

Source

pub fn register_service_client_raw( &mut self, service_name: &str, service_type: &str, service_hash: &str, callback: Option<RawResponseCallback>, context: *mut c_void, ) -> Result<HandleId, NodeError>

Register a raw (untyped) service client with the default reply buffer size.

The client is owned by the executor’s arena. Each spin_once dispatch polls the in-flight reply slot via try_recv_reply_raw and fires the registered callback when the response arrives. Used by the C API thin wrapper — see Phase 82.

Source

pub fn register_service_client_raw_sized<const REPLY_BUF: usize>( &mut self, service_name: &str, service_type: &str, service_hash: &str, qos: QosSettings, callback: Option<RawResponseCallback>, context: *mut c_void, ) -> Result<HandleId, NodeError>

Register a raw service client with a custom reply buffer size + QoS.

qos applies to the client’s request + reply endpoints (Phase 193.3b); defaults to QosSettings::services_default via the convenience wrapper.

Source

pub fn register_service_client_raw_sized_on<const REPLY_BUF: usize>( &mut self, node_id: NodeId, service_name: &str, service_type: &str, service_hash: &str, qos: QosSettings, callback: Option<RawResponseCallback>, context: *mut c_void, ) -> Result<HandleId, NodeError>

Phase 104.C.3.3.a — Node-aware variant of [register_service_client_raw_sized]. Routes the client creation through the named Node’s session.

Source

pub fn register_guard_condition<F>( &mut self, callback: F, ) -> Result<(HandleId, GuardConditionHandle), NodeError>
where F: FnMut() + 'static,

Register a guard condition with a callback.

Returns both the HandleId for trigger configuration and a GuardConditionHandle for triggering from other threads.

Source

pub fn cancel_timer(&mut self, id: HandleId) -> Result<(), NodeError>

Cancel a timer. A cancelled timer will not fire but still accumulates elapsed time. The timer can be restarted with reset_timer().

Source

pub fn reset_timer(&mut self, id: HandleId) -> Result<(), NodeError>

Reset a timer. Clears the cancelled state and resets the elapsed time to zero, so the timer starts a fresh period.

Source

pub fn timer_is_cancelled(&self, id: HandleId) -> bool

Check if a timer is cancelled.

Source

pub fn timer_period_ms(&self, id: HandleId) -> Option<u64>

Get the period of a timer in milliseconds, or None if the handle is not a valid timer.

Source

pub fn spin_once(&mut self, timeout: Duration) -> SpinOnceResult

Drive I/O and dispatch registered callbacks once.

Three-phase execution:

  1. Readiness scan — query each handle’s has_data().
  2. Trigger evaluation — check if the executor-level trigger passes.
  3. Dispatch — invoke callbacks according to their InvocationMode.

Returns a SpinOnceResult with counts of processed items and errors.

§Arguments
  • timeout — upper bound on the I/O wait. Saturated at i32::MAX ms (~24 days) for the underlying transport call.

Phase 84.D7: unified on core::time::Duration. The previous timeout_ms: i32 signature had a latent footgun where spin_once(-1) silently froze timers while still polling I/O; Duration has no negative sentinel.

Source

pub fn spin(&mut self, timeout: Duration) -> !

Drive I/O and dispatch callbacks in an infinite loop.

Each iteration calls spin_once(timeout_ms), which pumps the transport and dispatches all registered callbacks.

This is the primary run loop for embedded applications:

let mut executor = Executor::open(&config)?;
executor.register_subscription::<Int32, _>("/topic", |msg| { /* ... */ })?;
executor.spin(10); // never returns
Source

pub fn spin_default(&mut self) -> !

Phase 104.C.3.3.c — rclcpp-spin()-shape no-arg variant. Defaults the per-iteration timeout to 50 ms, which keeps idle binaries from busy-spinning while staying responsive enough for default-QoS messaging.

Source

pub async fn spin_async(&mut self) -> !

Drive I/O and dispatch callbacks asynchronously.

Runs forever, yielding between poll cycles so that other async tasks (e.g., Promise) can make progress.

Uses only core::future — no external async runtime dependency.

§Usage patterns
// Pattern 1: select with a promise (embassy-futures)
use embassy_futures::select::{select, Either};
let promise = client.call(&req)?;
let Either::Second(reply) = select(executor.spin_async(), promise).await
    else { unreachable!() };

// Pattern 2: manual polling (no async runtime)
let mut promise = client.call(&req)?;
loop {
    executor.spin_once(core::time::Duration::from_millis(10));
    if let Ok(Some(r)) = promise.try_recv() { break r; }
}
Source

pub fn spin_one_period( &mut self, period_ms: u64, elapsed_ms: u64, ) -> SpinPeriodPollingResult

Process one iteration and return remaining sleep time.

This is no_std compatible — the caller is responsible for the actual delay using platform-specific sleep.

§Arguments
  • period_ms - Target period in milliseconds
  • elapsed_ms - Time elapsed since last call (used for timer ticking)
§Example
loop {
    let r = executor.spin_one_period(10, elapsed_ms);
    platform_sleep_ms(r.remaining_ms);
}
Source§

impl Executor

Source

pub fn spin_blocking(&mut self, opts: SpinOptions) -> Result<(), NodeError>

Blocking spin loop with configurable exit conditions.

Runs until one of:

  • halt() is called (from another thread or signal handler)
  • Timeout expires (if set in options)
  • Max callbacks reached (if set in options)
  • only_next is true (single iteration)
§Example
// Spin forever until halted
executor.spin_blocking(SpinOptions::default())?;

// Spin with 5-second timeout
executor.spin_blocking(SpinOptions::new().timeout_ms(5000))?;

// Single iteration
executor.spin_blocking(SpinOptions::spin_once())?;
Source

pub fn spin_one_period_timed(&mut self, period: Duration) -> SpinPeriodResult

Execute one period with wall-clock overrun detection.

Calls spin_once(), measures wall-clock time, sleeps for the remainder if under budget.

§Example
let period = std::time::Duration::from_millis(10);
let result = executor.spin_one_period_timed(period);
if result.overrun {
    log::warn!("Period overrun: {:?}", result.elapsed);
}
Source

pub fn spin_period(&mut self, period: Duration) -> Result<(), NodeError>

Spin at a fixed rate with drift compensation. Blocks until halted.

Uses wall-clock time to maintain the target rate. The next invocation time is accumulated (not reset to now + period) to prevent cumulative drift.

§Example
// 100Hz control loop — blocks until halt() is called
executor.spin_period(std::time::Duration::from_millis(10))?;
Source

pub fn halt(&self)

Request the executor to stop spinning.

Sets a flag that causes spin_blocking() or spin_period() to exit on the next iteration. Safe to call from another thread or signal handler.

Also raises the Phase 104.C.6 wake flag so a spin_once already blocked inside a backend’s drive_io falls through to the halt check on its next loop iteration instead of waiting out its full timeout_ms first.

Source

pub unsafe fn open_threaded( self, policy: SchedPolicy, apply_policy: fn(SchedPolicy) -> Result<(), SchedError>, spin_period: Duration, ) -> ThreadHandle

Phase 110.D.b — move this Executor onto a fresh OS thread, apply a per-thread scheduling policy via the caller-supplied apply_policy function, and run the spin loop until [ThreadHandle::halt] fires.

The function-pointer indirection on apply_policy lets the caller pass any platform’s PlatformScheduler::set_current_thread_policy without forcing Executor to be generic over the platform — keeps the existing Executor type stable.

Multi-executor preemption (the actual hard-RT win) comes from the OS scheduler — call open_threaded once per criticality tier, each with its own policy / priority. The kernel handles preemption across executors; within a single executor, dispatch remains non-preemptive (110.A–C bucketed sets).

§Safety

Moves self across thread boundaries. Executor contains a raw *mut session::ConcreteSession when constructed via from_session_ptr; the caller must ensure that pointer’s referent stays valid across the lifetime of the spawned thread and that no other thread mutates the session concurrently. from_session (Owned) is safer — ConcreteSession ownership transfers cleanly into the thread.

Source

pub fn is_halted(&self) -> bool

Check if halt has been requested.

Source

pub fn halt_flag(&self) -> Arc<AtomicBool>

Get a clone of the halt flag for use in signal handlers or other threads.

§Example
let halt = executor.halt_flag();
std::thread::spawn(move || {
    std::thread::sleep(Duration::from_secs(5));
    halt.store(true, Ordering::SeqCst);
});
executor.spin_blocking(SpinOptions::default())?;
Source

pub fn wake(&self)

Phase 104.C.6 — wake the executor from another thread / ISR / signal handler.

Sets the shared wake_flag. The next spin_once swap-clears the flag, skips the blocking wait on the primary session, and polls every session non-blockingly so whatever queued the wake is observed in a single iteration. Idempotent — multiple wake() calls collapse into one observed wake per spin_once.

Source

pub fn wake_handle(&self) -> Arc<AtomicBool>

Phase 104.C.6 — clone of the shared wake flag for cross-thread use (signal handlers, foreign threads, future per-backend vtable wake hooks).

§Example
let wake = executor.wake_handle();
std::thread::spawn(move || {
    // ... compute something ...
    // hand off to executor by setting the flag.
    wake.store(true, Ordering::SeqCst);
});
loop { executor.spin_once(Duration::from_millis(100)); }
Source§

impl Executor

Source

pub fn register_action_server<A, GoalF, CancelF>( &mut self, action_name: &str, goal_callback: GoalF, cancel_callback: CancelF, ) -> Result<ActionServerHandle<A>, NodeError>

Register an action server with goal/cancel callbacks.

The executor automatically dispatches:

  • Goal acceptance via goal_callback
  • Cancel requests via cancel_callback
  • Result serving for completed goals

Use the returned ActionServerHandle to publish feedback and complete goals.

Uses default buffer sizes and max 4 concurrent goals.

Source

pub fn register_action_server_sized<A, GoalF, CancelF, const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>( &mut self, action_name: &str, goal_callback: GoalF, cancel_callback: CancelF, ) -> Result<ActionServerHandle<A>, NodeError>

Register an action server with custom buffer sizes.

Source§

impl Executor

Source

pub fn register_action_server_raw( &mut self, spec: RawActionServerSpec<'_>, ) -> Result<ActionServerRawHandle, NodeError>

Register a raw action server with raw-bytes callbacks.

Unlike register_action_server(), this does not require RosAction — the goal/cancel callbacks receive raw CDR bytes. This is used by the C API thin wrapper.

type_name and type_hash identify the action type for key expression construction and liveliness tokens.

Source

pub fn register_action_server_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize, const MAX_GOALS: usize>( &mut self, spec: RawActionServerSpec<'_>, ) -> Result<ActionServerRawHandle, NodeError>

Register a raw action server with custom buffer sizes.

spec.node_id selects the target: None registers on the executor’s own node, Some(id) routes the server’s 5 underlying handles through the named Node’s session (Phase 104.C.3.3.a). spec.qos applies to the action’s three underlying service servers (send_goal / cancel_goal / get_result; Phase 193.4b); the feedback + status publishers keep their own profiles.

Source§

impl Executor

Source

pub fn register_action_client_raw( &mut self, spec: RawActionClientSpec<'_>, ) -> Result<ActionClientRawHandle, NodeError>

Register a raw action client with the executor.

Creates service clients for send_goal, cancel_goal, get_result, and a feedback subscriber. The executor polls these during spin_once and invokes the provided callbacks when responses/feedback arrive.

§Arguments
  • action_name — action name (e.g., “/fibonacci”)
  • type_name — action type (e.g., “example_interfaces::action::dds_::Fibonacci_”)
  • type_hash — type hash (e.g., “TypeHashNotSupported”)
  • goal_response_callback — called when goal is accepted/rejected
  • feedback_callback — called when feedback is received
  • result_callback — called when result is received
  • context — opaque pointer passed to all callbacks
Source

pub fn register_action_client_raw_sized<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize>( &mut self, spec: RawActionClientSpec<'_>, ) -> Result<ActionClientRawHandle, NodeError>

Register a raw action client with explicit buffer sizes.

spec.node_id selects the target: None registers on the executor’s own node, Some(id) routes the client’s 4 underlying handles through the named Node’s session (Phase 104.C.3.3.a).

Source§

impl Executor

Source

pub fn register_action_client_core<const GOAL_BUF: usize, const RESULT_BUF: usize, const FEEDBACK_BUF: usize>( &mut self, core: ActionClientCore<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>, goal_response_callback: Option<RawGoalResponseCallback>, feedback_callback: Option<RawFeedbackCallback>, result_callback: Option<RawResultCallback>, context: *mut c_void, ) -> Result<ActionClientRawHandle, NodeError>

Register an existing ActionClientCore with the executor for async polling.

Unlike register_action_client_raw (which creates new transport handles), this takes ownership of an existing core. Use this when the core was already created by the C/C++ action client init.

Trait Implementations§

Source§

impl Drop for Executor

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

impl Send for Executor

Available on crate feature std only.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.