pub struct ExecutorNodeRuntime { /* private fields */ }Expand description
Executor-backed component runtime.
Owns the Executor and one slot per registered component. The
register / spin lifecycle:
from_executorwraps an openExecutor.register_nodebuilds the component’sState, runsComponent::registeragainst the live executor, whose registration sink materialises nodes / pubs / subs / timers on the real executor, and wires each subscription + timer callback to dispatch intoExecutableNode::on_callbackwith the rightCallbackId.spin/spin_oncedrive the executor; between iterations every registered component’sExecutableNode::tickruns.
Implementations§
Source§impl ExecutorNodeRuntime
impl ExecutorNodeRuntime
Sourcepub fn from_executor(executor: Executor<'static>) -> Self
pub fn from_executor(executor: Executor<'static>) -> Self
Sourcepub unsafe fn new_in(
executor: Executor<'static>,
backing: &'static mut [MaybeUninit<u64>],
sizing: RuntimeSizing,
) -> Self
pub unsafe fn new_in( executor: Executor<'static>, backing: &'static mut [MaybeUninit<u64>], sizing: RuntimeSizing, ) -> Self
Wrap an already-built Executor over CALLER-SUPPLIED slot storage.
Size backing with crate::runtime_storage::RuntimeSizing::u64_len; a short one panics,
naming both sizes (fail-loud on every profile — a short backing is
silent corruption, the executor::storage::carve / issue #131 lesson).
§Safety
backing must be uniquely owned by this runtime for its whole life:
slots carved from it are handed out as &'static mut, so aliasing it
anywhere else is undefined behaviour.
Sourcepub fn entry_spin_budget(&self) -> Duration
pub fn entry_spin_budget(&self) -> Duration
phase-521 W6 (RFC-0113 D2) — the budget this runtime’s entry loop
passes to spin_once:
Executor::entry_spin_budget, or the 10 ms cadence while a component
registered here has a working tick (it runs once per spin).
Sourcepub unsafe fn executor_ptr(this: *mut Self) -> *mut Executor<'static>
pub unsafe fn executor_ptr(this: *mut Self) -> *mut Executor<'static>
phase-509 P10 – the executor’s address, through a raw runtime
pointer and without a reference: what a level runner takes the
executor’s lock through before it touches the runtime
(crate::levels::LevelCore).
§Safety
this points at a live runtime.
phase-509 P10 – spin_once on a runtime other
level workers share: the executor’s lock is taken first, the pass
releases it around each user callback
(Executor::spin_once_shared), and the components’ ticks run under
it. Issue 2020 – on a level image, only the boot level’s components
(and any registered before a level was set): each other level’s pass
ticks its own (crate::levels::LevelCore::spin_level_once).
§Safety
this points at a live runtime whose executor has the lock installed
if any other thread can reach it, and no reference to the runtime is
live in the caller.
Sourcepub fn executor_mut(&mut self) -> &mut Executor<'static>
pub fn executor_mut(&mut self) -> &mut Executor<'static>
Mutably borrow the underlying executor — for advanced wiring
(parameter services, custom guard conditions). Don’t use during
spin from another thread; the runtime is
single-threaded.
Sourcepub fn apply_tier_sched_policy(
&mut self,
class: Option<&str>,
period_us: Option<u64>,
budget_us: Option<u64>,
deadline_us: Option<u64>,
deadline_policy: Option<&str>,
)
pub fn apply_tier_sched_policy( &mut self, class: Option<&str>, period_us: Option<u64>, budget_us: Option<u64>, deadline_us: Option<u64>, deadline_policy: Option<&str>, )
RFC-0052 / phase-296 W5.4 — lower a tier’s RTOS-agnostic scheduling
policy onto this executor’s DEFAULT scheduling context. One Executor
per tier means “the tier’s policy” == “this executor’s default SC”;
per-group/per-handle bindings still take precedence.
Portable across every board — call from each board’s run_tiers
after building the runtime. Takes the tier fields as primitives (not a
TierSpec) so nros needs no board/platform dependency; a board passes
tier.class, tier.period_us, … straight through.
real_time + budget_us + period_us → SchedClass::Sporadic;
best_effort → SchedClass::BestEffort; time_triggered +
period_us → the cyclic dispatcher (major frame = period, window =
budget_us or the whole frame); deadline_us sets the SC deadline and
deadline_policy its action. A tier with no class/budget/deadline
leaves the default Fifo SC untouched (byte-identical pre-W3 behavior).
Sourcepub fn apply_level_sched_policy(
&mut self,
class: Option<&str>,
period_us: Option<u64>,
budget_us: Option<u64>,
deadline_us: Option<u64>,
deadline_policy: Option<&str>,
kernel_enforces_budget: bool,
) -> Option<SchedContextId>
pub fn apply_level_sched_policy( &mut self, class: Option<&str>, period_us: Option<u64>, budget_us: Option<u64>, deadline_us: Option<u64>, deadline_policy: Option<&str>, kernel_enforces_budget: bool, ) -> Option<SchedContextId>
Issue 2018 (RFC-0108 As-built) – lower a LEVEL’s policy onto its own
scheduling context, the level-runner twin of
apply_tier_sched_policy. Call it on
the registering level (after the runner opened or adopted it), before
its setup: what the setup registers then binds to the level’s context
instead of the executor’s default, which every level on the core
shares.
kernel_enforces_budget says the kernel throttles the level’s thread
with the same budget and period (NuttX SCHED_SPORADIC, Linux
SCHED_DEADLINE), so the executor does not enforce it a second time
(Executor::defer_budget_to_kernel).
Returns the context, or None when the level declares no policy (it
keeps the default) or no context slot was free (said on the console:
the level then runs on the default context, unbudgeted by the
executor).
Sourcepub fn component_count(&self) -> usize
pub fn component_count(&self) -> usize
Number of registered components.
Sourcepub fn register_node<C: ExecutableNode + 'static>(
&mut self,
) -> NodeResult<RegisteredNode<C>>where
C::State: 'static,
pub fn register_node<C: ExecutableNode + 'static>(
&mut self,
) -> NodeResult<RegisteredNode<C>>where
C::State: 'static,
Register a Component (which must also be
ExecutableNode) into this runtime. Builds the
component’s State (via ExecutableNode::init) and
walks Component::register over the live executor — every
declared node / pub / sub / timer materialises as a real
executor handle, and subscription + timer callbacks are wired
to dispatch into ExecutableNode::on_callback.
Sourcepub fn spin_once(&mut self, timeout: Duration) -> Result<(), ExecutorError>
pub fn spin_once(&mut self, timeout: Duration) -> Result<(), ExecutorError>
Drive one executor iteration + a tick per registered
component.
Sourcepub fn spin_once_counted(
&mut self,
timeout: Duration,
) -> Result<SpinOnceResult, ExecutorError>
pub fn spin_once_counted( &mut self, timeout: Duration, ) -> Result<SpinOnceResult, ExecutorError>
Self::spin_once, returning the executor’s own per-iteration counts
instead of discarding them — issue 0572.
spin_once throws away a SpinOnceResult that already carries exactly
what a stalled tier needs to be diagnosed: how many timers fired, how
many subscription callbacks ran, and how many errored. Without it, “the
tier’s timer never fires” and “the tier’s callback runs and its publish
fails” are the same observation from outside the guest — a silent topic.
Sourcepub fn dispatch_callback(&mut self, cb_id: &str, ctx: &mut CallbackCtx<'_>)
pub fn dispatch_callback(&mut self, cb_id: &str, ctx: &mut CallbackCtx<'_>)
Phase 216.B.3 / C.3 follow-up — route a signaled callback to every registered component slot.
The RTIC (nros-board-rtic-stm32f4) and Embassy
(nros-board-embassy-stm32f4) dispatch tasks dequeue a
nros_platform::SignaledCallback envelope from their SPSC
queue / Embassy channel and need a routing entry point that
hands the callback off to the right Node’s on_callback
trampoline. This method is that entry point.
§Strategy — linear scan
Each registered slot dispatches through the codegen-emitted
d() trampoline from nros::node!() (a pointer once;
see
dispatch_fnpackages/core/nros-macros/src/lib.rs). That trampoline calls
<NodeTy as ExecutableNode>::on_callback, whose body
matches on the callback’s own tag set
(Subscription / Timer / Service / Action ids) and is a
no-op for non-matching cb_ids. So a linear scan across every
slot is correct — each slot self-filters and at most one
component actually acts on a given cb_id. A focused
cb_id → slot index is a separate follow-up; the trampoline’s
tag dispatch already gates the real work cheaply (string
compare on statically known literals), so the linear scan is
the minimum-viable wiring that closes the conceptual gap left
by the B.3 / C.3 skeleton emits.
§Borrow semantics
Each ComponentCell’s slot lives behind a RefCell; the
per-slot dispatch takes try_borrow_mut and is a no-op on
re-entrancy. The runtime is single-threaded by construction
(the dispatch task owns it via &mut self), so the borrow
always succeeds in normal flow.
§Both populations
Issue 1865 — a component reaches this runtime one of two ways, and the
two are disjoint (see the observed_callback_counts fold, issue #140):
register_node pushes it to components, while
the install seam every nros::node! register(runtime) goes through
(install_node_typed* → register_node_borrowed) ENROLLS it in the
executor and never pushes. This scanned components alone, so since
the RTIC entry moved onto register(runtime) (phase-289) every signaled
callback the RTIC/Embassy dispatch task forwarded reached no component.
Sourcepub fn spin(&mut self) -> Result<(), ExecutorError>
pub fn spin(&mut self) -> Result<(), ExecutorError>
Spin until the executor’s halt flag is raised.
phase-359 W10 — this used to be #[cfg(feature = "std")] and described
itself as hosted-only, but the body is Duration + spin_once +
is_halted, none of which need an OS. The gate was describing a
CONVENTION (a BSP usually wants its own loop so it can interleave
board work) as if it were a requirement, and a bare-metal image that
wants exactly this loop had to hand-roll it to get it.
It does need alloc: the halt flag is an Arc, so a core-only image
has no flag to poll.
Trait Implementations§
Source§impl NodeDispatchRuntime for ExecutorNodeRuntime
Available on crate feature alloc only.
impl NodeDispatchRuntime for ExecutorNodeRuntime
alloc only.Source§fn spin_once(&mut self, timeout_ms: u32) -> Result<(), ()>
fn spin_once(&mut self, timeout_ms: u32) -> Result<(), ()>
timeout_ms
milliseconds. Ok(()) on a clean spin (including timeout);
Err(()) if the executor surfaces a spin error. Read moreSource§fn entry_spin_budget_ms(&self) -> u32
fn entry_spin_budget_ms(&self) -> u32
spin_once. The executor-backed runtime answers
with how long its park can safely run (until its next deadline when
every source of work can end it, else a 10 ms slice). The default is
that slice, for a runtime that cannot say.Source§fn executor_handle(&mut self) -> *mut c_void
fn executor_handle(&mut self) -> *mut c_void
*mut Executor (as void*) for the
owned-spin entry, so a Node pkg’s register(runtime) wrapper can call
the uniform __nros_component_<pkg>_install(.., executor, ..) seam
(nros::__macro::install_node_typed) instead of the retired opaque-fn-ptr
register_dispatch_slot_dynnros-platform → nros layering wall cleanly (the concrete
ExecutorNodeRuntime lives in nros; this trait can’t name it). Read moreSource§fn observed_callback_counts(&self) -> (usize, usize)
fn observed_callback_counts(&self) -> (usize, usize)
Source§fn apply_lifecycle(&mut self, autostart: u8) -> Result<(), &'static str>
fn apply_lifecycle(&mut self, autostart: u8) -> Result<(), &'static str>
autostart: 0 = none, 1 = configure,
2 = configure+activate. Default no-op (non-executor runtimes, or nros
built without lifecycle-services); the ExecutorNodeRuntime impl in
nros does the real work behind that feature. nros::main! calls this
(via RuntimeCtx::apply_lifecycle) when system.toml declares
[lifecycle], mirroring the bake’s generate.rs::render_lifecycle_fn.
issue 0460 — the Err carries a STATIC REASON, not (). Read moreSource§fn apply_param_services(&mut self) -> Result<(), &'static str>
fn apply_param_services(&mut self) -> Result<(), &'static str>
<param>
initials are NOT passed here (issue 1272): each node’s are seeded on that
node as it is built, from the params slice nros::main! sets before
the node’s register call. After this,
ros2 param list/get/set works against the running node; reconfigured values
live in RAM until the next boot (RFC-0004 §10; persistence is out of scope,
issue 0080). Default no-op (non-executor runtimes, or nros built without
param-services); the ExecutorNodeRuntime impl in nros does the real work
behind that feature. nros::main! calls this (via
RuntimeCtx::apply_param_services) when system.toml declares
[param_services], mirroring the bake’s generate.rs::render_param_persistence_fn.
issue 0460 — the Err carries a STATIC REASON, not (). Read moreSource§fn apply_param_store(&mut self) -> Result<(), &'static str>
fn apply_param_store(&mut self) -> Result<(), &'static str>
params capability axis. nros::main! calls this (via
RuntimeCtx::apply_param_store) before the per-node register calls
when the system declares params and not param_services. Default
no-op (non-executor runtimes, or nros built without param-store,
which the entry const-asserts against).Source§fn apply_declared_params(
&mut self,
nodes: &'static [(&'static str, &'static str)],
params: &'static [(&'static str, &'static str, u8)],
) -> Result<(), &'static str>
fn apply_declared_params( &mut self, nodes: &'static [(&'static str, &'static str)], params: &'static [(&'static str, &'static str, u8)], ) -> Result<(), &'static str>
Source§fn signal_callback(&mut self, _cb: SignaledCallback<'_>)
fn signal_callback(&mut self, _cb: SignaledCallback<'_>)
DispatchStrategy::Deferred
(RTIC) runtimes — Inline runtimes drive callbacks
directly from spin_once and never call this. The default panic
surfaces the mis-wire loudly rather than silently dropping the
callback signal.Source§fn dispatch_strategy(&self) -> DispatchStrategy
fn dispatch_strategy(&self) -> DispatchStrategy
nros check (Phase 216.D.1) cross-validates each Node pkg’s
Node::DISPATCH against this value. Defaults to Inline so
every existing impl reports the historical behavior unchanged.