Skip to content

Instantly share code, notes, and snippets.

@Roms1383
Last active August 1, 2023 03:06
Show Gist options
  • Save Roms1383/ee41bdba1ee87d22f7421eda8592ca4d to your computer and use it in GitHub Desktop.
Save Roms1383/ee41bdba1ee87d22f7421eda8592ca4d to your computer and use it in GitHub Desktop.
Tim McNamara's video on Ring Buffer
use std::marker::PhantomData;
/// index which wraps around buffer length,
/// and might be initialized or not
#[derive(Debug, Clone, Copy)]
pub struct Idx<const N: usize>(Option<usize>);
impl<const N: usize> Idx<N> {
/// return the next index, wrapped around buffer length,
/// or `0`
pub fn next(&self) -> usize {
match self.0 {
Some(x) if x + 1 != N => x + 1,
_ => 0,
}
}
pub fn is_none(&self) -> bool {
self.0.is_none()
}
pub fn is_some(&self) -> bool {
self.0.is_some()
}
pub fn as_usize(&self) -> usize {
self.0.unwrap_or(0)
}
}
/// a bounded buffer (which cannot be overwritten)
#[derive(Debug)]
pub struct Bounded;
#[derive(Debug)]
/// an unbounded buffer (which can be overwritten)
pub struct Unbounded;
mod sealed {
use crate::{Bounded, Unbounded};
pub trait Sealed {}
impl Sealed for Bounded {}
impl Sealed for Unbounded {}
}
#[derive(Debug, Clone)]
pub struct RingBuffer<T: Clone, S: sealed::Sealed, const N: usize> {
sectors: Box<[Option<T>; N]>,
took: Idx<N>,
put: Idx<N>,
_ghost: PhantomData<S>,
}
#[derive(Debug, PartialEq)]
pub enum RingBufferError {
Full,
}
impl<T: Clone, S: sealed::Sealed, const N: usize> RingBuffer<T, S, N> {
pub fn new() -> Self {
let vec = vec![None; N];
let boxed_slice: Box<[Option<T>]> = vec.into_boxed_slice();
// SAFETY: nobody has a chance to touch it in-between
let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [Option<T>; N];
let sectors = unsafe { Box::from_raw(ptr) };
Self {
sectors,
took: Idx(None),
put: Idx(None),
_ghost: PhantomData::<S>,
}
}
/// if next slot is occupied,
/// then buffer is full
pub fn full(&self) -> bool {
self.occupied(self.put.next())
}
/// if we took and put equally,
/// or haven't written anything yet,
/// then buffer is empty.
pub fn empty(&self) -> bool {
self.put.as_usize() == self.took.as_usize()
}
/// pull a value from the buffer,
/// leaving slot empty
pub fn pull(&mut self) -> Option<T> {
let next = self.took.next();
// SAFETY: we are in bounds, pull is sequential
// and reader does not get incremented when there's no value
let value = unsafe { self.sectors.get_unchecked_mut(next) }.take();
if value.is_some() {
self.took = Idx(Some(next));
}
value
}
/// if slot is occupied at index
pub fn occupied(&self, at: usize) -> bool {
self.sectors.get(at).map(|x| x.is_some()).unwrap_or(false)
}
/// if slot is vacant at index
pub fn vacant(&self, at: usize) -> bool {
self.sectors.get(at).map(|x| x.is_none()).unwrap_or(false)
}
}
impl<T: Clone, const N: usize> RingBuffer<T, Bounded, N> {
/// same implementation as `Unbounded`,
/// but do not overwrite.
pub fn push(&mut self, value: T) -> Result<usize, RingBufferError> {
if self.full() {
return Err(RingBufferError::Full);
}
let next = self.put.next();
if let Some(sector) = self.sectors.get_mut(next) {
*sector = Some(value);
self.put = Idx(Some(next));
}
// SAFETY: we are in bounds, push is sequential
// and reader only increment when overwriting past it
if self.took.is_some() && next == self.took.next() {
self.took = Idx(Some(self.took.next()));
}
Ok(next)
}
}
/// same implementation as `Bounded`,
/// but overwrites.
impl<T: Clone, const N: usize> RingBuffer<T, Unbounded, N> {
pub fn push(&mut self, value: T) -> usize {
let next = self.put.next();
if let Some(sector) = self.sectors.get_mut(next) {
*sector = Some(value);
self.put = Idx(Some(next));
}
// SAFETY: we are in bounds, push is sequential
// and reader only increment when overwriting past it
if self.took.is_some() && next == self.took.next() {
self.took = Idx(Some(self.took.next()));
}
next
}
}
#[cfg(test)]
mod tests {
use crate::{Bounded, RingBufferError, Unbounded};
use super::RingBuffer;
#[doc = "from Wikipedia's example"]
#[test]
fn unbounded() {
let mut ring: RingBuffer<&'static str, Unbounded, 7> = RingBuffer::new();
assert_eq!(ring.pull(), None);
ring.push("1");
ring.push("2");
ring.push("3");
let first = ring.pull();
let second = ring.pull();
assert_eq!(first, Some("1"));
assert_eq!(second, Some("2"));
ring.push("4");
ring.push("5");
ring.push("6");
ring.push("7");
ring.push("8");
ring.push("9");
ring.push("A");
ring.push("B");
let third = ring.pull();
let fourth = ring.pull();
assert_eq!(third, Some("5"));
assert_eq!(fourth, Some("6"));
}
#[doc = "from Wikipedia's example"]
#[test]
fn bounded() {
let mut ring: RingBuffer<&'static str, Bounded, 7> = RingBuffer::new();
assert_eq!(ring.pull(), None);
let _ = ring.push("1");
let _ = ring.push("2");
let _ = ring.push("3");
let first = ring.pull();
let second = ring.pull();
assert_eq!(first, Some("1"));
assert_eq!(second, Some("2"));
let _ = ring.push("4");
let _ = ring.push("5");
let _ = ring.push("6");
let _ = ring.push("7");
let _ = ring.push("8");
let _ = ring.push("9");
let first = ring.push("A");
let second = ring.push("B");
assert_eq!(first, Err(RingBufferError::Full));
assert_eq!(second, Err(RingBufferError::Full));
let third = ring.pull();
let fourth = ring.pull();
assert_eq!(third, Some("3"));
assert_eq!(fourth, Some("4"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment