Skip to main content

ph_eventing/
ring.rs

1//! Fixed-size, stack-allocated ring buffer — no heap, no alloc, no atomics.
2//!
3//! [`RingBuf`] is a single-owner (`&mut self`) ring that overwrites the
4//! oldest element when full. It requires `T: Copy + Default` and is ideal
5//! for sample windows, local event logs, and anywhere a simple circular
6//! buffer is needed without cross-thread sharing.
7//!
8//! For a lock-free SPSC ring with sequence tracking, see [`crate::SeqRing`].
9//! For a lock-free SPSC ring with backpressure, see [`crate::EventBuf`].
10//!
11//! # Example
12//! ```
13//! use ph_eventing::RingBuf;
14//!
15//! let mut r = RingBuf::<u32, 4>::new();
16//! r.push(10);
17//! r.push(20);
18//! assert_eq!(r.latest(), Some(20));
19//! assert_eq!(r.get(0), Some(10)); // oldest
20//! ```
21
22/// A ring buffer of `N` elements stored entirely on the stack.
23///
24/// Once full, new pushes overwrite the oldest entry. Iteration with
25/// [`iter()`](RingBuf::iter) yields elements from oldest to newest.
26pub struct RingBuf<T: Copy + Default, const N: usize> {
27    buf: [T; N],
28    /// Write cursor — always points to the *next* slot to write.
29    head: usize,
30    /// Number of elements currently stored (≤ N).
31    len: usize,
32}
33
34impl<T: Copy + Default, const N: usize> RingBuf<T, N> {
35    pub fn new() -> Self {
36        assert!(N > 0, "RingBuf capacity N must be > 0");
37        Self {
38            buf: [T::default(); N],
39            head: 0,
40            len: 0,
41        }
42    }
43
44    pub fn push(&mut self, val: T) {
45        self.buf[self.head] = val;
46        self.head = (self.head + 1) % N;
47        if self.len < N {
48            self.len += 1;
49        }
50    }
51
52    pub fn len(&self) -> usize {
53        self.len
54    }
55
56    pub fn is_empty(&self) -> bool {
57        self.len == 0
58    }
59
60    pub fn is_full(&self) -> bool {
61        self.len == N
62    }
63
64    /// Number of elements the ring can hold.
65    #[inline]
66    pub const fn capacity(&self) -> usize {
67        N
68    }
69
70    pub fn clear(&mut self) {
71        self.head = 0;
72        self.len = 0;
73    }
74
75    /// Read the `i`-th element (0 = oldest).
76    pub fn get(&self, i: usize) -> Option<T> {
77        if i >= self.len {
78            return None;
79        }
80        let idx = if self.len < N { i } else { (self.head + i) % N };
81        Some(self.buf[idx])
82    }
83
84    /// Most recently pushed element.
85    pub fn latest(&self) -> Option<T> {
86        if self.len == 0 {
87            return None;
88        }
89        let idx = if self.head == 0 { N - 1 } else { self.head - 1 };
90        Some(self.buf[idx])
91    }
92
93    /// Iterate over elements oldest→newest.
94    pub fn iter(&self) -> RingIter<'_, T, N> {
95        RingIter { ring: self, pos: 0 }
96    }
97}
98
99impl<T: Copy + Default, const N: usize> Default for RingBuf<T, N> {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105impl<T: Copy + Default, const N: usize> core::fmt::Debug for RingBuf<T, N> {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        f.debug_struct("RingBuf")
108            .field("len", &self.len)
109            .field("capacity", &N)
110            .finish()
111    }
112}
113
114/// Iterator over [`RingBuf`] elements from oldest to newest.
115pub struct RingIter<'a, T: Copy + Default, const N: usize> {
116    ring: &'a RingBuf<T, N>,
117    pos: usize,
118}
119
120impl<'a, T: Copy + Default, const N: usize> Iterator for RingIter<'a, T, N> {
121    type Item = T;
122
123    fn next(&mut self) -> Option<T> {
124        let val = self.ring.get(self.pos)?;
125        self.pos += 1;
126        Some(val)
127    }
128
129    fn size_hint(&self) -> (usize, Option<usize>) {
130        let remaining = self.ring.len().saturating_sub(self.pos);
131        (remaining, Some(remaining))
132    }
133}
134
135impl<T: Copy + Default, const N: usize> ExactSizeIterator for RingIter<'_, T, N> {}
136
137impl<T: Copy + Default, const N: usize> core::fmt::Debug for RingIter<'_, T, N> {
138    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
139        f.debug_struct("RingIter")
140            .field("remaining", &(self.ring.len().saturating_sub(self.pos)))
141            .finish()
142    }
143}
144
145impl<'a, T: Copy + Default, const N: usize> IntoIterator for &'a RingBuf<T, N> {
146    type Item = T;
147    type IntoIter = RingIter<'a, T, N>;
148
149    fn into_iter(self) -> RingIter<'a, T, N> {
150        self.iter()
151    }
152}
153
154impl<T: Copy + Default, const N: usize> crate::traits::Sink<T> for RingBuf<T, N> {
155    type Error = core::convert::Infallible;
156
157    #[inline]
158    fn try_push(&mut self, val: T) -> Result<(), core::convert::Infallible> {
159        self.push(val);
160        Ok(())
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn new_ring_is_empty() {
170        let r = RingBuf::<u32, 4>::new();
171        assert!(r.is_empty());
172        assert!(!r.is_full());
173        assert_eq!(r.len(), 0);
174        assert_eq!(r.latest(), None);
175        assert_eq!(r.get(0), None);
176    }
177
178    #[test]
179    fn push_and_get() {
180        let mut r = RingBuf::<u32, 4>::new();
181        r.push(10);
182        r.push(20);
183        r.push(30);
184        assert_eq!(r.len(), 3);
185        assert_eq!(r.get(0), Some(10));
186        assert_eq!(r.get(1), Some(20));
187        assert_eq!(r.get(2), Some(30));
188        assert_eq!(r.get(3), None);
189        assert_eq!(r.latest(), Some(30));
190    }
191
192    #[test]
193    fn overwrite_oldest_when_full() {
194        let mut r = RingBuf::<u32, 3>::new();
195        r.push(1);
196        r.push(2);
197        r.push(3);
198        assert!(r.is_full());
199
200        r.push(4); // overwrites 1
201        assert_eq!(r.len(), 3);
202        assert_eq!(r.get(0), Some(2));
203        assert_eq!(r.get(1), Some(3));
204        assert_eq!(r.get(2), Some(4));
205        assert_eq!(r.latest(), Some(4));
206    }
207
208    #[test]
209    fn clear_resets_state() {
210        let mut r = RingBuf::<u32, 4>::new();
211        r.push(1);
212        r.push(2);
213        r.clear();
214        assert!(r.is_empty());
215        assert_eq!(r.len(), 0);
216        assert_eq!(r.latest(), None);
217    }
218
219    #[test]
220    fn iter_oldest_to_newest() {
221        let mut r = RingBuf::<u32, 4>::new();
222        for i in 1..=6 {
223            r.push(i);
224        }
225        // capacity 4, pushed 6 → oldest is 3
226        let v: std::vec::Vec<u32> = r.iter().collect();
227        assert_eq!(v, [3, 4, 5, 6]);
228    }
229
230    #[test]
231    fn iter_exact_size() {
232        let mut r = RingBuf::<u32, 4>::new();
233        r.push(1);
234        r.push(2);
235        let it = r.iter();
236        assert_eq!(it.len(), 2);
237    }
238
239    #[test]
240    fn default_is_new() {
241        let r: RingBuf<u8, 8> = RingBuf::default();
242        assert!(r.is_empty());
243    }
244
245    #[test]
246    #[should_panic(expected = "RingBuf capacity N must be > 0")]
247    fn zero_capacity_panics() {
248        let _ = RingBuf::<u32, 0>::new();
249    }
250
251    #[test]
252    fn capacity_returns_n() {
253        let r = RingBuf::<u32, 8>::new();
254        assert_eq!(r.capacity(), 8);
255    }
256
257    #[test]
258    fn into_iter_for_ref() {
259        let mut r = RingBuf::<u32, 4>::new();
260        r.push(1);
261        r.push(2);
262        r.push(3);
263        let v: std::vec::Vec<u32> = (&r).into_iter().collect();
264        assert_eq!(v, [1, 2, 3]);
265    }
266}