use crossbeam_utils::CachePadded;
use std::cell::{Cell, UnsafeCell};
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
struct RingBuffer<T, const CAP: usize> {
head: CachePadded<AtomicUsize>,
tail: CachePadded<AtomicUsize>,
slots: Box<[UnsafeCell<MaybeUninit<T>>]>,
}
unsafe impl<T: Send, const CAP: usize> Send for RingBuffer<T, CAP> {}
unsafe impl<T: Send, const CAP: usize> Sync for RingBuffer<T, CAP> {}
impl<T, const CAP: usize> Drop for RingBuffer<T, CAP> {
fn drop(&mut self) {
if std::mem::needs_drop::<T>() {
let head = self.head.load(Ordering::Relaxed);
let tail = self.tail.load(Ordering::Relaxed);
let count = head.wrapping_sub(tail);
for i in 0..count {
let idx = tail.wrapping_add(i) & (CAP - 1);
unsafe {
let slot = self.slots[idx].get();
(*slot).assume_init_drop();
}
}
}
}
}
pub struct SpscProducer<T, const CAP: usize> {
inner: Arc<RingBuffer<T, CAP>>,
cached_tail: Cell<usize>,
_not_sync: PhantomData<Cell<()>>,
}
pub struct SpscConsumer<T, const CAP: usize> {
inner: Arc<RingBuffer<T, CAP>>,
cached_head: Cell<usize>,
_not_sync: PhantomData<Cell<()>>,
}
impl<T, const CAP: usize> std::fmt::Debug for SpscProducer<T, CAP> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpscProducer")
.field("capacity", &CAP)
.finish()
}
}
impl<T, const CAP: usize> std::fmt::Debug for SpscConsumer<T, CAP> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SpscConsumer")
.field("capacity", &CAP)
.finish()
}
}
pub fn spsc_pair<T, const CAP: usize>() -> (SpscProducer<T, CAP>, SpscConsumer<T, CAP>) {
assert!(CAP > 0, "capacity must be greater than zero");
assert!(CAP.is_power_of_two(), "capacity must be a power of two");
let mut v: Vec<UnsafeCell<MaybeUninit<T>>> = Vec::with_capacity(CAP);
unsafe {
v.set_len(CAP);
}
let slots = v.into_boxed_slice();
let inner = Arc::new(RingBuffer {
head: CachePadded::new(AtomicUsize::new(0)),
tail: CachePadded::new(AtomicUsize::new(0)),
slots,
});
let producer = SpscProducer {
inner: Arc::clone(&inner),
cached_tail: Cell::new(0),
_not_sync: PhantomData,
};
let consumer = SpscConsumer {
inner,
cached_head: Cell::new(0),
_not_sync: PhantomData,
};
(producer, consumer)
}
impl<T, const CAP: usize> SpscProducer<T, CAP> {
pub fn push(&self, item: T) -> Result<(), T> {
let head = self.inner.head.load(Ordering::Relaxed);
let mut tail = self.cached_tail.get();
if head.wrapping_sub(tail) >= CAP {
tail = self.inner.tail.load(Ordering::Acquire);
self.cached_tail.set(tail);
if head.wrapping_sub(tail) >= CAP {
return Err(item);
}
}
let idx = head & (CAP - 1);
unsafe {
let slot = self.inner.slots[idx].get();
(*slot).write(item);
}
self.inner
.head
.store(head.wrapping_add(1), Ordering::Release);
Ok(())
}
pub fn capacity(&self) -> usize {
CAP
}
}
impl<T, const CAP: usize> SpscConsumer<T, CAP> {
pub fn pop(&self) -> Option<T> {
let tail = self.inner.tail.load(Ordering::Relaxed);
let mut head = self.cached_head.get();
if head == tail {
head = self.inner.head.load(Ordering::Acquire);
self.cached_head.set(head);
if head == tail {
return None;
}
}
let idx = tail & (CAP - 1);
let item = unsafe {
let slot = self.inner.slots[idx].get();
(*slot).assume_init_read()
};
self.inner
.tail
.store(tail.wrapping_add(1), Ordering::Release);
Some(item)
}
pub fn drain_batch(&self, out: &mut Vec<T>, max: usize) -> usize {
if max == 0 {
return 0;
}
let tail = self.inner.tail.load(Ordering::Relaxed);
let mut head = self.cached_head.get();
if head.wrapping_sub(tail) < max {
head = self.inner.head.load(Ordering::Acquire);
self.cached_head.set(head);
}
let available = head.wrapping_sub(tail);
let to_drain = available.min(max);
if to_drain == 0 {
return 0;
}
out.reserve(to_drain);
for i in 0..to_drain {
let idx = tail.wrapping_add(i) & (CAP - 1);
let item = unsafe {
let slot = self.inner.slots[idx].get();
(*slot).assume_init_read()
};
out.push(item);
}
self.inner
.tail
.store(tail.wrapping_add(to_drain), Ordering::Release);
to_drain
}
pub fn is_empty(&self) -> bool {
let tail = self.inner.tail.load(Ordering::Relaxed);
let head = self.inner.head.load(Ordering::Acquire);
self.cached_head.set(head);
head == tail
}
}
impl<T, const CAP: usize> Drop for SpscConsumer<T, CAP> {
fn drop(&mut self) {
while self.pop().is_some() {}
}
}