use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::err::{Error, Result};
use crate::sync::asm::fence::pause;
const CACHE_LINE: usize = 64;
#[repr(C)]
struct PoolSlot<T> {
next: AtomicUsize,
data: UnsafeCell<MaybeUninit<T>>,
}
#[repr(C, align(64))]
pub struct ObjectPool<T, const N: usize> {
free_head: AtomicUsize,
allocated: AtomicUsize,
_pad: [u8; CACHE_LINE - 16],
slots: [PoolSlot<T>; N],
}
unsafe impl<T: Send, const N: usize> Send for ObjectPool<T, N> {}
unsafe impl<T: Send, const N: usize> Sync for ObjectPool<T, N> {}
impl<T, const N: usize> ObjectPool<T, N> {
const NULL: usize = usize::MAX;
pub fn new() -> Self {
let mut slots: [MaybeUninit<PoolSlot<T>>; N] =
unsafe { MaybeUninit::uninit().assume_init() };
for (i, slot) in slots.iter_mut().enumerate() {
let next = if i + 1 < N { i + 1 } else { Self::NULL };
slot.write(PoolSlot {
next: AtomicUsize::new(next),
data: UnsafeCell::new(MaybeUninit::uninit()),
});
}
Self {
free_head: AtomicUsize::new(0),
allocated: AtomicUsize::new(0),
_pad: [0; CACHE_LINE - 16],
slots: unsafe { std::mem::transmute_copy(&slots) },
}
}
pub fn acquire(&self) -> Result<PoolHandle<'_, T, N>> {
loop {
let head = self.free_head.load(Ordering::Acquire);
if head == Self::NULL {
return Err(Error::OutOfMemory);
}
let next = self.slots[head].next.load(Ordering::Relaxed);
if self
.free_head
.compare_exchange_weak(head, next, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
self.allocated.fetch_add(1, Ordering::Relaxed);
return Ok(PoolHandle {
pool: self,
index: head,
});
}
pause();
}
}
pub fn try_acquire(&self) -> Option<PoolHandle<'_, T, N>> {
let head = self.free_head.load(Ordering::Acquire);
if head == Self::NULL {
return None;
}
let next = self.slots[head].next.load(Ordering::Relaxed);
if self
.free_head
.compare_exchange(head, next, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
self.allocated.fetch_add(1, Ordering::Relaxed);
Some(PoolHandle {
pool: self,
index: head,
})
} else {
None
}
}
fn release(&self, index: usize) {
loop {
let head = self.free_head.load(Ordering::Relaxed);
self.slots[index].next.store(head, Ordering::Relaxed);
if self
.free_head
.compare_exchange_weak(head, index, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
self.allocated.fetch_sub(1, Ordering::Relaxed);
return;
}
pause();
}
}
#[inline]
pub fn allocated(&self) -> usize {
self.allocated.load(Ordering::Relaxed)
}
#[inline]
pub fn available(&self) -> usize {
N - self.allocated()
}
#[inline]
pub fn capacity(&self) -> usize {
N
}
pub fn is_full(&self) -> bool {
self.free_head.load(Ordering::Relaxed) == Self::NULL
}
}
impl<T, const N: usize> Default for ObjectPool<T, N> {
fn default() -> Self {
Self::new()
}
}
pub struct PoolHandle<'a, T, const N: usize> {
pool: &'a ObjectPool<T, N>,
index: usize,
}
impl<T, const N: usize> PoolHandle<'_, T, N> {
pub fn write(&mut self, value: T) {
unsafe {
(*self.pool.slots[self.index].data.get()).write(value);
}
}
pub fn as_ptr(&self) -> NonNull<T> {
unsafe { NonNull::new_unchecked((*self.pool.slots[self.index].data.get()).as_mut_ptr()) }
}
pub fn get(&self) -> &T {
unsafe { (*self.pool.slots[self.index].data.get()).assume_init_ref() }
}
pub fn get_mut(&mut self) -> &mut T {
unsafe { (*self.pool.slots[self.index].data.get()).assume_init_mut() }
}
pub fn take(self) -> T {
let pool = self.pool;
let index = self.index;
let value = unsafe { (*pool.slots[index].data.get()).assume_init_read() };
std::mem::forget(self);
pool.release(index);
value
}
}
impl<T, const N: usize> Drop for PoolHandle<'_, T, N> {
fn drop(&mut self) {
self.pool.release(self.index);
}
}