use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;
use bun_collections::LinearFifo;
use bun_collections::linear_fifo::{DynamicBuffer, LinearFifoBuffer, SliceBuffer, StaticBuffer};
use crate::Condition;
use crate::Mutex;
#[derive(thiserror::Error, strum::IntoStaticStr, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelError {
#[error("Closed")]
Closed,
#[error("OutOfMemory")]
OutOfMemory,
}
bun_core::oom_from_alloc!(ChannelError);
bun_core::named_error_set!(ChannelError);
pub struct Channel<T, B: LinearFifoBuffer<T> = DynamicBuffer<T>> {
mutex: Mutex,
putters: Condition,
getters: Condition,
buffer: UnsafeCell<LinearFifo<T, B>>,
is_closed: Cell<bool>,
}
unsafe impl<T: Send, B: LinearFifoBuffer<T>> Send for Channel<T, B> {}
unsafe impl<T: Send, B: LinearFifoBuffer<T>> Sync for Channel<T, B> {}
impl<T: Copy, const N: usize> Channel<T, StaticBuffer<T, N>> {
#[inline]
pub fn init_static() -> Self {
Self::with_buffer(LinearFifo::<T, StaticBuffer<T, N>>::init())
}
}
impl<'a, T: Copy> Channel<T, SliceBuffer<'a, T>> {
#[inline]
pub fn init_slice(buf: &'a mut [T]) -> Self {
Self::with_buffer(LinearFifo::<T, SliceBuffer<'a, T>>::init(buf))
}
}
impl<T: Copy> Channel<T, DynamicBuffer<T>> {
#[inline]
pub fn init_dynamic() -> Self {
Self::with_buffer(LinearFifo::<T, DynamicBuffer<T>>::init())
}
}
impl<T: Copy, B: LinearFifoBuffer<T>> Channel<T, B> {
fn with_buffer(buffer: LinearFifo<T, B>) -> Self {
Self {
mutex: Mutex::default(),
putters: Condition::default(),
getters: Condition::default(),
buffer: UnsafeCell::new(buffer),
is_closed: Cell::new(false),
}
}
pub fn close(&self) {
let _guard = self.mutex.lock_guard();
if self.is_closed.get() {
return;
}
self.is_closed.set(true);
self.putters.broadcast();
self.getters.broadcast();
}
pub fn try_write_item(&self, item: T) -> Result<bool, ChannelError> {
let wrote = self.write(core::slice::from_ref(&item))?;
Ok(wrote == 1)
}
pub fn write_item(&self, item: T) -> Result<(), ChannelError> {
self.write_all(core::slice::from_ref(&item))
}
pub fn write(&self, items: &[T]) -> Result<usize, ChannelError> {
self.write_items(items, false)
}
pub fn try_read_item(&self) -> Result<Option<T>, ChannelError> {
let mut items: [MaybeUninit<T>; 1] = [MaybeUninit::uninit()];
let slice = unsafe { &mut *items.as_mut_ptr().cast::<[T; 1]>() };
if self.read(slice)? != 1 {
return Ok(None);
}
Ok(Some(unsafe { items[0].assume_init_read() }))
}
pub fn read_item(&self) -> Result<T, ChannelError> {
let mut items: [MaybeUninit<T>; 1] = [MaybeUninit::uninit()];
let slice = unsafe { &mut *items.as_mut_ptr().cast::<[T; 1]>() };
self.read_all(slice)?;
Ok(unsafe { items[0].assume_init_read() })
}
pub fn read(&self, items: &mut [T]) -> Result<usize, ChannelError> {
self.read_items(items, false)
}
pub fn write_all(&self, items: &[T]) -> Result<(), ChannelError> {
let n = self.write_items(items, true)?;
debug_assert!(n == items.len());
Ok(())
}
pub fn read_all(&self, items: &mut [T]) -> Result<(), ChannelError> {
let n = self.read_items(items, true)?;
debug_assert!(n == items.len());
Ok(())
}
fn write_items(&self, items: &[T], should_block: bool) -> Result<usize, ChannelError> {
let _guard = self.mutex.lock_guard();
let mut pushed: usize = 0;
while pushed < items.len() {
let did_push = 'blk: {
if self.is_closed.get() {
return Err(ChannelError::Closed);
}
let buffer = unsafe { &mut *self.buffer.get() };
match buffer.write(items) {
Ok(()) => {}
Err(err) => {
if B::DYNAMIC {
return Err(err.into());
}
break 'blk false;
}
}
self.getters.signal();
break 'blk true;
};
if did_push {
pushed += 1;
} else if should_block {
self.putters.wait(&self.mutex);
} else {
break;
}
}
Ok(pushed)
}
fn read_items(&self, items: &mut [T], should_block: bool) -> Result<usize, ChannelError> {
let _guard = self.mutex.lock_guard();
let mut popped: usize = 0;
while popped < items.len() {
let new_item: Option<T> = 'blk: {
let buffer = unsafe { &mut *self.buffer.get() };
if buffer.readable_length() == 0 {
if self.is_closed.get() {
return Err(ChannelError::Closed);
}
break 'blk None;
}
let item = buffer.read_item();
self.putters.signal();
break 'blk item;
};
if let Some(item) = new_item {
items[popped] = item;
popped += 1;
} else if should_block {
self.getters.wait(&self.mutex);
} else {
break;
}
}
Ok(popped)
}
}