pub struct ParameterStorage<const N: usize = nros_params::::server::ParameterStorage::{constant#0}> { /* private fields */ }Expand description
Backing storage a caller places, then lends to a ParameterServer.
N defaults to MAX_PARAMETERS, the build-time knob
(NROS_MAX_PARAMETERS / CONFIG_NROS_MAX_PARAMETERS). It is a DEFAULT,
not a cap — ParameterStorage::<8>::new() and ParameterStorage::<256>
are equally valid, and the server takes its capacity from whatever table it
is handed.
The intended shapes are a static, a caller-owned struct field, or (with
alloc) one allocation made once at start-up:
use nros_params::{ParameterServer, ParameterStorage, ParameterValue};
let mut storage: ParameterStorage<8> = ParameterStorage::new();
let mut server = ParameterServer::new_in(storage.as_table());
assert!(server.declare("max_speed", ParameterValue::Double(1.0)));§Why this type exists at all — issue 0756
This is where the size lives, and it is enormous: ParameterValue is sized
by its largest variant
(StringArray(Vec<String<MAX_STRING_VALUE_LEN>, MAX_ARRAY_LEN>)), so every
slot costs ~8.5 KiB whatever it actually holds. Measured: 285,184 bytes
at the default 32 slots, 2,281,472 at 256. (The eight extra bytes in
issue 0756’s 285,192 were the count that stayed behind on
ParameterServer, which is 24 bytes total now.)
Before phase-382 W2’ that bulk was INSIDE ParameterServer, so
Box::new(ParameterServer::new()) built a multi-hundred-KiB value on the
STACK before copying it into the allocation — Rust has no placement-new —
which silently overran a thread stack sized for anything smaller. On the
Zephyr lane an image built with 256 slots booted to
dds_create_participant and hung with no fault and no output, while 32 —
which fits the 512 KiB main stack the cyclonedds snippet asks for — ran
clean. The mitigation then was ParameterServer::init_in_place, a
placement initialiser that bounded the largest temporary at one
Option<ParameterEntry>.
W2’ dissolves the server half of that: ParameterServer is now a table
borrow plus a count (tens of bytes), so nothing about constructing a server
depends on the knob. The bulk did not vanish, it MOVED here — and so did
the hazard, verbatim: Box::new(ParameterStorage::new()) is the same
stack-first copy the issue was about. init_in_place is therefore kept,
on the type that is actually big, and any heap-placed storage must go
through it. A caller who places the storage as a static or lets W3’ carve
it out of the executor backing never materialises it by value and does not
need it.
Implementations§
Source§impl<const N: usize> ParameterStorage<N>
impl<const N: usize> ParameterStorage<N>
Sourcepub const fn new() -> ParameterStorage<N>
pub const fn new() -> ParameterStorage<N>
Create empty storage.
const, so a static NROS_PARAMS: ParameterStorage = ParameterStorage::new();
lands in .bss with no initialiser and no stack temporary.
Sourcepub fn as_table(&mut self) -> ParameterTable<'_>
pub fn as_table(&mut self) -> ParameterTable<'_>
Lend the storage to a ParameterServer.
Sourcepub unsafe fn init_in_place(dst: *mut ParameterStorage<N>)
pub unsafe fn init_in_place(dst: *mut ParameterStorage<N>)
Initialise storage directly into dst, without ever materialising one
by value.
Issue 0756 — see this type’s documentation for why that matters. The
short version: this type is 285,184 bytes at the default 32 slots and
2,281,472 at 256, and Rust has no placement-new, so
Box::new(ParameterStorage::new()) builds all of it on the caller’s
stack first. Initialising through the allocation bounds the largest
stack temporary at one Option<ParameterEntry>, so the
NROS_MAX_PARAMETERS knob no longer decides whether boot survives.
§Safety
dst must be non-null, correctly aligned, and valid for writes of
size_of::<Self>() bytes. The pointee may be uninitialised on entry;
it is fully initialised on return.