Skip to main content

laminar_core/alloc/
ring_buffer.rs

1//! Fixed-capacity ring buffer for zero-allocation queuing.
2//!
3//! Provides a circular buffer with O(1) push/pop operations without
4//! any heap allocation after initialization.
5
6use std::mem::MaybeUninit;
7
8/// Fixed-capacity ring buffer.
9///
10/// A circular buffer that stores up to `N-1` elements (one slot reserved
11/// for distinguishing full from empty). All operations are O(1) and
12/// allocation-free after construction.
13///
14/// # Type Parameters
15///
16/// * `T` - The element type
17/// * `N` - Buffer capacity (must be > 1; usable capacity is N-1)
18///
19/// # Example
20///
21/// ```
22/// use laminar_core::alloc::RingBuffer;
23///
24/// let mut buffer: RingBuffer<u64, 4> = RingBuffer::new();
25///
26/// buffer.push(1).unwrap();
27/// buffer.push(2).unwrap();
28/// buffer.push(3).unwrap();
29/// // buffer.push(4) would fail - only 3 slots usable
30///
31/// assert_eq!(buffer.pop(), Some(1));
32/// assert_eq!(buffer.pop(), Some(2));
33/// ```
34///
35/// # Thread Safety
36///
37/// This buffer is NOT thread-safe. For multi-threaded scenarios, use the
38/// SPSC queue in `tpc::spsc`.
39pub struct RingBuffer<T, const N: usize> {
40    /// The underlying buffer storage
41    buffer: [MaybeUninit<T>; N],
42    /// Read position (tail)
43    tail: usize,
44    /// Write position (head)
45    head: usize,
46}
47
48impl<T, const N: usize> RingBuffer<T, N> {
49    /// Create a new empty ring buffer.
50    ///
51    /// # Panics
52    ///
53    /// Panics if `N < 2` (need at least 2 slots for ring buffer semantics).
54    #[must_use]
55    pub fn new() -> Self {
56        assert!(N >= 2, "RingBuffer requires N >= 2");
57        Self {
58            // SAFETY: MaybeUninit<T> doesn't require initialization
59            buffer: unsafe { MaybeUninit::uninit().assume_init() },
60            tail: 0,
61            head: 0,
62        }
63    }
64
65    /// Push an item onto the buffer.
66    ///
67    /// # Performance
68    ///
69    /// O(1), no allocation.
70    ///
71    /// # Errors
72    ///
73    /// Returns `Err(item)` if the buffer is full, giving back ownership
74    /// of the item that couldn't be inserted.
75    ///
76    /// # Example
77    ///
78    /// ```
79    /// use laminar_core::alloc::RingBuffer;
80    ///
81    /// let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
82    /// buf.push(1).unwrap();
83    /// buf.push(2).unwrap();
84    /// buf.push(3).unwrap();
85    /// assert!(buf.push(4).is_err()); // Full
86    /// ```
87    #[inline]
88    pub fn push(&mut self, item: T) -> Result<(), T> {
89        let next_head = (self.head + 1) % N;
90        if next_head == self.tail {
91            return Err(item); // Full
92        }
93
94        // SAFETY: We're writing to a valid index within bounds,
95        // and we've verified the slot is available (not readable).
96        unsafe {
97            self.buffer[self.head].as_mut_ptr().write(item);
98        }
99        self.head = next_head;
100        Ok(())
101    }
102
103    /// Pop an item from the buffer.
104    ///
105    /// Returns `None` if the buffer is empty.
106    ///
107    /// # Performance
108    ///
109    /// O(1), no allocation.
110    ///
111    /// # Example
112    ///
113    /// ```
114    /// use laminar_core::alloc::RingBuffer;
115    ///
116    /// let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
117    /// buf.push(42).unwrap();
118    /// assert_eq!(buf.pop(), Some(42));
119    /// assert_eq!(buf.pop(), None);
120    /// ```
121    #[inline]
122    pub fn pop(&mut self) -> Option<T> {
123        if self.head == self.tail {
124            return None; // Empty
125        }
126
127        // SAFETY: We've verified the slot contains a valid item
128        // (head != tail means there's at least one item).
129        let item = unsafe { self.buffer[self.tail].as_ptr().read() };
130        self.tail = (self.tail + 1) % N;
131        Some(item)
132    }
133
134    /// Peek at the front item without removing it.
135    ///
136    /// Returns `None` if the buffer is empty.
137    ///
138    /// # Performance
139    ///
140    /// O(1), no allocation.
141    #[inline]
142    pub fn peek(&self) -> Option<&T> {
143        if self.head == self.tail {
144            return None;
145        }
146
147        // SAFETY: We've verified the slot contains a valid item.
148        Some(unsafe { &*self.buffer[self.tail].as_ptr() })
149    }
150
151    /// Get mutable reference to the front item without removing it.
152    #[inline]
153    pub fn peek_mut(&mut self) -> Option<&mut T> {
154        if self.head == self.tail {
155            return None;
156        }
157
158        // SAFETY: We've verified the slot contains a valid item.
159        Some(unsafe { &mut *self.buffer[self.tail].as_mut_ptr() })
160    }
161
162    /// Check if the buffer is empty.
163    #[inline]
164    #[must_use]
165    pub fn is_empty(&self) -> bool {
166        self.head == self.tail
167    }
168
169    /// Check if the buffer is full.
170    #[inline]
171    #[must_use]
172    pub fn is_full(&self) -> bool {
173        (self.head + 1) % N == self.tail
174    }
175
176    /// Get the number of items in the buffer.
177    #[inline]
178    #[must_use]
179    pub fn len(&self) -> usize {
180        if self.head >= self.tail {
181            self.head - self.tail
182        } else {
183            N - self.tail + self.head
184        }
185    }
186
187    /// Get the maximum usable capacity (N - 1).
188    #[inline]
189    #[must_use]
190    pub const fn capacity(&self) -> usize {
191        N - 1
192    }
193
194    /// Clear all items from the buffer.
195    ///
196    /// Drops all contained items and resets to empty state.
197    pub fn clear(&mut self) {
198        while self.pop().is_some() {}
199    }
200
201    /// Iterate over items without consuming them.
202    pub fn iter(&self) -> RingBufferIter<'_, T, N> {
203        RingBufferIter {
204            buffer: self,
205            pos: self.tail,
206            remaining: self.len(),
207        }
208    }
209}
210
211impl<T, const N: usize> Default for RingBuffer<T, N> {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217impl<T, const N: usize> Drop for RingBuffer<T, N> {
218    fn drop(&mut self) {
219        // Drop all remaining items
220        self.clear();
221    }
222}
223
224impl<T: std::fmt::Debug, const N: usize> std::fmt::Debug for RingBuffer<T, N> {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        f.debug_struct("RingBuffer")
227            .field("len", &self.len())
228            .field("capacity", &self.capacity())
229            .field("items", &self.iter().collect::<Vec<_>>())
230            .finish()
231    }
232}
233
234/// Iterator over ring buffer items.
235pub struct RingBufferIter<'a, T, const N: usize> {
236    buffer: &'a RingBuffer<T, N>,
237    pos: usize,
238    remaining: usize,
239}
240
241impl<'a, T, const N: usize> Iterator for RingBufferIter<'a, T, N> {
242    type Item = &'a T;
243
244    fn next(&mut self) -> Option<Self::Item> {
245        if self.remaining == 0 {
246            return None;
247        }
248
249        // SAFETY: pos is within the valid range of items
250        let item = unsafe { &*self.buffer.buffer[self.pos].as_ptr() };
251        self.pos = (self.pos + 1) % N;
252        self.remaining -= 1;
253        Some(item)
254    }
255
256    fn size_hint(&self) -> (usize, Option<usize>) {
257        (self.remaining, Some(self.remaining))
258    }
259}
260
261impl<T, const N: usize> ExactSizeIterator for RingBufferIter<'_, T, N> {}
262
263impl<'a, T, const N: usize> IntoIterator for &'a RingBuffer<T, N> {
264    type Item = &'a T;
265    type IntoIter = RingBufferIter<'a, T, N>;
266
267    fn into_iter(self) -> Self::IntoIter {
268        self.iter()
269    }
270}
271
272// We need to allow unsafe_code for this module due to MaybeUninit usage
273#[allow(unsafe_code)]
274const _: () = ();
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_new_buffer() {
282        let buf: RingBuffer<u64, 8> = RingBuffer::new();
283        assert!(buf.is_empty());
284        assert!(!buf.is_full());
285        assert_eq!(buf.len(), 0);
286        assert_eq!(buf.capacity(), 7); // N-1
287    }
288
289    #[test]
290    fn test_push_pop() {
291        let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
292
293        buf.push(1).unwrap();
294        buf.push(2).unwrap();
295        buf.push(3).unwrap();
296
297        assert_eq!(buf.len(), 3);
298        assert!(buf.is_full());
299
300        assert_eq!(buf.pop(), Some(1));
301        assert_eq!(buf.pop(), Some(2));
302        assert_eq!(buf.pop(), Some(3));
303        assert_eq!(buf.pop(), None);
304    }
305
306    #[test]
307    fn test_full_buffer() {
308        let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
309
310        buf.push(1).unwrap();
311        buf.push(2).unwrap();
312        buf.push(3).unwrap();
313
314        // Buffer full (capacity is 3 for N=4)
315        assert!(buf.push(4).is_err());
316    }
317
318    #[test]
319    fn test_wraparound() {
320        let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
321
322        // Fill and drain multiple times to test wraparound
323        for round in 0..5 {
324            let base = round * 10;
325            buf.push(base + 1).unwrap();
326            buf.push(base + 2).unwrap();
327            buf.push(base + 3).unwrap();
328
329            assert_eq!(buf.pop(), Some(base + 1));
330            assert_eq!(buf.pop(), Some(base + 2));
331            assert_eq!(buf.pop(), Some(base + 3));
332        }
333    }
334
335    #[test]
336    fn test_peek() {
337        let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
338
339        assert_eq!(buf.peek(), None);
340
341        buf.push(42).unwrap();
342        assert_eq!(buf.peek(), Some(&42));
343        assert_eq!(buf.len(), 1); // Peek doesn't remove
344
345        buf.pop();
346        assert_eq!(buf.peek(), None);
347    }
348
349    #[test]
350    fn test_peek_mut() {
351        let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
352        buf.push(10).unwrap();
353
354        if let Some(val) = buf.peek_mut() {
355            *val = 20;
356        }
357
358        assert_eq!(buf.pop(), Some(20));
359    }
360
361    #[test]
362    fn test_clear() {
363        let mut buf: RingBuffer<i32, 8> = RingBuffer::new();
364        buf.push(1).unwrap();
365        buf.push(2).unwrap();
366        buf.push(3).unwrap();
367
368        buf.clear();
369
370        assert!(buf.is_empty());
371        assert_eq!(buf.len(), 0);
372    }
373
374    #[test]
375    fn test_iter() {
376        let mut buf: RingBuffer<i32, 8> = RingBuffer::new();
377        buf.push(1).unwrap();
378        buf.push(2).unwrap();
379        buf.push(3).unwrap();
380
381        let items: Vec<_> = buf.iter().copied().collect();
382        assert_eq!(items, vec![1, 2, 3]);
383
384        // Iteration doesn't consume
385        assert_eq!(buf.len(), 3);
386    }
387
388    #[test]
389    fn test_debug() {
390        let mut buf: RingBuffer<i32, 4> = RingBuffer::new();
391        buf.push(1).unwrap();
392        buf.push(2).unwrap();
393
394        let debug_str = format!("{buf:?}");
395        assert!(debug_str.contains("RingBuffer"));
396        assert!(debug_str.contains("len"));
397    }
398
399    #[test]
400    fn test_drop_items() {
401        use std::sync::atomic::{AtomicUsize, Ordering};
402
403        static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
404
405        #[derive(Debug)]
406        struct DropCounter;
407        impl Drop for DropCounter {
408            fn drop(&mut self) {
409                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
410            }
411        }
412
413        DROP_COUNT.store(0, Ordering::SeqCst);
414
415        {
416            let mut buf: RingBuffer<DropCounter, 8> = RingBuffer::new();
417            buf.push(DropCounter).unwrap();
418            buf.push(DropCounter).unwrap();
419            buf.push(DropCounter).unwrap();
420        }
421
422        // All items should be dropped when buffer is dropped
423        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
424    }
425
426    #[test]
427    #[should_panic(expected = "N >= 2")]
428    fn test_invalid_size() {
429        let _: RingBuffer<u8, 1> = RingBuffer::new();
430    }
431}