pub const MINI: usize = 4;
pub const MEDIUM: usize = 32;
pub const LARGE: usize = 128;
pub const HUGE: usize = 8192;
pub(crate) trait ChunkQueue {
fn pop(&mut self) -> Option<u64>;
fn push(&mut self, val: u64) -> Result<(), &'static str>;
#[allow(dead_code)]
fn is_empty(&self) -> bool;
#[allow(dead_code)]
fn is_full(&self) -> bool;
#[allow(dead_code)]
fn capacity(&self) -> usize;
}
struct RingState { head: usize, tail: usize, len: usize }
pub(crate) struct RingInner<const N: usize> {
buf: Box<[u64; N]>,
state: RingState,
}
impl<const N: usize> RingInner<N> {
fn new() -> Self {
Self {
buf: Box::new([0u64; N]),
state: RingState { head: 0, tail: 0, len: 0 },
}
}
fn pop(&mut self) -> Option<u64> {
if self.state.len == 0 { return None; }
let val = self.buf[self.state.tail];
self.state.tail = (self.state.tail + 1) % N;
self.state.len -= 1;
Some(val)
}
fn push(&mut self, val: u64) -> Result<(), &'static str> {
if self.state.len == N { return Err("ring full"); }
self.buf[self.state.head] = val;
self.state.head = (self.state.head + 1) % N;
self.state.len += 1;
Ok(())
}
fn is_empty(&self) -> bool { self.state.len == 0 }
fn is_full(&self) -> bool { self.state.len == N }
fn capacity(&self) -> usize { N }
}
pub(crate) enum RingBuffer {
Mini(RingInner<MINI>),
Medium(RingInner<MEDIUM>),
Large(RingInner<LARGE>),
Huge(RingInner<HUGE>),
}
impl RingBuffer {
pub fn new(max_chunks: usize) -> Self {
if max_chunks <= MINI { RingBuffer::Mini(RingInner::new()) }
else if max_chunks <= MEDIUM { RingBuffer::Medium(RingInner::new()) }
else if max_chunks <= LARGE { RingBuffer::Large(RingInner::new()) }
else { RingBuffer::Huge(RingInner::new()) }
}
}
macro_rules! dispatch {
($self:expr, $method:ident $(, $arg:expr)*) => {
match $self {
RingBuffer::Mini(r) => r.$method($($arg),*),
RingBuffer::Medium(r) => r.$method($($arg),*),
RingBuffer::Large(r) => r.$method($($arg),*),
RingBuffer::Huge(r) => r.$method($($arg),*),
}
};
}
impl ChunkQueue for RingBuffer {
fn pop(&mut self) -> Option<u64> { dispatch!(self, pop) }
fn push(&mut self, v: u64) -> Result<(), &'static str> { dispatch!(self, push, v) }
fn is_empty(&self) -> bool { dispatch!(self, is_empty) }
fn is_full(&self) -> bool { dispatch!(self, is_full) }
fn capacity(&self) -> usize { dispatch!(self, capacity) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fifo_order_and_empty_full_transitions() {
let mut r: RingInner<MINI> = RingInner::new();
assert!(r.is_empty());
assert!(!r.is_full());
assert_eq!(r.pop(), None);
for v in 0..MINI as u64 {
r.push(v).expect("push within capacity");
}
assert!(r.is_full());
assert_eq!(r.push(99), Err("ring full"));
for v in 0..MINI as u64 {
assert_eq!(r.pop(), Some(v));
}
assert!(r.is_empty());
assert_eq!(r.pop(), None);
}
#[test]
fn head_tail_wraparound_preserves_fifo() {
let mut r: RingInner<MINI> = RingInner::new();
let mut next: u64 = 0;
for _cycle in 0..10 {
r.push(next).unwrap();
next += 1;
r.push(next).unwrap();
next += 1;
r.push(next).unwrap();
next += 1;
assert_eq!(r.pop(), Some(next - 3));
assert_eq!(r.pop(), Some(next - 2));
assert_eq!(r.pop(), Some(next - 1));
assert!(r.is_empty());
}
}
#[test]
fn ringbuffer_selects_smallest_fitting_variant() {
for &max in &[MINI, MINI + 1, MEDIUM, MEDIUM + 1, LARGE, LARGE + 1] {
let mut rb = RingBuffer::new(max);
assert!(rb.capacity() >= max, "variant too small for {max}");
assert!(rb.is_empty());
rb.push(7).unwrap();
assert!(!rb.is_empty());
assert_eq!(rb.pop(), Some(7));
assert!(rb.is_empty());
}
}
}