1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
//! Deterministic handoff primitives for no-std embedded targets.
//!
//! # Primitives
//!
//! | Type | When to reach for it |
//! |------|----------------------|
//! | [`Block`] / [`BlockBuilder`] | Complete contiguous sample windows; compose with a transport. |
//! | [`RingBuf`] | Single-owner ring — simple, no atomics, `&mut` access. |
//! | [`SeqRing`] | Lock-free SPSC ring that **overwrites** old entries (lossy, high-throughput). |
//! | [`EventBuf`] | Lock-free SPSC ring with **backpressure** — rejects pushes when full. |
//! | [`CountedSignal`] | Saturating SPSC count for identical, payload-free events. |
//! | [`EventFlags`] | Coalesced SPSC condition set — one bit per condition, one atomic operation per hot path. |
//! | [`LatestBuf`] | Freshness-first SPSC snapshot — retains one newest unread value. |
//!
//! All are fixed-size and zero-allocation. The buffer types are generic
//! over `T: Copy`; [`CountedSignal`] carries no payload and [`EventFlags`]
//! provides exactly 32 payload-free conditions.
//!
//! # Common traits
//!
//! | Trait | Role | Implementors |
//! |-------|------|--------------|
//! | [`Sink<T>`](traits::Sink) | Accept events | `RingBuf`, `seq_ring::Producer`, `event_buf::Producer` |
//! | [`Source<T>`](traits::Source) | Yield events | `seq_ring::Consumer`, `event_buf::Consumer` |
//! | [`Link<In,Out>`](traits::Link) | Both | Blanket impl for any `Sink<In> + Source<Out>` |
//! | [`LatestSink<T>`](traits::LatestSink) | Publish a newest value | `latest_buf::Producer` |
//! | [`LatestSource<T>`](traits::LatestSource) | Take the newest value with loss evidence | `latest_buf::Consumer` |
//!
//! [`forward`](traits::forward) bridges the stream pair only; `LatestBuf`
//! deliberately stands outside it (decision D2), and the signal types
//! implement neither family.
//!
//! The [`traits::forward`] function transfers items from any `Source` to any
//! `Sink`, making it easy to bridge different buffer types.
//!
//! # Static bring-up
//!
//! [`static_spsc!`](crate::static_spsc) declares a `static` buffer together with
//! named handle types, so a signature need not spell out
//! `event_buf::Producer<'static, T, N>`:
//!
//! ```
//! ph_eventing::static_spsc! {
//! pub mod telemetry: EventBuf<u32, 64>;
//! }
//!
//! fn on_sample(tx: &telemetry::Tx, v: u32) { let _ = tx.push(v); }
//!
//! let (tx, rx) = telemetry::take().expect("first take");
//! on_sample(&tx, 1);
//! assert_eq!(rx.pop(), Some(1));
//! ```
//!
//! # Quick start — `RingBuf`
//! ```
//! use ph_eventing::RingBuf;
//!
//! let mut ring = RingBuf::<u32, 4>::new();
//! ring.push(1);
//! ring.push(2);
//! ring.push(3);
//! assert_eq!(ring.latest(), Some(3));
//! ```
//!
//! # Quick start — `SeqRing`
//! ```
//! use ph_eventing::SeqRing;
//!
//! let ring = SeqRing::<u32, 64>::new();
//! let producer = ring.try_producer().expect("producer");
//! let mut consumer = ring.try_consumer().expect("consumer");
//!
//! producer.push(42);
//! // Assert the delivery flag, not just the hook body: an empty ring would
//! // skip the hook and the assertions inside it would pass vacuously.
//! let delivered = consumer.poll_one(|seq, v| {
//! assert_eq!(seq, 1);
//! assert_eq!(*v, 42);
//! });
//! assert!(delivered);
//! ```
//!
//! # Quick start — `EventBuf`
//! ```
//! use ph_eventing::EventBuf;
//!
//! let buf = EventBuf::<u32, 4>::new();
//! let producer = buf.try_producer().expect("producer");
//! let consumer = buf.try_consumer().expect("consumer");
//!
//! assert!(producer.push(1).is_ok());
//! assert!(producer.push(2).is_ok());
//! assert_eq!(consumer.pop(), Some(1));
//! ```
//!
//! # Quick start — `forward`
//! ```
//! use ph_eventing::{SeqRing, EventBuf};
//! use ph_eventing::traits::{Source, Sink, forward};
//!
//! let seq = SeqRing::<u32, 8>::new();
//! let sp = seq.try_producer().expect("producer");
//! let mut sc = seq.try_consumer().expect("consumer");
//!
//! sp.push(1); sp.push(2);
//!
//! let eb = EventBuf::<u32, 8>::new();
//! let mut ep = eb.try_producer().expect("producer");
//!
//! let (n, err) = forward(&mut sc, &mut ep, 10);
//! assert_eq!(n, 2);
//! assert!(err.is_none());
//! ```
//!
//! # No-std
//! The crate is `#![no_std]` by default. Tests require `std`.
//!
//! # Targets without atomics
//! Every concurrent primitive — `SeqRing`, `EventBuf`, `EventFlags`,
//! `CountedSignal`, and `LatestBuf` — requires 32-bit atomics. For targets that lack them
//! (for example `thumbv6m-none-eabi`), enable
//! `portable-atomic-unsafe-assume-single-core` or `portable-atomic-critical-section`.
//! The crate always compiles those modules, so no-atomic targets need one of
//! those features even when only [`RingBuf`] is used. `RingBuf` itself uses no
//! atomics.
//!
//! # Safety and concurrency
//! - `RingBuf` has no atomics and no interior mutability — standard Rust borrow
//! rules apply. It stores slots as `MaybeUninit<T>` and reads only live
//! entries, so it does contain `unsafe`.
//! - `SeqRing`, `EventBuf`, and `EventFlags` are SPSC by design: exactly one
//! producer and one consumer must be active. Handle acquisition is
//! `try_producer()` / `try_consumer()`, which return `None` rather than
//! panicking — on a microcontroller a panic is a reset. (The panicking
//! `producer()` / `consumer()`, deprecated since 0.2.0, were removed in
//! 0.3.0.) Using unsafe to bypass these constraints is undefined behavior.
//!
//! The examples here use `.expect(...)` for brevity, which is a panic. That
//! is fine in a doctest on a host; in firmware, branch on the `None`:
//!
//! ```
//! # use ph_eventing::EventBuf;
//! # let buf = EventBuf::<u32, 4>::new();
//! let Some(tx) = buf.try_producer() else {
//! return; // already claimed -- report it, do not reset the device
//! };
//! # let _ = tx;
//! ```
//! - [`EventBuf`] is race-free by construction — its producer and consumer
//! never touch the same slot — and passes Miri with the data-race detector
//! enabled.
//! - [`SeqRing`] is a seqlock and carries a **known formal data race**. A
//! raced copy is discarded and never becomes an invalid value — within the
//! whole-span bound: the discard compares `u32` sequences, so a consumer
//! stalled mid-read for a full `2^32 - 1` publications can pass both checks
//! against a rewritten slot (the [`seq_ring`] "whole-span sequence
//! aliasing" section carries the reachability arithmetic and escape
//! hatches). The access itself is undefined behaviour by the letter of the
//! memory model. Practical
//! consequence: running Miri over a test that drives this ring from two
//! threads reports UB inside this crate — that is the deviation, not a new
//! bug. It is a deliberate trade of formal soundness for accepting any
//! `T: Copy`; the [`seq_ring`] module docs give the alternatives and why each
//! was rejected. [`EventBuf`] has no such caveat, but applies backpressure
//! rather than overwriting, so it is not a drop-in replacement.
//!
//! # Using it across contexts
//! The typical embedded shape is a producer in an interrupt handler and a
//! consumer in a task loop.
//!
//! - [`SeqRing`] and [`EventBuf`] are `Sync` when `T: Send`, and [`EventFlags`]
//! is `Sync`, so a shared reference can be handed to both contexts. The
//! `Producer` and `Consumer` handles are
//! `Send + !Sync`: move each into the context that owns it, never share one.
//! - The handles borrow the buffer, so the buffer must outlive them.
//! - **`new()` is a `const fn`** on the normal build, so
//! `static BUF: EventBuf<u32, 64> = EventBuf::new();` works. (Under
//! `--cfg loom` it is non-const because Loom's atomics are not
//! const-constructible.) Handles still borrow the buffer, so an ISR /
//! task split typically pairs the `static` with a `StaticCell` or similar
//! for the handles themselves.
//!
//! `N` is fixed at compile time and the buffer lives inline —
//! `N * size_of::<T>()` bytes, no allocation. For [`EventBuf`] it is the
//! backpressure threshold; for [`SeqRing`] it is how far the consumer may lag
//! before entries are lost. It need not be a power of two.
//!
//! # SeqRing semantics
//! - Sequence numbers are monotonically increasing `u32` values; `0` is reserved for "empty".
//! - `poll_one`/`poll_up_to` drain in-order and return `PollStats`; `poll_one_value`
//! returns `(seq, T)` without a hook.
//! - `latest` / `latest_value` read the newest value without advancing the consumer cursor.
//! - If the consumer lags by more than `N`, it skips ahead and reports drops via `PollStats`.
//! - Once every `2^32 - 1` pushes the sequence counter wraps, and a few extra entries are dropped
//! there because `push` skips the reserved sequence `0`: exactly one for a power-of-two `N`,
//! none if `N` divides `2^32 - 1`, up to `N - 1` otherwise. Reported as ordinary drops, never a
//! stale or torn value within the whole-span bound stated in the [`seq_ring`]
//! module docs. Prefer a power of two for `N`.
//! - `Consumer::dropped` saturates rather than wrapping; `usize` is 32 bits on
//! the targets this crate ships to, so a long-lived lagging consumer can
//! reach the top of the range.
//!
//! # EventBuf semantics
//! - `push` returns `Err(val)` when the buffer is full — no data is silently lost.
//! - `pop` returns the oldest item, or `None` when empty.
//! - `peek` copies the oldest item without advancing the consumer cursor.
//! - `drain(max, hook)` consumes up to `max` items through a callback.
//!
//! # EventFlags semantics
//! - [`event_flags::Producer::raise`] unions a mask into the pending set.
//! - [`event_flags::Consumer::take_all`] atomically returns and clears that set.
//! - Duplicate raises may coalesce; ordering and multiplicity are not retained.
//! - A take that observes a raise also observes memory actions sequenced before it.
compile_error!;
// NOTE: `portable-atomic-unsafe-assume-single-core` and
// `portable-atomic-critical-section` select different portable-atomic backends
// and cannot both be enabled — which makes `--all-features` unsupported for
// this crate. The guard for it lives in build.rs, not here: both features
// forward straight to portable-atomic, whose own `compile_error!` fires while
// the dependency compiles, so a guard in this file would never be reached. A
// build script does not depend on portable-atomic and runs regardless.
pub
pub use ;
pub use ;
pub use EventBuf;
pub use ;
pub use ;
pub use RingBuf;
pub use ;
pub use ;
extern crate std;
/// Helpers shared by the concurrency tests.
pub