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
//! Stack-allocated ring buffers for no-std embedded targets.
//!
//! # Primitives
//!
//! | Type | When to reach for it |
//! |------|----------------------|
//! | [`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. |
//!
//! All three are fixed-size, zero-allocation, and generic over `T: Copy`.
//!
//! # 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>` |
//!
//! 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);
//! consumer.poll_one(|seq, v| {
//! assert_eq!(seq, 1);
//! assert_eq!(*v, 42);
//! });
//! ```
//!
//! # 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
//! `SeqRing` and `EventBuf` require 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` and `EventBuf` are SPSC by design: exactly one producer and one
//! consumer must be active. Use `try_producer()` / `try_consumer()`, which
//! return `None` rather than panicking — on a microcontroller a panic is a
//! reset. The panicking `producer()` / `consumer()` are deprecated since
//! 0.2.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**. The
//! copy is never returned and never becomes an invalid value, but the access
//! 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`, so `&buf` 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. Prefer a power of two for `N`; see the [`seq_ring`] module docs.
//! - `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.
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 EventBuf;
pub use RingBuf;
pub use ;
pub use ;
extern crate std;
/// Helpers shared by the concurrency tests.
pub