use std::cell::UnsafeCell;
use std::fmt;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
pub const POOL_MAX_SLOTS: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum AudioBufferWriteError {
#[error(
"audio buffer write of {requested_samples} samples exceeds capacity {capacity_samples}"
)]
CapacityExceeded {
requested_samples: usize,
capacity_samples: usize,
},
}
pub struct AudioBufferPool {
slots: Box<[UnsafeCell<Box<[f32]>>]>,
shared_ref_counts: Box<[AtomicUsize]>,
slot_size: usize, free_mask: AtomicU64, acquire_failures: AtomicUsize,
}
unsafe impl Sync for AudioBufferPool {}
impl AudioBufferPool {
pub fn new(slot_count: usize, slot_size: usize) -> Arc<Self> {
assert!((1..=POOL_MAX_SLOTS).contains(&slot_count));
assert!(slot_size > 0);
let slots: Vec<_> = (0..slot_count)
.map(|_| UnsafeCell::new(vec![0.0f32; slot_size].into_boxed_slice()))
.collect();
let full_mask = if slot_count == 64 {
u64::MAX
} else {
(1u64 << slot_count) - 1
};
Arc::new(Self {
slots: slots.into_boxed_slice(),
shared_ref_counts: (0..slot_count)
.map(|_| AtomicUsize::new(0))
.collect::<Vec<_>>()
.into_boxed_slice(),
slot_size,
free_mask: AtomicU64::new(full_mask),
acquire_failures: AtomicUsize::new(0),
})
}
pub fn slot_size(&self) -> usize {
self.slot_size
}
pub fn slot_count(&self) -> usize {
self.slots.len()
}
pub fn acquire_failures(&self) -> usize {
self.acquire_failures.load(Ordering::Relaxed)
}
pub fn available_slots(&self) -> usize {
self.free_mask.load(Ordering::Acquire).count_ones() as usize
}
pub fn acquire(self: &Arc<Self>) -> Option<AudioBufferHandle> {
loop {
let mask = self.free_mask.load(Ordering::Acquire);
if mask == 0 {
self.acquire_failures.fetch_add(1, Ordering::Relaxed);
return None;
}
let idx = mask.trailing_zeros() as usize;
let bit = 1u64 << idx;
if self
.free_mask
.compare_exchange_weak(mask, mask & !bit, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
return Some(AudioBufferHandle {
pool: Arc::clone(self),
index: idx as u32,
len: self.slot_size as u32,
});
}
}
}
pub fn is_in_use(&self, index: u32) -> bool {
self.free_mask.load(Ordering::Acquire) & (1u64 << index) == 0
}
pub fn shared_ref_count(&self, index: u32) -> usize {
self.shared_ref_counts
.get(index as usize)
.map_or(0, |count| count.load(Ordering::Acquire))
}
#[cfg(test)]
pub(super) fn set_shared_ref_count_for_testing(&self, index: u32, count: usize) {
self.shared_ref_counts[index as usize].store(count, Ordering::Release);
}
#[cfg(test)]
pub(super) fn release_shared_for_testing(&self, index: u32) -> bool {
self.release_shared(index)
}
fn begin_shared(&self, index: u32) -> bool {
self.shared_ref_counts
.get(index as usize)
.is_some_and(|count| {
count
.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
})
}
fn try_retain_shared(&self, index: u32) -> bool {
let Some(count) = self.shared_ref_counts.get(index as usize) else {
return false;
};
let mut current = count.load(Ordering::Acquire);
loop {
if current == 0 || current == usize::MAX {
return false;
}
match count.compare_exchange_weak(
current,
current + 1,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return true,
Err(observed) => current = observed,
}
}
}
fn release_shared(&self, index: u32) -> bool {
let Some(count) = self.shared_ref_counts.get(index as usize) else {
return false;
};
let mut current = count.load(Ordering::Acquire);
loop {
if current == 0 {
return false;
}
match count.compare_exchange_weak(
current,
current - 1,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
if current == 1 {
self.release(index);
}
return true;
}
Err(observed) => current = observed,
}
}
}
fn release(&self, index: u32) {
self.free_mask.fetch_or(1u64 << index, Ordering::Release);
}
fn slot(&self, index: u32, len: u32) -> &[f32] {
assert!((index as usize) < self.slots.len() && (len as usize) <= self.slot_size);
let cell = unsafe { &*self.slots[index as usize].get() };
&cell[..len as usize]
}
unsafe fn slot_mut_ptr(&self, index: u32, len: u32) -> *mut [f32] {
assert!((index as usize) < self.slots.len() && (len as usize) <= self.slot_size);
let cell = unsafe { &mut *self.slots[index as usize].get() };
std::ptr::slice_from_raw_parts_mut(cell.as_mut_ptr(), len as usize)
}
}
pub struct AudioBufferHandle {
pool: Arc<AudioBufferPool>,
index: u32,
len: u32,
}
impl AudioBufferHandle {
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn index(&self) -> u32 {
self.index
}
pub fn as_slice(&self) -> &[f32] {
self.pool.slot(self.index, self.len)
}
pub fn as_mut_slice(&mut self) -> &mut [f32] {
unsafe { &mut *self.pool.slot_mut_ptr(self.index, self.len) }
}
pub fn try_set_len(&mut self, len: usize) -> Result<(), AudioBufferWriteError> {
if len > self.pool.slot_size() {
return Err(AudioBufferWriteError::CapacityExceeded {
requested_samples: len,
capacity_samples: self.pool.slot_size(),
});
}
self.len = len as u32;
Ok(())
}
pub fn try_copy_from_slice(&mut self, data: &[f32]) -> Result<(), AudioBufferWriteError> {
self.try_set_len(data.len())?;
self.as_mut_slice().copy_from_slice(data);
Ok(())
}
pub fn freeze(self) -> Result<SharedAudioBufferHandle, Self> {
if !self.pool.begin_shared(self.index) {
return Err(self);
}
let this = std::mem::ManuallyDrop::new(self);
let pool = unsafe { std::ptr::read(&this.pool) };
Ok(SharedAudioBufferHandle {
pool,
index: this.index,
len: this.len,
})
}
}
impl Drop for AudioBufferHandle {
fn drop(&mut self) {
if self.pool.shared_ref_count(self.index) == 0 {
self.pool.release(self.index);
}
}
}
impl fmt::Debug for AudioBufferHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AudioBufferHandle")
.field("index", &self.index)
.field("len", &self.len)
.finish()
}
}
pub struct SharedAudioBufferHandle {
pool: Arc<AudioBufferPool>,
index: u32,
len: u32,
}
impl SharedAudioBufferHandle {
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn index(&self) -> u32 {
self.index
}
pub fn as_slice(&self) -> &[f32] {
self.pool.slot(self.index, self.len)
}
pub fn try_clone(&self) -> Option<Self> {
if !self.pool.try_retain_shared(self.index) {
return None;
}
Some(Self {
pool: Arc::clone(&self.pool),
index: self.index,
len: self.len,
})
}
pub fn shared_ref_count(&self) -> usize {
self.pool.shared_ref_count(self.index)
}
}
impl Drop for SharedAudioBufferHandle {
fn drop(&mut self) {
self.pool.release_shared(self.index);
}
}
impl fmt::Debug for SharedAudioBufferHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SharedAudioBufferHandle")
.field("index", &self.index)
.field("len", &self.len)
.field("shared_ref_count", &self.shared_ref_count())
.finish()
}
}