use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
enum RingState {
Filling,
Full { cursor: usize },
}
impl RingState {
const fn is_full(&self) -> bool {
matches!(self, Self::Full { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RingBuffer<T> {
capacity: usize,
buffer: SmallVec<[T; 16]>,
state: RingState,
}
impl<T> RingBuffer<T> {
#[must_use]
pub fn new(capacity: usize) -> Self {
assert!(
capacity > 0,
"RingBuffer capacity must be strictly greater than 0. Got {capacity} <= 0."
);
Self {
capacity,
buffer: SmallVec::with_capacity(capacity),
state: RingState::Filling,
}
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub const fn is_full(&self) -> bool {
self.state.is_full()
}
pub fn push(&mut self, value: T) -> Option<T> {
match self.state {
RingState::Filling => {
self.buffer.push(value);
if self.buffer.len() == self.capacity {
self.state = RingState::Full { cursor: 0 };
}
None
}
RingState::Full { cursor } => {
let evicted_value = std::mem::replace(&mut self.buffer[cursor], value);
let next_cursor = if cursor + 1 == self.capacity {
0
} else {
cursor + 1
};
self.state = RingState::Full {
cursor: next_cursor,
};
Some(evicted_value)
}
}
}
pub fn clear(&mut self) {
self.buffer.clear();
self.state = RingState::Filling;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_starts_empty() {
let buffer: RingBuffer<i32> = RingBuffer::new(4);
assert!(buffer.is_empty());
assert!(!buffer.is_full());
assert_eq!(buffer.len(), 0);
assert_eq!(buffer.capacity(), 4);
}
#[test]
#[should_panic(expected = "capacity must be strictly greater than 0")]
fn new_zero_capacity_panics() {
drop(RingBuffer::<i32>::new(0));
}
#[test]
fn reports_full_exactly_at_capacity() {
let mut buffer = RingBuffer::new(3);
assert_eq!(buffer.push(1), None);
assert!(!buffer.is_full());
assert_eq!(buffer.push(2), None);
assert!(!buffer.is_full());
assert_eq!(buffer.push(3), None);
assert!(buffer.is_full());
assert_eq!(buffer.len(), 3);
}
#[test]
fn evicts_oldest_first_when_full() {
let mut buffer = RingBuffer::new(3);
for v in [1, 2, 3] {
assert_eq!(buffer.push(v), None);
}
assert_eq!(buffer.push(4), Some(1));
assert_eq!(buffer.push(5), Some(2));
assert_eq!(buffer.push(6), Some(3));
assert_eq!(buffer.push(7), Some(4));
assert_eq!(buffer.len(), 3);
assert_eq!(buffer.capacity(), 3);
}
#[test]
fn capacity_one_overwrites_on_every_push() {
let mut buffer = RingBuffer::new(1);
assert_eq!(buffer.push(10), None);
assert!(buffer.is_full());
assert_eq!(buffer.push(20), Some(10));
assert_eq!(buffer.push(30), Some(20));
assert_eq!(buffer.len(), 1);
}
#[test]
fn clear_resets_state_and_allows_refilling() {
let mut buffer = RingBuffer::new(2);
buffer.push(1);
buffer.push(2);
assert!(buffer.is_full());
buffer.clear();
assert!(buffer.is_empty());
assert!(!buffer.is_full());
assert_eq!(buffer.len(), 0);
assert_eq!(buffer.push(9), None);
assert_eq!(buffer.push(8), None);
assert_eq!(buffer.push(7), Some(9));
}
}