pub struct Executor<'s> { /* private fields */ }Implementations§
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_in(
config: &ExecutorConfig<'_>,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Result<Executor<'s>, NodeError>
pub unsafe fn open_in( config: &ExecutorConfig<'_>, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Executor<'s>, NodeError>
phase-271 — open a new executor session over caller-supplied backing,
sized by sizing (per-entry sizing). The core, non-generic sized entry
point: the alloc open convenience leaks a default
backing and delegates here, and the nros::main! macro emits a backing
sized to the entry’s own entity count.
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.
§Safety
backing must be ≥ sizing.u64_len() words, live for 's, and be
otherwise untouched while the executor lives (see
from_session_in).
Source§impl Executor<'static>
impl Executor<'static>
Sourcepub fn open(config: &ExecutorConfig<'_>) -> Result<Executor<'static>, NodeError>
pub fn open(config: &ExecutorConfig<'_>) -> Result<Executor<'static>, NodeError>
Open a new executor session using the active RMW backend, at the
build-time default sizing. Convenience over
open_in: leaks a default-sized backing (executor-
lifetime) so existing callers keep the zero-storage-arg signature.
Per-entry sizing goes through open_in / the nros::main! macro.
§Example
let config = ExecutorConfig::from_env().node_name("my_node");
let mut executor = Executor::open(&config)?;Sourcepub fn open_sized(
config: &ExecutorConfig<'_>,
sizing: ExecutorSizing,
) -> Result<Executor<'static>, NodeError>
pub fn open_sized( config: &ExecutorConfig<'_>, sizing: ExecutorSizing, ) -> Result<Executor<'static>, NodeError>
phase-271 — like open but sized to a caller-supplied
sizing (its own declared topology) instead of the build-time default.
The alloc entry point the nros::main! macro’s native board path uses
to size a fat entry (>default MAX_CBS callbacks) without a
workspace-global NROS_EXECUTOR_MAX_CBS. Leaks a sizing-sized backing
(executor-lifetime); no-alloc entries use open_in with their own static.
Sourcepub fn open_multi(
specs: &[SessionSpec<'_>],
) -> Result<Executor<'static>, NodeError>
pub fn open_multi( specs: &[SessionSpec<'_>], ) -> Result<Executor<'static>, 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.
Default-sized alloc convenience over
open_multi_in (leaks a default backing).
Sourcepub fn open_with_rmw(
rmw_name: &str,
config: &ExecutorConfig<'_>,
) -> Result<Executor<'static>, NodeError>
pub fn open_with_rmw( rmw_name: &str, config: &ExecutorConfig<'_>, ) -> Result<Executor<'static>, 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.
Default-sized alloc convenience over
open_with_rmw_in (leaks a default backing).
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_multi_in(
specs: &[SessionSpec<'_>],
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Result<Executor<'s>, NodeError>
pub unsafe fn open_multi_in( specs: &[SessionSpec<'_>], backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Executor<'s>, NodeError>
Per-entry-sized open_multi: carves backing for
the executor’s tables instead of leaking a default one.
§Safety
backing/sizing as in from_session_in.
Sourcepub unsafe fn open_with_rmw_in(
rmw_name: &str,
config: &ExecutorConfig<'_>,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Result<Executor<'s>, NodeError>
pub unsafe fn open_with_rmw_in( rmw_name: &str, config: &ExecutorConfig<'_>, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Result<Executor<'s>, NodeError>
Per-entry-sized open_with_rmw: carves backing
for the executor’s tables instead of leaking a default one.
§Safety
backing/sizing as in from_session_in.
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn from_session_in(
session: CffiSession,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
pub unsafe fn from_session_in( session: CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
Create an owning executor over caller-supplied backing, sized by
sizing. The core, non-generic, per-entry entry point (the alloc
from_session convenience leaks a default backing
and calls this; the macro / C FFI pass an entry-sized backing).
§Safety
backing must be ≥ sizing.u64_len() words, stay alive for 's, and
not be otherwise accessed while the executor lives (it aliases it).
sizing.cbs must be ≤ 64 (the u64 ready-set bitmask ceiling).
Sourcepub unsafe fn from_session_ptr_in(
session_ptr: *mut CffiSession,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
pub unsafe fn from_session_ptr_in( session_ptr: *mut CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
Create a borrowing executor over caller-supplied backing, sized by
sizing. Counterpart to from_session_in for
the per-tier / C model (the session is borrowed, not owned).
§Safety
session_ptrmust point to a valid session that outlives the executor and is not moved/dropped while it exists.backingobligations as infrom_session_in.
Source§impl Executor<'static>
impl Executor<'static>
Sourcepub fn from_session(session: CffiSession) -> Executor<'static>
pub fn from_session(session: CffiSession) -> Executor<'static>
Create an executor from an already-opened session, using the build-time
default sizing (MAX_CBS/MAX_SC/ARENA_SIZE). Convenience for
std/alloc callers that don’t size per-entry: it leaks a default-sized
backing (executor-lifetime, one-time) and calls
from_session_in. Per-entry sizing goes through
the macro / open_in instead.
Sourcepub fn from_session_with(
session: CffiSession,
config: &ExecutorConfig<'_>,
) -> Executor<'static>
pub fn from_session_with( session: CffiSession, config: &ExecutorConfig<'_>, ) -> Executor<'static>
from_session with an ExecutorConfig, so a
caller that brings its own session can also bring its own clock.
issue 0709 / issue 0687 — from_session takes no config, and that is
the path the no-port population uses: it accepts any Session, so a
consumer with a non-cffi backend reaches the executor through it and had
NO way to install clock_us. phase-359 W10 argued the std-without-a-
port clock fallbacks could go because “a caller with a clock installs it
through ExecutorConfig::clock_us” — true for open, false here, which
is half of why that deletion was reverted.
Only the timing sources are read from config; identity (locator,
domain, names) belongs to the session the caller already opened. As in
open_in, a None field does NOT clobber the
platform default — it means “not specified” (the bug issue 0671
records).
Sourcepub unsafe fn from_session_ptr(
session_ptr: *mut CffiSession,
) -> Executor<'static>
pub unsafe fn from_session_ptr( session_ptr: *mut CffiSession, ) -> Executor<'static>
Create an executor from a borrowed session pointer, default-sized. The
alloc convenience wrapper over
from_session_ptr_in — leaks a default
backing so existing callers keep the zero-storage-arg signature.
§Safety
session_ptrmust 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§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_with_session(session: *mut CffiSession) -> Executor<'static>
pub unsafe fn open_with_session(session: *mut CffiSession) -> Executor<'static>
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.
Sourcepub unsafe fn open_with_session_in(
session: *mut CffiSession,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
pub unsafe fn open_with_session_in( session: *mut CffiSession, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
phase-271 — per-tier borrowed-session constructor over caller-supplied,
per-tier-sized backing. The sized counterpart to
open_with_session: each RTOS tier task owns
its own backing so tiers size independently.
§Safety
session obligations as in open_with_session;
backing/sizing as in from_session_ptr_in.
Sourcepub fn session_ptr(&mut self) -> *mut CffiSession
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).
Sourcepub fn session_handle(&mut self) -> SessionHandle
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.
Sourcepub unsafe fn open_with_session_handle(
handle: SessionHandle,
) -> Executor<'static>
pub unsafe fn open_with_session_handle( handle: SessionHandle, ) -> Executor<'static>
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.
Sourcepub unsafe fn open_with_session_handle_in(
handle: SessionHandle,
backing: &'s mut [MaybeUninit<u64>],
sizing: ExecutorSizing,
) -> Executor<'s>
pub unsafe fn open_with_session_handle_in( handle: SessionHandle, backing: &'s mut [MaybeUninit<u64>], sizing: ExecutorSizing, ) -> Executor<'s>
phase-271 — sized counterpart to
open_with_session_handle (per-tier
backing).
§Safety
Sourcepub fn set_active_groups(&mut self, groups: &[&str])
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).
Sourcepub fn group_active(&self, group: &str) -> bool
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. The wildcard accepts everything.
Sourcepub fn set_primary_identity(&mut self, rmw_name: &str, locator: &str)
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.
Sourcepub fn set_domain_id(&mut self, domain_id: u32)
pub fn set_domain_id(&mut self, domain_id: u32)
TopicInfo/ServiceInfo so the zenoh backend can declare liveliness.
Issue 0656 — set the ROS domain for entities this executor declares.
For bindings that build an executor from an existing session
(from_session_ptr_in), where no ExecutorConfig is available and the
domain would otherwise stay at its 0 floor.
pub fn set_node_identity(&mut self, node_name: &str, namespace: &str)
Sourcepub fn declare_remap(
&mut self,
node_name: &str,
namespace: &str,
from: &str,
to: &str,
) -> Result<(), ()>
pub fn declare_remap( &mut self, node_name: &str, namespace: &str, from: &str, to: &str, ) -> Result<(), ()>
Record one launch <remap from= to=/> rule for the node identified by
(node_name, namespace). Rules are matched in declaration order (first
wins) by Self::resolve_entity_name_for. Errors when a string
overflows its slot or the table is at [MAX_REMAPS] — callers surface
this rather than silently dropping a routing rule.
Sourcepub fn resolve_entity_name_for(
&self,
node_name: &str,
namespace: &str,
source: &str,
) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
pub fn resolve_entity_name_for( &self, node_name: &str, namespace: &str, source: &str, ) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
Resolve a source-level entity name for the node identified by
(node_name, namespace): ROS 2 name expansion (~/relative → FQN)
plus this node’s declared remap rules (exact-FQN match, first rule
wins). Nodes with no rules still get expansion. Errors on an
unexpandable name (see crate::names::expand_name).
Sourcepub fn resolve_entity_name(
&self,
source: &str,
) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
pub fn resolve_entity_name( &self, source: &str, ) -> Result<String<nros_node::::names::ResolvedName::{constant#0}>, ()>
Self::resolve_entity_name_for against the executor’s CURRENT node
identity (set_node_identity) — the nros-c registration sites set that
identity per node immediately before registering each entity.
Sourcepub fn bind_node_name_sched(
&mut self,
name: &str,
namespace: &str,
sc: SchedContextId,
)
pub fn bind_node_name_sched( &mut self, name: &str, namespace: &str, sc: SchedContextId, )
Seed a config-resolved tier binding by (name, namespace) before the
node is built. NodeBuilder::build consults this table when no
explicit .sched() override is given — the table entry then wins over
the SchedContextId(0) default (precedence: explicit > table > 0).
Call BEFORE node_builder(name).build(). An existing entry for the
same (name, namespace) key is overwritten (last-write wins). Overflow
past MAX_NODES is silently ignored. An empty namespace is normalised
to "/" to match what NodeBuilder::build computes for a root-NS node.
Sourcepub fn bind_group_sched(
&mut self,
name: &str,
namespace: &str,
group: &str,
sc: SchedContextId,
)
pub fn bind_group_sched( &mut self, name: &str, namespace: &str, group: &str, sc: SchedContextId, )
Seed a config-resolved tier binding by (name, namespace, group) before
entities are registered. apply_node_default_sched consults this table
first (group table > node default > SchedContextId(0)).
Call BEFORE entity creation. An existing entry for the same
(name, namespace, group) key is overwritten (last-write wins). Overflow
past MAX_CBS is silently ignored. An empty namespace is normalised to
"/" to match NodeBuilder::build. Mirror of bind_node_name_sched.
Sourcepub fn default_sched_context_id(&self) -> SchedContextId
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.
Sourcepub fn create_sched_context(
&mut self,
sc: SchedContext,
) -> Result<SchedContextId, NodeError>
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.
Sourcepub fn epoch_now_us(&self) -> Option<u64>
pub fn epoch_now_us(&self) -> Option<u64>
RFC-0052 W3b.2 — wall-clock µs since the UNIX epoch, when this
target has an epoch source (config epoch_us, defaulted from
SystemTime on hosted configs). None = no wall clock; age
monitors must not have been baked (the emitter refuses).
Sourcepub fn set_monitor_table(&mut self, table: &'static [MonitorSpec])
pub fn set_monitor_table(&mut self, table: &'static [MonitorSpec])
RFC-0052 W3b.4 — install the baked contract-monitor table. Call
BEFORE entity creation so create_publisher can attach each
contracted endpoint’s counter cell. Mirrors set_qos_overrides:
&'static, codegen-baked, empty by default.
Sourcepub fn monitor_table(&self) -> &'static [MonitorSpec]
pub fn monitor_table(&self) -> &'static [MonitorSpec]
The installed monitor table (empty unless the entry set one).
Sourcepub fn set_age_table(&mut self, table: &'static [AgeMonitorSpec])
pub fn set_age_table(&mut self, table: &'static [AgeMonitorSpec])
W3b.5 — install the baked subscriber age-contract table. Call
BEFORE entity creation so create_subscription can attach each
contracted endpoint’s age cell (needs an epoch source — see
ExecutorConfig::epoch_us; without one the take path records
nothing and age monitors stay silent).
Sourcepub fn age_table(&self) -> &'static [AgeMonitorSpec]
pub fn age_table(&self) -> &'static [AgeMonitorSpec]
The installed age table (empty unless the entry set one).
Sourcepub fn set_epoch_clock(&mut self, epoch_us: fn() -> u64)
pub fn set_epoch_clock(&mut self, epoch_us: fn() -> u64)
W3b.5 — override the wall-clock (epoch µs) source age monitors take
message stamps against. Hosted builds default to SystemTime; a
board with a synced RTC installs its own here (or via
ExecutorConfig::epoch_us). Call BEFORE entity creation — the age
hook captures this at create_subscription time.
Sourcepub fn release_jitter(&self) -> (u64, u32, u32)
pub fn release_jitter(&self) -> (u64, u32, u32)
Release-jitter statistics from the spin loop: worst lateness in microseconds, the number of wakes that were already late, and the number of wakes total.
The maximum is the figure of merit, and the ratio is what tells the two failures apart: one late wake in ten thousand is a glitch, ten thousand in ten thousand means the period cannot be met at all.
Zero on a build with no clock – spin_period refuses to run at all
there (issue 0709), so there is nothing to have measured.
Sourcepub fn clear_release_jitter_stats(&mut self)
pub fn clear_release_jitter_stats(&mut self)
Reset the release-jitter statistics. For monitoring code that logs and clears per window, so a single early outlier does not pin the maximum for the life of the process.
pub fn set_fault_handler(&mut self, f: fn(&Violation))
Sourcepub fn set_report_violations(&mut self, enabled: bool)
pub fn set_report_violations(&mut self, enabled: bool)
Issue #514 — whether the executor logs each violation as it is detected (the default).
Turn this off in an application that reports violations its own
way via Self::drain_violations; the ring is unaffected
either way.
Sourcepub fn violations_dropped(&self) -> u32
pub fn violations_dropped(&self) -> u32
Issue #514 — violations discarded because the ring was full.
Non-zero means the image produced faults faster than they were reported, so the reported set is a prefix, not the whole story.
Sourcepub fn drain_violations(&mut self, f: impl FnMut(&Violation))
pub fn drain_violations(&mut self, f: impl FnMut(&Violation))
RFC-0052 W3b.4 — drain pending contract violations (rate rule for
now; age/latency land with W3b.5). The entry glue calls this after
spin_once and feeds each entry to the nros-diagnostics
reporter. Draining clears the ring.
Sourcepub fn set_min_stack_headroom_bytes(&mut self, bytes: usize)
pub fn set_min_stack_headroom_bytes(&mut self, bytes: usize)
Issue #505 — report activations dropped by
TimerOverrunPolicy::Skip
since the last check.
Unlike the rate/age/latency rules this needs no baked spec table:
every periodic timer counts its own overruns, and a dropped
activation is a contract failure for any declared period. It runs
on the same tick so violations land in the same ring the entry
glue drains.
Declare the minimum stack headroom this executor’s thread must keep,
in bytes. 0 (the default) disables the stack-headroom-runtime
rule.
Set by the entry that spawned the thread, because it is the only
party that knows what stack it handed over: the executor never sees
stack_bytes, and no portable query returns a task’s total stack, so
neither an absolute floor nor a percentage can be inferred here.
Sourcepub fn set_default_sched_context(&mut self, sc: SchedContext)
pub fn set_default_sched_context(&mut self, sc: SchedContext)
RFC-0052 / phase-296 W3a — replace the DEFAULT scheduling context (slot 0, the SC every unbound callback dispatches through).
The run_tiers model runs one Executor per tier, so a tier-wide
scheduling policy ([tiers.<t>] class/budget_us/period_us and the
TT window) is exactly “this executor’s default SC”. Boards call
this once, before entity creation; explicit per-handle/per-group
bindings still take precedence (they never resolve to slot 0).
Sporadic-class SCs get the same sibling SporadicState the
create_sched_context path builds, so budget refill/exhaustion
applies to the default queue too.
Sourcepub fn bind_handle_to_sched_context(
&mut self,
handle: HandleId,
sc_id: SchedContextId,
) -> Result<(), NodeError>
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.
Sourcepub fn register_time_triggered_dispatcher(&mut self, major_frame_us: u32)
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.
Sourcepub fn apply_time_triggered_schedule<const N: usize>(
&mut self,
schedule: &TimeTriggeredSchedule<N>,
) -> Result<[SchedContextId; N], TimeTriggeredScheduleError>
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).
Sourcepub fn register_sporadic_timer(
&mut self,
sc_id: SchedContextId,
timer: OpaqueTimerHandle,
) -> Result<Arc<AtomicSporadicState>, NodeError>
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).
Sourcepub fn sched_context(&self, sc_id: SchedContextId) -> Option<&SchedContext>
pub fn sched_context(&self, sc_id: SchedContextId) -> Option<&SchedContext>
Inspect a registered scheduling context. Phase 110.B.
Sourcepub fn node_builder<'a, 'cfg>(
&'a mut self,
name: &'cfg str,
) -> NodeBuilder<'a, 'cfg, 's>
pub fn node_builder<'a, 'cfg>( &'a mut self, name: &'cfg str, ) -> NodeBuilder<'a, 'cfg, 's>
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.
Sourcepub fn nodes(&self) -> &[NodeRecord]
pub fn nodes(&self) -> &[NodeRecord]
Return the Node table — Phase 104.C.2 read accessor.
Sourcepub fn node(&self, id: NodeId) -> Option<&NodeRecord>
pub fn node(&self, id: NodeId) -> Option<&NodeRecord>
Borrow a Node’s metadata by id, returning None if the id
is out of range.
Sourcepub fn node_mut(&mut self, id: NodeId) -> NodeCtx<'_, 's>
pub fn node_mut(&mut self, id: NodeId) -> NodeCtx<'_, 's>
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).
Sourcepub fn set_node_qos_overrides(
&mut self,
node_id: NodeId,
overrides: &'static [(&'static str, u8, u8, u32)],
)
pub fn set_node_qos_overrides( &mut self, node_id: NodeId, overrides: &'static [(&'static str, u8, u8, u32)], )
Issue #52 — install the baked QoS-override table on one node. Every
entity created on it AFTERWARDS folds the matching (topic, role)
entries into its QoS, before the backend-compat check — so an override
the active RMW cannot honour still errors loudly rather than silently
downgrading.
Called by the generated entry (nros::main! → the register seam) right
after the node is created and before the component declares entities.
Unknown node_id is a no-op.
Sourcepub fn node_session_mut(&mut self, node_id: NodeId) -> Option<&mut CffiSession>
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.
Sourcepub fn create_publisher_on<M>(
&mut self,
node_id: NodeId,
topic_name: &str,
qos: QoSProfile,
) -> Result<EmbeddedPublisher<M>, NodeError>where
M: MessageForRmw,
pub fn create_publisher_on<M>(
&mut self,
node_id: NodeId,
topic_name: &str,
qos: QoSProfile,
) -> Result<EmbeddedPublisher<M>, NodeError>where
M: MessageForRmw,
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.
Sourcepub fn create_publisher_raw_on(
&mut self,
node_id: NodeId,
topic_name: &str,
type_name: &str,
type_hash: &str,
qos: QoSProfile,
) -> Result<EmbeddedRawPublisher, NodeError>
pub fn create_publisher_raw_on( &mut self, node_id: NodeId, topic_name: &str, type_name: &str, type_hash: &str, qos: QoSProfile, ) -> 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.
Sourcepub fn with_node_try<R>(
&mut self,
id: NodeId,
f: impl FnOnce(&mut NodeHandle<'_>) -> Result<R, NodeError>,
) -> Result<R, NodeError>
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(...))?;pub fn with_node<R>( &mut self, id: NodeId, f: impl FnOnce(&mut NodeHandle<'_>) -> R, ) -> Result<R, NodeError>
Sourcepub fn node_id_by_name(&self, name: &str, namespace: &str) -> Option<NodeId>
pub fn node_id_by_name(&self, name: &str, namespace: &str) -> Option<NodeId>
Find a registered executor node by final name and namespace.
Sourcepub fn create_node(&mut self, name: &str) -> Result<NodeHandle<'_>, NodeError>
pub fn create_node(&mut self, name: &str) -> Result<NodeHandle<'_>, NodeError>
Create a node on this executor.
Registers the node in the executor’s table, deduplicating on
(name, namespace) — phase-376 W5/B1.
Until 2026-08-24 this path registered NOTHING. It built a NodeHandle
and returned it, so node_id_by_name could not find a node the caller
had just created, and two calls with one name handed out two handles the
executor had never heard of. create_node_on_with_domain had the dedup
(phase-267 added it there when N bridge endpoints overflowed the table);
the plain path never got it.
That is a prerequisite for the create_node vtable slot, whose contract
is that the runtime calls it ONCE per distinct (name, namespace):
without a registry to check, the runtime would call it once per
create_node and every backend would need its own dedup — which is the
registry the slot exists to delete (zenoh’s ensure_node_liveliness
linear-scans per_node_liveliness for exactly this reason).
The table is bounded by MAX_NODES (NROS_EXECUTOR_MAX_NODES, default
4), so a caller creating a fifth DISTINCT node now gets
NodeError::NodeTableFull where it previously got a handle. That is the
bound doing its job: a node the executor does not know about cannot
carry a sched context, a QoS override, or a graph identity. Repeated
calls with the SAME name are free.
Sourcepub fn create_node_on(
&mut self,
name: &str,
rmw: &str,
) -> Result<NodeHandle<'_>, NodeError>
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.
Sourcepub fn create_node_on_with_domain(
&mut self,
name: &str,
rmw: &str,
domain_id: Option<u32>,
locator: Option<&str>,
) -> Result<NodeHandle<'_>, NodeError>
pub fn create_node_on_with_domain( &mut self, name: &str, rmw: &str, domain_id: Option<u32>, locator: Option<&str>, ) -> Result<NodeHandle<'_>, NodeError>
Like create_node_on but pins the extra
session’s domain id. Required for a multi-domain config-driven bridge:
an extra RMW session’s participant domain follows the node builder’s
domain_id (resolve_session_slot → domain_id.unwrap_or(0)), NOT the
SessionSpec’s — so without this an egress on a non-zero domain silently
opens on domain 0 and never matches its receiver (phase-267 issue 0109).
None domain preserves the legacy domain-0 default. locator pins the
extra session’s address — REQUIRED for an agent-based backend (xrce: the
Micro-XRCE-DDS Agent addr) whose session can’t be opened locator-less;
None keeps the rmw-default (cyclonedds is domain-discovered, no locator).
Sourcepub fn close(&mut self) -> Result<(), NodeError>
pub fn close(&mut self) -> Result<(), NodeError>
Close the underlying session, running the shutdown hooks around it.
Issue 0790. The order is the feature:
- every registered PRE-shutdown hook, while the session is still open and every entity still works — this is where a node publishes a final state, answers a last request, parks an actuator or releases a bus;
- the session close;
- every registered ON-shutdown hook.
A hook runs EXACTLY ONCE: each phase table is emptied before its first
hook is invoked, so a second close() — or the Drop sweep after one
— finds nothing left to run. Step 2 runs even
if a pre-shutdown hook was registered and step 3 even if the close
failed — a hook cannot strand the session, and a dead session must not
strand the hooks.
§This is a CLEAN-STOP facility and nothing more
A watchdog reset, a hard fault or a panic does not come through here, so these hooks do not run then. Nothing in a fixed static table can promise otherwise, and an API that implied it would be worse than none: hardware that must be safe across an abnormal stop needs a hardware answer (a pull-down, a watchdog-driven output disable), not a callback.
Sourcepub unsafe fn add_pre_shutdown_callback(
&mut self,
callback: unsafe extern "C" fn(*mut c_void),
context: *mut c_void,
) -> Result<ShutdownCallbackHandle, NodeError>
pub unsafe fn add_pre_shutdown_callback( &mut self, callback: unsafe extern "C" fn(*mut c_void), context: *mut c_void, ) -> Result<ShutdownCallbackHandle, NodeError>
Register a hook to run BEFORE the session is closed (issue 0790).
rclcpp’s Context::add_pre_shutdown_callback. Returns the handle
Self::remove_pre_shutdown_callback takes, or
NodeError::ShutdownCallbacksFull when the phase table is full —
raise NROS_EXECUTOR_MAX_SHUTDOWN_CBS (default 2) at build time.
§Safety
callback must be safe to invoke exactly once with context, and
context must stay valid until the hook runs or is removed. The hook
runs on whichever task calls Self::close (or drops the executor),
which is not necessarily the task that registered it.
Sourcepub unsafe fn add_on_shutdown_callback(
&mut self,
callback: unsafe extern "C" fn(*mut c_void),
context: *mut c_void,
) -> Result<ShutdownCallbackHandle, NodeError>
pub unsafe fn add_on_shutdown_callback( &mut self, callback: unsafe extern "C" fn(*mut c_void), context: *mut c_void, ) -> Result<ShutdownCallbackHandle, NodeError>
Register a hook to run AFTER the session is closed (issue 0790).
rclcpp’s Context::add_on_shutdown_callback / rclcpp::on_shutdown.
Entities are gone by the time it runs, so anything that needs the wire
belongs in Self::add_pre_shutdown_callback instead.
§Safety
Same contract as Self::add_pre_shutdown_callback.
Sourcepub fn remove_pre_shutdown_callback(
&mut self,
handle: ShutdownCallbackHandle,
) -> bool
pub fn remove_pre_shutdown_callback( &mut self, handle: ShutdownCallbackHandle, ) -> bool
Remove a pre-shutdown hook. true if handle named a live one.
rclcpp’s Context::remove_pre_shutdown_callback, and bool for the
same reason: “it was not there” is an ordinary answer (the hook may
already have run), not an error. A handle issued for the OTHER phase
returns false and removes nothing — that is what the phase tag in
ShutdownCallbackHandle buys.
Sourcepub fn remove_on_shutdown_callback(
&mut self,
handle: ShutdownCallbackHandle,
) -> bool
pub fn remove_on_shutdown_callback( &mut self, handle: ShutdownCallbackHandle, ) -> bool
Remove an on-shutdown hook. See Self::remove_pre_shutdown_callback.
Sourcepub fn shutdown_callback_count(&self, phase: ShutdownPhase) -> usize
pub fn shutdown_callback_count(&self, phase: ShutdownPhase) -> usize
How many hooks are currently registered for phase. Diagnostic /
test surface, and what a “did my registration land?” assertion reads.
Sourcepub 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<(), ()>
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).
Sourcepub fn dispatch_slot_count(&self) -> usize
pub fn dispatch_slot_count(&self) -> usize
Phase 216 follow-up — current registered dispatch-slot count. Diagnostic / test surface.
Sourcepub 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<(), ()>
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.
Sourcepub fn component_slot_count(&self) -> usize
pub fn component_slot_count(&self) -> usize
Phase 258 (Track 2, 2a) — current enrolled component-slot count. Diagnostic / test surface.
Sourcepub fn enrolled_component_states(&self) -> impl Iterator<Item = *mut c_void>
pub fn enrolled_component_states(&self) -> impl Iterator<Item = *mut c_void>
issue #140 — the enrolled components’ opaque state pointers, in enroll
order. Each is the leaked Arc<ComponentCell> enroll_component was
handed (see [ComponentSlot::state]); the nros layer re-borrows them
to fold per-component dispatch counters into
observed_callback_counts — install-seam components
(register_node_borrowed) live ONLY here, not in
ExecutorNodeRuntime::components, so the hosted-spin counts read zero
without this surface.
Sourcepub fn dispatch_callback(&mut self, cb_id: &str, ctx: *mut c_void)
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/api/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().
Sourcepub fn session(&self) -> &CffiSession
pub fn session(&self) -> &CffiSession
Get a reference to the underlying session.
Sourcepub fn session_mut(&mut self) -> &mut CffiSession
pub fn session_mut(&mut self) -> &mut CffiSession
Get a mutable reference to the underlying session.
Sourcepub fn ping(&mut self, timeout_ms: i32) -> Result<(), NodeError>
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.
Sourcepub fn get_node_names(
&mut self,
visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool,
) -> Result<(), NodeError>
pub fn get_node_names( &mut self, visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — every node on the graph, with its namespace.
visit(name, namespace, enclave) is called once per node and returns
false to stop early. A VISITOR rather than a returned collection
because there is no allocator at this layer and the graph has no bound
the caller can know; peak extra memory is one entry. Every string is
BORROWED for the duration of the call.
enclave is None where the backend does not track one, which is what
lets one call answer both rmw_get_node_names and
rmw_get_node_names_with_enclaves.
This reports what has already been DISCOVERED, and never blocks. The
first call after startup legitimately sees a partial graph — the backend
keeps a standing query fed by spin, so the view fills in over
successive calls. Code that waits for a peer should poll, not call once
and conclude. An empty result is “nobody seen yet”, never “nobody
exists”.
Err(Transport(Unsupported)) from a backend with no graph — distinct
from an empty graph, deliberately.
Sourcepub fn get_topic_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_topic_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — every topic on the graph, with the types on it.
visit(topic_name, types) is called once per distinct TOPIC — a topic
carrying two types is one call with two entries, not two calls. types
may legitimately be empty on a partially discovered graph: reporting the
name without a type beats dropping it.
Same discovery caveat as Self::get_node_names.
Sourcepub fn get_service_names_and_types(
&mut self,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_service_names_and_types( &mut self, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — every service on the graph, with its types.
As Self::get_topic_names_and_types, over servers and clients.
Sourcepub fn count_publishers(&mut self, topic_name: &str) -> Result<usize, NodeError>
pub fn count_publishers(&mut self, topic_name: &str) -> Result<usize, NodeError>
phase-381 W4 — how many publishers are visible on topic_name.
topic_name is a ROS name ("/chatter"). A count reflects what has
been DISCOVERED, so it can be low right after startup and is never a
proof of absence — see Self::get_node_names.
Sourcepub fn count_subscribers(
&mut self,
topic_name: &str,
) -> Result<usize, NodeError>
pub fn count_subscribers( &mut self, topic_name: &str, ) -> Result<usize, NodeError>
phase-381 W4 — how many subscribers are visible on topic_name.
See Self::count_publishers for the caveats.
Sourcepub fn get_publisher_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_publisher_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what one named node PUBLISHES, with the types.
visit(topic_name, types) per distinct topic. A node the graph has not
discovered yields no visits, which is not an error — see
Self::get_node_names for why an empty answer means “not seen yet”.
Sourcepub fn get_subscription_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_subscription_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what one named node SUBSCRIBES to, with the types.
subscription, not subscriber — this is rclrs’s spelling
(get_subscription_names_and_types_by_node), and the Rust surface takes
its vocabulary from rclrs so a user porting Rust ROS 2 code types what
they already know. The C surface says subscriber because rcl does, and
the vtable slot says subscriber because upstream rmw does. Three
layers, three upstreams, one word each — not drift. Issue 0788 owns the
wider verb sweep.
Sourcepub fn get_service_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_service_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what services one named node SERVES, with the types.
Sourcepub fn get_client_names_and_types_by_node(
&mut self,
node_name: &str,
node_namespace: &str,
visit: &mut dyn FnMut(&str, &[&str]) -> bool,
) -> Result<(), NodeError>
pub fn get_client_names_and_types_by_node( &mut self, node_name: &str, node_namespace: &str, visit: &mut dyn FnMut(&str, &[&str]) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — what services one named node CALLS, with the types.
Sourcepub fn get_publishers_info_by_topic(
&mut self,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), NodeError>
pub fn get_publishers_info_by_topic( &mut self, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — the publishers on topic_name, one visit each.
Each GraphEndpointInfo BORROWS its strings for the duration of the
visit; copy anything kept.
It carries no QoS. The granted profile is what would answer “why is nothing arriving”, and no backend can read one back yet — reporting the remote’s DECLARED profile instead would be a confident wrong answer, so the field is absent rather than misleading.
Sourcepub fn get_subscriptions_info_by_topic(
&mut self,
topic_name: &str,
visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool,
) -> Result<(), NodeError>
pub fn get_subscriptions_info_by_topic( &mut self, topic_name: &str, visit: &mut dyn FnMut(&GraphEndpointInfo<'_>) -> bool, ) -> Result<(), NodeError>
phase-381 W4 — the subscriptions on topic_name, one visit each.
See Self::get_publishers_info_by_topic.
Sourcepub unsafe fn action_client_core_mut(
&mut self,
entry_index: usize,
) -> Option<&mut ActionClientCore>
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.
Sourcepub unsafe fn service_client_entry_mut(
&mut self,
entry_index: usize,
) -> Option<&mut ServiceClientRawArenaEntry<nros_node::::executor::spin::{impl#14}::service_client_entry_mut::{constant#0}>>
pub unsafe fn service_client_entry_mut( &mut self, entry_index: usize, ) -> Option<&mut ServiceClientRawArenaEntry<nros_node::::executor::spin::{impl#14}::service_client_entry_mut::{constant#0}>>
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.
Sourcepub fn set_trigger(&mut self, trigger: Trigger)
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.
Sourcepub fn set_semantics(&mut self, semantics: ExecutorSemantics)
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.
Sourcepub fn set_invocation(&mut self, id: HandleId, mode: InvocationMode)
pub fn set_invocation(&mut self, id: HandleId, mode: InvocationMode)
Sourcepub fn arena_used(&self) -> usize
pub fn arena_used(&self) -> usize
Arena bytes claimed by registered entities so far.
EXACT, not a worst case: the arena is a bump allocator and every
arena_alloc charges size_of::<T>(), so nothing is reserved per slot.
ARENA_SIZE itself is derived the other way — every slot budgeted at
the ActionClient worst case — which is issue 0900, and this accessor is
how an image can find out what it actually needs.
Sourcepub fn arena_capacity(&self) -> usize
pub fn arena_capacity(&self) -> usize
Total arena bytes this executor was given.
The arena is a borrowed slice; whether its storage is stack or .bss is
the caller’s choice, not a property of this type (see
report_arena_headroom).
Sourcepub 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: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>
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: QoSProfile, callback: F, ) -> Result<HandleId, NodeError>
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).
Sourcepub fn add_arena_subscription_callback<F, const RX_BUF: usize>(
&mut self,
handle: <CffiSession as Session>::SubscriptionHandle,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>
pub fn add_arena_subscription_callback<F, const RX_BUF: usize>( &mut self, handle: <CffiSession as Session>::SubscriptionHandle, qos: QoSProfile, callback: F, ) -> Result<HandleId, NodeError>
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_subscriptiononself.session_mut()with aTopicInfo. Thenode_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 viaSelf::session_mutand call its backend-specific create method, then hand the handle here.nros-px4::uorb::create_subscription_with_callbackis 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].
Sourcepub fn register_service<Svc, F>(
&mut self,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
pub fn register_service<Svc, F>(
&mut self,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Register a service callback with the default buffer size.
The callback is stored in the arena and invoked during spin_once().
Sourcepub 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 as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
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 as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Register a service callback with custom request/reply buffer sizes.
Sourcepub fn register_service_sized_on<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
&mut self,
node_id: NodeId,
service_name: &str,
qos: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
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: QoSProfile,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Phase 104.C.3.3.a — Node-aware variant of
register_service_sized.
Sourcepub fn register_service_on<Svc, F>(
&mut self,
node_id: NodeId,
service_name: &str,
callback: F,
) -> Result<HandleId, NodeError>where
Svc: RosService + 'static,
<Svc as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
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 as RosService>::Request: MessageForRmw,
<Svc as RosService>::Reply: MessageForRmw,
F: FnMut(&<Svc as RosService>::Request) -> <Svc as RosService>::Reply + 'static,
Phase 104.C.3.3.a — Node-aware variant of
register_service.
Sourcepub fn register_timer<F>(
&mut self,
period: TimerDuration,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
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.
Sourcepub fn register_timer_on_clock<F>(
&mut self,
period: TimerDuration,
source: TimerClockSource,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
pub fn register_timer_on_clock<F>(
&mut self,
period: TimerDuration,
source: TimerClockSource,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
Register a repeating timer driven by a CLOCK rather than by the spin
delta — phase-425 W4, the shape rclcpp spells
create_timer(node, clock, period, cb).
register_timer is the wall timer: it consumes
the executor’s monotonic spin delta and no simulator can slow it down.
This one reads source on every poll and advances by the difference, so
a TimerClockSource::Ros timer follows /clock — it stops while the
simulator is paused, halves with a bag replayed at 0.5x, and restarts
its period on a backwards jump instead of stalling for the length of it.
With no /clock source installed, a Ros timer reads system time and
behaves like a wall timer with NTP steps, which is the same fallback
rclcpp::Clock has: a node written for simulation still runs standalone.
Sourcepub fn register_timer_oneshot<F>(
&mut self,
delay: TimerDuration,
callback: F,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
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.
Sourcepub fn register_timer_on<F>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
callback: F,
group: Option<&str>,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
pub fn register_timer_on<F>(
&mut self,
node_id: Option<NodeId>,
period: TimerDuration,
callback: F,
group: Option<&str>,
) -> Result<HandleId, NodeError>where
F: FnMut() + 'static,
Phase 273 (RFC-0047) — register a repeating timer callback bound to a
specific node and optional callback group. The group name is threaded to
apply_node_default_sched so the seeded group_sched_table assigns
the timer’s callback to the group’s SchedContext. When group is
None the node’s default_sched applies (phase-272 behavior).
This is the executor-level primitive called by the Rust _in API
(NodeCtx::create_timer_in) and the C/C++ group-aware timer FFI.
Sourcepub 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: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut c_void),
context: *mut c_void,
group: Option<&str>,
rx_buffer_hint: usize,
) -> Result<HandleId, NodeError>
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: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut c_void), context: *mut c_void, group: Option<&str>, rx_buffer_hint: usize, ) -> 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.
Sourcepub 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: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *const u8, usize, *mut c_void),
context: *mut c_void,
rx_buffer_hint: usize,
) -> Result<HandleId, NodeError>
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: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *const u8, usize, *mut c_void), context: *mut c_void, rx_buffer_hint: usize, ) -> 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_add_subscription_raw_with_info. Flat per-entry
payload + attachment buffers (cap RAW_INFO_ATT_CAP);
one sample per spin_once.
Sourcepub fn register_service_raw(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
) -> Result<HandleId, NodeError>
pub fn register_service_raw( &mut self, service_name: &str, service_type: &str, service_hash: &str, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, 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.
Sourcepub 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: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
) -> Result<HandleId, NodeError>
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: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, 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).
Sourcepub 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: QoSProfile,
callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool,
context: *mut c_void,
) -> Result<HandleId, NodeError>
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: QoSProfile, callback: unsafe extern "C" fn(*const u8, usize, *mut u8, usize, *mut usize, *mut c_void) -> bool, context: *mut c_void, ) -> Result<HandleId, NodeError>
Phase 104.C.3.3.a — Node-aware variant of
[register_service_raw_sized]. C-FFI path.
Sourcepub fn register_service_client_raw(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<HandleId, NodeError>
pub fn register_service_client_raw( &mut self, service_name: &str, service_type: &str, service_hash: &str, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, 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 take_response_raw
and fires the registered callback when the response arrives.
Used by the C API thin wrapper — see Phase 82.
Sourcepub fn register_service_client_raw_sized<const REPLY_BUF: usize>(
&mut self,
service_name: &str,
service_type: &str,
service_hash: &str,
qos: QoSProfile,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<HandleId, NodeError>
pub fn register_service_client_raw_sized<const REPLY_BUF: usize>( &mut self, service_name: &str, service_type: &str, service_hash: &str, qos: QoSProfile, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, 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 QoSProfile::services_default via the convenience
wrapper.
Sourcepub 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: QoSProfile,
callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<HandleId, NodeError>
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: QoSProfile, callback: Option<unsafe extern "C" fn(*const u8, usize, *mut c_void)>, 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.
Sourcepub fn register_guard_condition<F>(
&mut self,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
pub fn register_guard_condition<F>(
&mut self,
callback: F,
) -> Result<(HandleId, GuardCondition), NodeError>where
F: FnMut() + 'static,
Register a guard condition with a callback.
Returns both the HandleId for trigger configuration and a
GuardCondition for triggering from other threads.
Sourcepub fn cancel_timer(&mut self, id: HandleId) -> Result<(), NodeError>
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().
Sourcepub fn reset_timer(&mut self, id: HandleId) -> Result<(), NodeError>
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.
Sourcepub fn timer_is_canceled(&self, id: HandleId) -> bool
pub fn timer_is_canceled(&self, id: HandleId) -> bool
Check if a timer is cancelled.
Sourcepub fn timer_period_ms(&self, id: HandleId) -> Option<u64>
pub fn timer_period_ms(&self, id: HandleId) -> Option<u64>
Get the period of a timer in milliseconds (truncated — see
Self::timer_period_us), or None if the handle is not a
valid timer.
Sourcepub fn timer_period_us(&self, id: HandleId) -> Option<u64>
pub fn timer_period_us(&self, id: HandleId) -> Option<u64>
Get the period of a timer in microseconds, or None if the
handle is not a valid timer.
Sourcepub fn set_timer_overrun_policy(
&mut self,
id: HandleId,
policy: TimerOverrunPolicy,
) -> Result<(), NodeError>
pub fn set_timer_overrun_policy( &mut self, id: HandleId, policy: TimerOverrunPolicy, ) -> Result<(), NodeError>
Set a timer’s overrun policy (issue #505). Timers default to
TimerOverrunPolicy::Skip; switch to
TimerOverrunPolicy::CatchUp for timers whose every activation
is a unit of work that must not be lost.
Sourcepub fn timer_overruns(&self, id: HandleId) -> Option<u32>
pub fn timer_overruns(&self, id: HandleId) -> Option<u32>
Periods this timer missed and dropped under
TimerOverrunPolicy::Skip (issue #505), or None if the handle
is not a valid timer.
Monotonic and saturating. A growing count is the on-target signal that a tier is not keeping up with its declared cadence — the symptom an external observer would otherwise have to infer by differencing timestamps.
Sourcepub fn session_io_failures(&self) -> u32
pub fn session_io_failures(&self) -> u32
Drive I/O and dispatch registered callbacks once.
Three-phase execution:
- Readiness scan — query each handle’s
has_data(). - Trigger evaluation — check if the executor-level trigger passes.
- 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 ati32::MAXms (~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.
Consecutive drive_io failures on the primary session (issue 0324).
0 means the last drive succeeded. A value that keeps climbing across
spins is a session that is no longer doing I/O — router gone, lease
expired, socket closed — which otherwise presents as a node that spins
Ok(()) forever while publishing nowhere and firing no callbacks.
Transient failures happen, so a single non-zero reading is not a fault; a threshold (say, “more than a few consecutive spins”) is the useful signal. Extra (bridge / multi-domain) sessions are best-effort and are deliberately NOT counted here.
Sourcepub fn session_io_healthy(&self) -> bool
pub fn session_io_healthy(&self) -> bool
Whether the primary session drove I/O successfully on the last spin.
Convenience over Self::session_io_failures for the common
“is my transport alive?” check.
pub fn spin_once(&mut self, timeout: Duration) -> SpinOnceResult
Sourcepub fn spin(&mut self, timeout: Duration) -> !
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 returnsSourcepub fn spin_default(&mut self) -> !
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.
Sourcepub async fn spin_async(&mut self) -> !
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.take() { break r; }
}Sourcepub fn spin_one_period(
&mut self,
period_ms: u64,
elapsed_ms: u64,
) -> SpinPeriodPollingResult
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 millisecondselapsed_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<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn spin_blocking(&mut self, opts: SpinOptions) -> Result<(), NodeError>
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_nextis true (single iteration)
§Example
// Spin forever until halted
executor.spin_blocking(SpinOptions::default())?;
// Spin with 5-second timeout
executor.spin_blocking(SpinOptions::new().timeout(core::time::Duration::from_secs(5)))?;
// Single iteration
executor.spin_blocking(SpinOptions::spin_once())?;Sourcepub fn spin_one_period_timed(&mut self, period: Duration) -> SpinPeriodResult
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 = core::time::Duration::from_millis(10);
let result = executor.spin_one_period_timed(period);
if result.overrun {
log::warn!("Period overrun: {:?}", result.elapsed);
}Sourcepub fn spin_period(&mut self, period: Duration) -> Result<(), NodeError>
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(core::time::Duration::from_millis(10))?;Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub unsafe fn open_threaded(
self,
policy: SchedPolicy,
apply_policy: fn(SchedPolicy) -> Result<(), SchedError>,
spin_period: Duration,
) -> ThreadHandlewhere
's: 'static,
pub unsafe fn open_threaded(
self,
policy: SchedPolicy,
apply_policy: fn(SchedPolicy) -> Result<(), SchedError>,
spin_period: Duration,
) -> ThreadHandlewhere
's: 'static,
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.
Sourcepub fn halt(&self)
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.
Sourcepub fn wake(&self)
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§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn halt_flag(&self) -> Arc<AtomicBool>
pub fn halt_flag(&self) -> Arc<AtomicBool>
Sourcepub fn wake_handle(&self) -> Arc<AtomicBool>
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<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn register_action_server<A, GoalF, CancelF>(
&mut self,
action_name: &str,
goal_callback: GoalF,
cancel_callback: CancelF,
) -> Result<ActionServerHandle<A>, NodeError>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalId, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalId, GoalStatus) -> CancelResponse + 'static,
pub fn register_action_server<A, GoalF, CancelF>(
&mut self,
action_name: &str,
goal_callback: GoalF,
cancel_callback: CancelF,
) -> Result<ActionServerHandle<A>, NodeError>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalId, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalId, GoalStatus) -> CancelResponse + 'static,
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.
Sourcepub 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>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalId, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalId, GoalStatus) -> CancelResponse + 'static,
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>where
A: RosAction + 'static,
<A as RosAction>::Goal: Clone + MessageForRmw,
<A as RosAction>::Result: Clone + Default + MessageForRmw,
<A as RosAction>::Feedback: MessageForRmw,
<A as RosAction>::SendGoalRequest: MessageForRmw,
<A as RosAction>::SendGoalResponse: MessageForRmw,
<A as RosAction>::GetResultRequest: MessageForRmw,
<A as RosAction>::GetResultResponse: MessageForRmw,
<A as RosAction>::FeedbackMessage: MessageForRmw,
GoalF: FnMut(&GoalId, &<A as RosAction>::Goal) -> GoalResponse + 'static,
CancelF: FnMut(&GoalId, GoalStatus) -> CancelResponse + 'static,
Register an action server with custom buffer sizes.
Source§impl<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn register_action_server_raw(
&mut self,
spec: RawActionServerSpec<'_>,
) -> Result<ActionServerRawHandle, NodeError>
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.
Sourcepub 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>
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<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub fn register_action_client_raw(
&mut self,
spec: RawActionClientSpec<'_>,
) -> Result<ActionClientRawHandle, NodeError>
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/rejectedfeedback_callback— called when feedback is receivedresult_callback— called when result is receivedcontext— opaque pointer passed to all callbacks
Sourcepub 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>
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<'s> Executor<'s>
impl<'s> Executor<'s>
Sourcepub 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<unsafe extern "C" fn(*const GoalId, bool, *mut c_void)>,
feedback_callback: Option<unsafe extern "C" fn(*const GoalId, *const u8, usize, *mut c_void)>,
result_callback: Option<unsafe extern "C" fn(*const GoalId, GoalStatus, *const u8, usize, *mut c_void)>,
context: *mut c_void,
) -> Result<ActionClientRawHandle, NodeError>
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<unsafe extern "C" fn(*const GoalId, bool, *mut c_void)>, feedback_callback: Option<unsafe extern "C" fn(*const GoalId, *const u8, usize, *mut c_void)>, result_callback: Option<unsafe extern "C" fn(*const GoalId, GoalStatus, *const u8, usize, *mut c_void)>, 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 DeclaredNodeRuntime for Executor<'static>
Available on crate feature rmw-cffi only.
impl DeclaredNodeRuntime for Executor<'static>
rmw-cffi only.Source§type NodeHandle = NodeId
type NodeHandle = NodeId
Source§fn build_component_node(
&mut self,
_id: NodeId<'_>,
options: NodeOptions<'_>,
) -> NodeResult<Self::NodeHandle>
fn build_component_node( &mut self, _id: NodeId<'_>, options: NodeOptions<'_>, ) -> NodeResult<Self::NodeHandle>
impl<'s> Send for Executor<'s>
alloc only.