Skip to main content

refraction_types/
ring_buffer.rs

1use crate::{
2    BOUNDED_RING_BUFFER_DEFAULT_SIZE,
3    ring_iter::{IntoIter, Iter, IterMut},
4};
5
6#[derive(Debug, Clone)]
7pub struct BoundedRingBuffer<T> {
8    pub(crate) data: Vec<T>,
9    pub(crate) get_idx: usize,
10    pub(crate) put_idx: usize,
11    pub(crate) capacity: usize,
12    pub(crate) len: usize,
13}
14
15impl<T> BoundedRingBuffer<T>
16where
17    T: Copy + Default,
18{
19    /// Creates a buffer with the default capacity.
20    #[must_use]
21    pub fn new() -> Self {
22        Self::with_capacity(BOUNDED_RING_BUFFER_DEFAULT_SIZE)
23    }
24
25    /// Creates a buffer with a fixed capacity.
26    ///
27    /// # Panics
28    ///
29    /// Panics if `cap` is `0`.
30    #[must_use]
31    pub fn with_capacity(cap: usize) -> Self {
32        assert_ne!(cap, 0, "Capacity cannot be zero!");
33        Self {
34            data: vec![T::default(); cap],
35            put_idx: 0,
36            get_idx: 0,
37            capacity: cap,
38            len: 0,
39        }
40    }
41
42    /// Returns the number of currently stored elements.
43    #[must_use]
44    pub fn len(&self) -> usize {
45        self.len
46    }
47
48    /// Returns the maximum number of elements the buffer can store.
49    #[must_use]
50    pub fn capacity(&self) -> usize {
51        self.capacity
52    }
53
54    /// Returns the current read index.
55    #[must_use]
56    pub fn get_idx(&self) -> usize {
57        self.get_idx
58    }
59
60    /// Returns the current write index.
61    #[must_use]
62    pub fn put_idx(&self) -> usize {
63        self.put_idx
64    }
65
66    /// Returns `true` when the buffer has no readable elements.
67    #[must_use]
68    pub fn is_empty(&self) -> bool {
69        self.get_idx == self.put_idx && self.len == 0
70    }
71
72    /// Returns an iterator over readable elements in logical queue order.
73    #[must_use]
74    pub fn iter(&self) -> Iter<'_, T> {
75        if self.is_empty() {
76            return Iter([].iter().chain([].iter()));
77        }
78
79        let (first, second) = self.as_slice();
80
81        let second_iter = match second {
82            Some(slice) => slice.iter(),
83            None => [].iter(),
84        };
85
86        Iter(first.iter().chain(second_iter))
87    }
88
89    /// Returns a mutable iterator over readable elements in logical queue order.
90    #[must_use]
91    pub fn iter_mut(&mut self) -> IterMut<'_, T> {
92        if self.is_empty() {
93            return IterMut([].iter_mut().chain([].iter_mut()));
94        }
95
96        let (first, second) = self.as_mut_slice();
97
98        let second_iter = match second {
99            Some(slice) => slice.iter_mut(),
100            None => [].iter_mut(),
101        };
102
103        IterMut(first.iter_mut().chain(second_iter))
104    }
105
106    // into_iter is implemented via IntoIterator trait below
107
108    /// Discards `n` elements from the front of the buffer without copying them.
109    ///
110    /// If `n` exceeds the number of available elements, all elements are discarded.
111    pub fn consume(&mut self, n: usize) {
112        let n = n.min(self.len);
113        self.get_idx = (self.get_idx + n) % self.capacity;
114        self.len -= n;
115    }
116
117    /// Returns `true` when all capacity is occupied.
118    #[must_use]
119    pub fn full(&self) -> bool {
120        self.get_idx == self.put_idx && self.len == self.capacity
121    }
122
123    /// Returns a shared reference to the element at the current write index.
124    ///
125    /// This does not modify buffer state.
126    #[must_use]
127    pub fn back(&self) -> Option<&T> {
128        if self.is_empty() {
129            None
130        } else {
131            Some(&self.data[(self.put_idx + self.capacity - 1) % self.capacity])
132        }
133    }
134
135    /// Returns a mutable reference to the element at the current write index - 1.
136    ///
137    /// This does not modify indices or length.
138    #[must_use]
139    pub fn back_mut(&mut self) -> Option<&mut T> {
140        if self.is_empty() {
141            None
142        } else {
143            Some(&mut self.data[(self.put_idx + self.capacity - 1) % self.capacity])
144        }
145    }
146
147    /// Returns a shared reference to the front readable element.
148    #[must_use]
149    pub fn front(&self) -> Option<&T> {
150        if self.is_empty() {
151            None
152        } else {
153            Some(&self.data[self.get_idx])
154        }
155    }
156
157    /// Returns a mutable reference to the front readable element.
158    #[must_use]
159    pub fn front_mut(&mut self) -> Option<&mut T> {
160        if self.is_empty() {
161            None
162        } else {
163            let idx = self.get_idx;
164            Some(&mut self.data[idx])
165        }
166    }
167
168    /// Enqueues a single element.
169    ///
170    /// When the buffer is full, the oldest element is overwritten.
171    pub fn enqueue(&mut self, data: T) {
172        if self.full() {
173            self.get_idx = (self.get_idx + 1) % self.capacity;
174        } else {
175            self.len += 1;
176        }
177
178        self.data[self.put_idx] = data;
179        self.put_idx = (self.put_idx + 1) % self.capacity;
180    }
181
182    /// Dequeues and returns the front element, or `None` if empty.
183    #[must_use]
184    pub fn dequeue(&mut self) -> Option<T> {
185        if self.is_empty() {
186            return None;
187        }
188
189        let item = self.data[self.get_idx];
190        self.get_idx = (self.get_idx + 1) % self.capacity;
191        self.len -= 1;
192
193        Some(item)
194    }
195
196    /// Advances the write cursor by `size` slots as if `size` items were written.
197    ///
198    /// If this exceeds available space, the oldest elements are dropped.
199    pub fn advance(&mut self, size: usize) {
200        let size = size.min(self.capacity);
201
202        let new_put_idx = (self.put_idx + size) % self.capacity;
203        let remaining_space = self.capacity - self.len;
204        let offset = size.saturating_sub(remaining_space);
205        let new_get_idx = (self.get_idx + offset) % self.capacity;
206
207        self.get_idx = new_get_idx;
208        self.put_idx = new_put_idx;
209        self.len = (self.len + size).min(self.capacity);
210    }
211
212    /// Enqueues a slice of elements preserving order.
213    ///
214    /// This is the recommended high-throughput API for streaming workloads.
215    ///
216    /// If `data` is larger than capacity, only the last `capacity` elements are kept.
217    pub fn enqueue_slice(&mut self, data: &[T]) {
218        match data.len().cmp(&self.capacity) {
219            std::cmp::Ordering::Less => {
220                let new_put_idx = (self.put_idx + data.len()) % self.capacity;
221                let remaining_space = self.capacity - self.len;
222                let offset = data.len().saturating_sub(remaining_space);
223                let new_get_idx = (self.get_idx + offset) % self.capacity;
224
225                //we are wrapped now
226                if new_put_idx < self.put_idx {
227                    //copy from put to the end
228                    let middle = self.capacity - self.put_idx;
229                    self.data[self.put_idx..].copy_from_slice(&data[0..middle]);
230                    self.data[..new_put_idx].copy_from_slice(&data[middle..]);
231                } else {
232                    self.data[self.put_idx..self.put_idx + data.len()].copy_from_slice(data);
233                }
234
235                self.get_idx = new_get_idx;
236                self.put_idx = new_put_idx;
237                self.len = (self.len + data.len()).min(self.capacity);
238            }
239            std::cmp::Ordering::Equal => {
240                self.data.copy_from_slice(data);
241                self.put_idx = 0;
242                self.get_idx = 0;
243                self.len = data.len();
244            }
245            std::cmp::Ordering::Greater => {
246                let new_data_len = data.len();
247                let start = new_data_len - self.capacity;
248                let new_data = &data[start..new_data_len];
249                self.enqueue_slice(new_data);
250            }
251        }
252    }
253
254    /// Dequeues up to `dequeue_size` elements into `out_buffer`.
255    ///
256    /// This is the recommended high-throughput API for draining buffered data.
257    ///
258    /// Returns the number of elements actually written to `out_buffer`.
259    ///
260    /// # Panics
261    ///
262    /// Panics if `out_buffer.len() < dequeue_size`.
263    #[must_use]
264    pub fn dequeue_slice(&mut self, out_buffer: &mut [T], dequeue_size: usize) -> usize {
265        if self.is_empty() {
266            return 0;
267        }
268
269        assert!(
270            out_buffer.len() >= dequeue_size,
271            "Dequeue size cannot be greater than out buffer"
272        );
273
274        match dequeue_size.cmp(&self.len) {
275            std::cmp::Ordering::Less => {
276                let new_get_idx = (self.get_idx + dequeue_size) % self.capacity;
277
278                if new_get_idx < self.get_idx {
279                    let middle = self.capacity - self.get_idx;
280                    out_buffer[0..middle].copy_from_slice(&self.data[self.get_idx..]);
281                    out_buffer[middle..dequeue_size].copy_from_slice(&self.data[..new_get_idx]);
282                } else {
283                    out_buffer[0..dequeue_size]
284                        .copy_from_slice(&self.data[self.get_idx..new_get_idx]);
285                }
286
287                self.get_idx = new_get_idx;
288                self.len -= dequeue_size;
289
290                dequeue_size
291            }
292            std::cmp::Ordering::Equal => {
293                if self.get_idx >= self.put_idx {
294                    let middle = self.capacity - self.get_idx;
295                    out_buffer[0..middle].copy_from_slice(&self.data[self.get_idx..]);
296                    out_buffer[middle..dequeue_size].copy_from_slice(&self.data[..self.put_idx]);
297                } else {
298                    out_buffer[0..dequeue_size]
299                        .copy_from_slice(&self.data[self.get_idx..self.put_idx]);
300                }
301                self.get_idx = self.put_idx;
302                self.len = 0;
303                dequeue_size
304            }
305            std::cmp::Ordering::Greater => {
306                self.dequeue_slice(&mut out_buffer[0..self.len], self.len)
307            }
308        }
309    }
310
311    /// Returns a contiguous slice of `len` elements starting at `offset` from the read cursor,
312    /// or `None` if the range wraps around the buffer boundary.
313    ///
314    /// Does **not** consume any elements.
315    #[must_use]
316    pub fn try_peek_slice(&self, offset: usize, len: usize) -> Option<&[T]> {
317        if offset + len > self.len {
318            return None;
319        }
320        let start = (self.get_idx + offset) % self.capacity;
321        let end = start + len;
322        if end <= self.capacity {
323            Some(&self.data[start..end])
324        } else {
325            None
326        }
327    }
328
329    /// Copies readable elements into `out` starting at logical `offset`.
330    ///
331    /// This method does not modify buffer state and can be used to inspect
332    /// bytes before deciding whether to consume them.
333    ///
334    /// Returns the number of copied elements (`out.len()`) on success, or `0`
335    /// if the requested range is out of bounds or the buffer is empty.
336    #[must_use]
337    pub fn copy_out(&self, offset: usize, out: &mut [T]) -> usize {
338        if self.is_empty() {
339            return 0;
340        }
341
342        if offset + out.len() > self.len {
343            return 0;
344        }
345
346        let len = out.len();
347        let start = (self.get_idx + offset) % self.capacity;
348        let end = start + len;
349        if end <= self.capacity {
350            out.copy_from_slice(&self.data[start..end]);
351        } else {
352            let middle = self.capacity - start;
353            let wrapped_end = end - self.capacity;
354            out[..middle].copy_from_slice(&self.data[start..]);
355            out[middle..].copy_from_slice(&self.data[..wrapped_end]);
356        }
357
358        out.len()
359    }
360
361    /// Returns readable data as one contiguous slice plus an optional wrapped slice.
362    #[must_use]
363    pub fn as_slice(&self) -> (&[T], Option<&[T]>) {
364        if self.get_idx < self.put_idx {
365            (&self.data[self.get_idx..self.put_idx], None)
366        } else {
367            (&self.data[self.get_idx..], Some(&self.data[..self.put_idx]))
368        }
369    }
370
371    /// Returns mutable readable data as one contiguous slice plus an optional wrapped slice.
372    #[must_use]
373    pub fn as_mut_slice(&mut self) -> (&mut [T], Option<&mut [T]>) {
374        if self.get_idx < self.put_idx {
375            (&mut self.data[self.get_idx..self.put_idx], None)
376        } else {
377            let (left, right) = self.data.split_at_mut(self.get_idx);
378            (right, Some(&mut left[..self.put_idx]))
379        }
380    }
381}
382impl<T> IntoIterator for BoundedRingBuffer<T>
383where
384    T: Copy + Default,
385{
386    type Item = T;
387    type IntoIter = IntoIter<T>;
388
389    fn into_iter(self) -> Self::IntoIter {
390        IntoIter(self)
391    }
392}
393
394impl<'a, T> IntoIterator for &'a BoundedRingBuffer<T>
395where
396    T: Copy + Default,
397{
398    type Item = &'a T;
399    type IntoIter = Iter<'a, T>;
400
401    fn into_iter(self) -> Self::IntoIter {
402        self.iter()
403    }
404}
405
406impl<'a, T> IntoIterator for &'a mut BoundedRingBuffer<T>
407where
408    T: Copy + Default,
409{
410    type Item = &'a mut T;
411    type IntoIter = IterMut<'a, T>;
412
413    fn into_iter(self) -> Self::IntoIter {
414        self.iter_mut()
415    }
416}