Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy-zoomies

gatling/src/revolver/ring.rs

/// Capacities for the statically-sized ring variants.
/// Each variant covers a range of `max_chunks` values.
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
    }
}

/// A free-list ring buffer backed by a fixed-size const-generic array.
/// Dispatches to the smallest variant that fits `max_chunks`.
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());
        // Pushing into a full ring is refused, not silently overwritten.
        assert_eq!(r.push(99), Err("ring full"));

        // FIFO: values come back in insertion order.
        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() {
        // Repeatedly fill-then-drain so head/tail wrap past the end of the
        // backing array many times; FIFO order must hold every cycle.
        let mut r: RingInner<MINI> = RingInner::new();
        let mut next: u64 = 0;
        for _cycle in 0..10 {
            // Push a partial batch, pop part of it — staggers head/tail so the
            // next full fill straddles the array's wrap point.
            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() {
        // Capacity dispatch: each variant must report a capacity that actually
        // holds `max_chunks`, and round-trip a value.
        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());
        }
    }
}