macro_rules! static_spsc {
(
$(#[$attr:meta])*
$vis:vis mod $name:ident : EventBuf<$t:ty, $n:tt>;
) => { ... };
(
$(#[$attr:meta])*
$vis:vis mod $name:ident : SeqRing<$t:ty, $n:tt>;
) => { ... };
}Expand description
Declare a static SPSC buffer with its handle types and a paired take.
§What it solves
A const fn new puts the buffer in .bss — no flash, no startup code. The
handles are a different matter: they are Send + !Sync, which is exactly
what makes it sound to move a producer into an ISR and a consumer into a
task loop, and a static requires Sync. Handles can therefore never
live in a static, whatever the constructor looks like. Taking them stays
a runtime step, permanently.
What is left is boilerplate, and it is genuinely awkward: every function
that accepts a handle must spell out
ph_eventing::event_buf::Producer<'static, u32, 64>. This macro names those
types for you.
§Example
ph_eventing::static_spsc! {
/// Telemetry from the sampling ISR to the reporting task.
pub mod telemetry: EventBuf<u32, 64>;
}
// `Tx` and `Rx` are ordinary type aliases — usable in signatures.
fn on_sample(tx: &telemetry::Tx, v: u32) {
let _ = tx.push(v);
}
let (tx, rx) = telemetry::take().expect("first take");
on_sample(&tx, 7);
assert_eq!(rx.pop(), Some(7));
// SPSC is still enforced: there is only ever one of each.
assert!(telemetry::take().is_none());SeqRing works the same way:
ph_eventing::static_spsc! {
pub mod events: SeqRing<u32, 32>;
}
let (tx, mut rx) = events::take().expect("first take");
tx.push(1);
assert_eq!(rx.poll_one_value(), Some((1, 1)));§Notes
take()is all-or-nothing. If the consumer cannot be taken it drops the producer rather than leaving the buffer half-claimed, so a failed call leaves nothing stranded.- It uses the fallible constructors, so no panic path reaches your binary. On a microcontroller a panic is a reset, and the panic machinery costs flash.
take()is not one-shot. Dropping both handles releases their claim flags, and a later call succeeds again — which is what makes a failed partial take recoverable rather than permanent.- The generated module owns its
static, buttake()is not the only way in: the accessor below hands out&'staticto it, sotry_producer()/try_consumer()remain reachable. That is deliberate and safe — the SPSC guarantee is enforced by the buffer’s own claim flags, not by hiding it. The macro removes the boilerplate; it does not add an invariant.