use core::mem::MaybeUninit;
use core::{cell::Cell, cell::UnsafeCell};
#[derive(Eq, PartialEq)]
pub struct Index<const RANGE: usize> {
cell: Cell<u32>,
}
#[derive(Debug)]
pub enum ErrCode {
BufFull,
BufEmpty,
}
impl<const N: usize> Index<N> {
const OK: () = assert!(N < (u32::MAX/2) as usize, "Ringbuf capacity must be < u32::MAX/2");
#[inline(always)]
pub fn wrap_inc(&self) {
let n = N as u32;
let val = self.cell.get().wrapping_add(1);
if !n.is_power_of_two() && val > 2 * n - 1 {
self.cell.set(val.wrapping_sub(2 * n));
} else {
self.cell.set(val);
}
}
#[inline(always)]
pub fn wrap_dist(&self, val: &Index<N>) -> u32 {
let n = N as u32;
let raw = self.cell.get().wrapping_sub(val.get());
if !n.is_power_of_two() {
if (raw as i32) < 0 {
return raw.wrapping_add(2 * n);
} else if raw > 2 * n - 1 {
return raw.wrapping_sub(2 * n);
}
}
raw
}
#[inline(always)]
pub fn mask(&self) -> u32 {
let n = N as u32;
let val = self.cell.get();
if n.is_power_of_two() {
val & (n - 1)
} else if val > n - 1 {
val - n
} else {
val
}
}
#[inline(always)]
pub fn get(&self) -> u32 {
self.cell.get()
}
#[allow(clippy::let_unit_value)]
#[inline(always)]
pub const fn new(val: u32) -> Self {
let _: () = Index::<N>::OK;
Index {
cell: Cell::new(val),
}
}
}
pub struct RingBufRef<T, const N: usize> {
rd_idx: Index<N>,
wr_idx: Index<N>,
buffer_ucell: [UnsafeCell<MaybeUninit<T>>; N],
}
unsafe impl<T, const N: usize> Sync for RingBufRef<T, N> {}
impl<T, const N: usize> RingBufRef<T, N> {
const OK: () = assert!(N > 0, "Ringbuf capacity must be larger than 0!");
const INIT_U: UnsafeCell<MaybeUninit<T>> = UnsafeCell::new(MaybeUninit::uninit());
pub const INIT_0: RingBufRef<T, N> = Self::new();
#[allow(clippy::let_unit_value)]
#[inline]
pub const fn new() -> Self {
let _: () = RingBufRef::<T, N>::OK;
RingBufRef {
rd_idx: Index::new(0),
wr_idx: Index::new(0),
buffer_ucell: [Self::INIT_U; N],
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.rd_idx == self.wr_idx
}
#[inline(always)]
pub fn len(&self) -> u32 {
self.wr_idx.wrap_dist(&self.rd_idx)
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.len() as usize == N
}
#[inline(always)]
pub fn capacity(&self) -> usize {
N
}
#[inline(always)]
pub fn writer_front(&self) -> Option<&mut T> {
if !self.is_full() {
let m: *mut MaybeUninit<T> = self.buffer_ucell[self.wr_idx.mask() as usize].get();
let t: &mut T = unsafe { &mut *(m as *mut T) };
Some(t)
} else {
None
}
}
#[inline(always)]
pub fn commit(&self) -> Result<(), ErrCode> {
if !self.is_full() {
self.wr_idx.wrap_inc();
Ok(())
} else {
Err(ErrCode::BufFull)
}
}
#[inline(always)]
pub fn push(&self, val: T) -> Result<(), ErrCode> {
if !self.is_full() {
unsafe {
(*self.buffer_ucell[self.wr_idx.mask() as usize].get()).write(val);
}
self.wr_idx.wrap_inc();
Ok(())
} else {
Err(ErrCode::BufFull)
}
}
#[inline(always)]
pub fn reader_front(&self) -> Option<&T> {
if self.is_empty() {
None
} else {
let x: *mut MaybeUninit<T> = self.buffer_ucell[self.rd_idx.mask() as usize].get();
let t: &T = unsafe { &*(x as *const T) };
Some(t)
}
}
#[inline(always)]
pub fn reader_front_mut(&self) -> Option<&mut T> {
if self.is_empty() {
None
} else {
let x: *mut MaybeUninit<T> = self.buffer_ucell[self.rd_idx.mask() as usize].get();
let t: &mut T = unsafe { &mut *(x as *mut T) };
Some(t)
}
}
#[inline(always)]
pub fn pop(&self) -> Result<(), ErrCode> {
if !self.is_empty() {
self.rd_idx.wrap_inc();
Ok(())
} else {
Err(ErrCode::BufEmpty)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
impl<T, const N: usize> RingBufRef<T, N> {
pub fn test_init_wr_rd(&self, val: u32) {
self.wr_idx.cell.set(val);
self.rd_idx.cell.set(val);
}
}
const CMD_Q_DEPTH: usize = 4;
pub struct SomeStruct {
id: u32,
}
pub struct Interface {
cmd_q: RingBufRef<SomeStruct, CMD_Q_DEPTH>,
}
const NUM_INTFS: usize = 2;
#[allow(clippy::declare_interior_mutable_const)]
const INTF_INIT: Interface = Interface {
cmd_q: RingBufRef::INIT_0,
};
static SHARED_INTF: [Interface; NUM_INTFS] = [INTF_INIT; NUM_INTFS];
fn test_operations<const N: usize>(rbufr1: RingBufRef<u32, N>, iter: usize) {
for i in 0..iter {
let loc = rbufr1.writer_front();
if let Some(v) = loc {
*v = i as u32;
}
assert!(rbufr1.commit().is_ok());
if let Some(v) = rbufr1.reader_front() {
assert!(*v == i as u32);
assert!(rbufr1.pop().is_ok());
}
}
assert!(rbufr1.reader_front().is_none());
for _ in 0..N {
assert!(rbufr1.writer_front().is_some());
assert!(rbufr1.commit().is_ok());
}
assert!(rbufr1.writer_front().is_none());
assert!(rbufr1.commit().is_err());
for _ in 0..N / 2 {
assert!(rbufr1.pop().is_ok());
}
for _ in 0..N / 2 {
assert!(rbufr1.writer_front().is_some());
assert!(rbufr1.commit().is_ok());
}
}
#[test]
fn validate_size() {
assert!(core::mem::size_of::<RingBufRef<u32, 16>>() == (4 + 4 + 16*4));
assert!(core::mem::size_of::<RingBufRef<u16, 16>>() == (4 + 4 + 16*2));
assert!(core::mem::size_of::<RingBufRef<u8, 32>>() == (4 + 4 + 32));
}
#[test]
fn power_of_two_len() {
let rbufr1: RingBufRef<u32, 16> = RingBufRef::new();
test_operations::<16>(rbufr1, 2 * 16 - 1 + 16 / 2);
}
#[test]
fn ping_pong() {
let rbufr1: RingBufRef<u32, 2> = RingBufRef::new();
test_operations::<2>(rbufr1, 2 * 2 - 1 + 2 / 2);
}
#[test]
fn single() {
let rbufr1: RingBufRef<u32, 1> = RingBufRef::new();
test_operations::<1>(rbufr1, 7);
}
#[test]
fn non_power_of_two_len() {
let rbufr1: RingBufRef<u32, 15> = RingBufRef::new();
test_operations::<15>(rbufr1, 2 * 15 - 1 + 15 / 2);
}
#[test]
fn power_of_two_len_wrap() {
let rbufr1: RingBufRef<u32, {(u16::MAX) as usize + 1}> = RingBufRef::new();
rbufr1.test_init_wr_rd(u32::MAX-2);
test_operations::<{(u16::MAX) as usize + 1}>(rbufr1, 32768);
}
#[test]
fn static_instance_example() {
let intf: &'static Interface = &SHARED_INTF[0];
let alloc_res = intf.cmd_q.writer_front();
if let Some(cmd) = alloc_res {
cmd.id = 42;
assert!(intf.cmd_q.commit().is_ok());
}
let cmd = intf.cmd_q.reader_front();
assert!(cmd.is_some());
assert!(cmd.unwrap().id == 42);
}
}