use crate::allocator::{Allocator, StackAllocator};
use crate::config::PoolConfig;
use crate::error::{Error, Result};
use crate::handle::OwnedHandle;
use crate::traits::Poolable;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::ptr;
#[cfg(feature = "stats")]
use crate::stats::PoolStatistics;
pub struct FixedPool<T> {
storage: RefCell<Vec<MaybeUninit<T>>>,
allocator: RefCell<StackAllocator>,
capacity: usize,
#[allow(dead_code)]
config: PoolConfig<T>,
#[cfg(feature = "stats")]
stats: RefCell<crate::stats::StatisticsCollector>,
_marker: PhantomData<T>,
}
impl<T: Poolable> FixedPool<T> {
pub fn new(capacity: usize) -> Result<Self> {
let config = PoolConfig::builder().capacity(capacity).build()?;
Self::with_config(config)
}
pub fn with_config(config: PoolConfig<T>) -> Result<Self> {
let capacity = config.capacity();
let mut storage = Vec::with_capacity(capacity);
storage.resize_with(capacity, MaybeUninit::uninit);
let pool = Self {
storage: RefCell::new(storage),
allocator: RefCell::new(StackAllocator::new(capacity)),
capacity,
config,
#[cfg(feature = "stats")]
stats: RefCell::new(crate::stats::StatisticsCollector::new(capacity)),
_marker: PhantomData,
};
Ok(pool)
}
#[inline]
pub fn allocate(&self, mut value: T) -> Result<OwnedHandle<'_, T>> {
let index = self
.allocator
.borrow_mut()
.allocate()
.ok_or(Error::PoolExhausted {
capacity: self.capacity,
allocated: self.capacity,
})?;
value.on_acquire();
{
let mut storage = self.storage.borrow_mut();
storage[index].write(value);
}
#[cfg(feature = "stats")]
self.stats.borrow_mut().record_allocation();
Ok(OwnedHandle::new(self, index))
}
pub fn allocate_batch(
&self,
values: alloc::vec::Vec<T>,
) -> Result<alloc::vec::Vec<OwnedHandle<'_, T>>> {
if values.len() > self.available() {
return Err(Error::PoolExhausted {
capacity: self.capacity,
allocated: self.allocated(),
});
}
let mut handles = alloc::vec::Vec::with_capacity(values.len());
for value in values {
match self.allocate(value) {
Ok(handle) => handles.push(handle),
Err(e) => {
drop(handles);
return Err(e);
}
}
}
Ok(handles)
}
#[inline]
pub fn try_allocate(&self, value: T) -> Option<OwnedHandle<'_, T>> {
self.allocate(value).ok()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn available(&self) -> usize {
self.allocator.borrow().available()
}
#[inline]
pub fn allocated(&self) -> usize {
self.capacity - self.available()
}
#[inline]
pub fn is_full(&self) -> bool {
self.allocator.borrow().is_full()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.allocator.borrow().is_empty()
}
#[inline(always)]
pub(crate) fn get(&self, index: usize) -> &T {
let storage = self.storage.borrow();
unsafe {
let ptr = storage.as_ptr();
&*ptr.add(index).cast::<T>()
}
}
#[inline(always)]
#[allow(clippy::mut_from_ref)]
pub(crate) fn get_mut(&self, index: usize) -> &mut T {
let storage = self.storage.borrow_mut();
unsafe {
let ptr = storage.as_ptr() as *mut MaybeUninit<T>;
&mut *ptr.add(index).cast::<T>()
}
}
pub(crate) fn return_to_pool(&self, index: usize) {
let mut storage = self.storage.borrow_mut();
unsafe {
let value_ptr = storage[index].as_mut_ptr();
(*value_ptr).on_release();
ptr::drop_in_place(value_ptr);
}
self.allocator.borrow_mut().free(index);
#[cfg(feature = "stats")]
self.stats.borrow_mut().record_deallocation();
}
#[cfg(feature = "stats")]
#[cfg_attr(docsrs, doc(cfg(feature = "stats")))]
pub fn statistics(&self) -> PoolStatistics {
let mut stats = self.stats.borrow().snapshot();
stats.current_usage = self.allocated();
stats
}
#[cfg(feature = "stats")]
#[cfg_attr(docsrs, doc(cfg(feature = "stats")))]
pub fn reset_statistics(&self) {
self.stats.borrow_mut().reset();
}
}
impl<T> Drop for FixedPool<T> {
fn drop(&mut self) {
let _allocator = self.allocator.borrow();
}
}
unsafe impl<T: Send> Send for FixedPool<T> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_pool() {
let pool = FixedPool::<i32>::new(100).unwrap();
assert_eq!(pool.capacity(), 100);
assert_eq!(pool.available(), 100);
assert_eq!(pool.allocated(), 0);
assert!(pool.is_empty());
assert!(!pool.is_full());
}
#[test]
fn allocate_and_drop() {
let pool = FixedPool::new(10).unwrap();
{
let handle = pool.allocate(42).unwrap();
assert_eq!(*handle, 42);
assert_eq!(pool.allocated(), 1);
assert_eq!(pool.available(), 9);
}
assert_eq!(pool.allocated(), 0);
assert_eq!(pool.available(), 10);
}
#[test]
fn allocate_until_full() {
let pool = FixedPool::new(3).unwrap();
let _h1 = pool.allocate(1).unwrap();
let _h2 = pool.allocate(2).unwrap();
let _h3 = pool.allocate(3).unwrap();
assert!(pool.is_full());
let result = pool.allocate(4);
assert!(matches!(result, Err(Error::PoolExhausted { .. })));
}
#[test]
fn reuse_after_free() {
let pool = FixedPool::new(2).unwrap();
let h1 = pool.allocate(1).unwrap();
drop(h1);
let h2 = pool.allocate(2).unwrap();
assert_eq!(*h2, 2);
}
#[test]
fn modify_value() {
let pool = FixedPool::new(10).unwrap();
let mut handle = pool.allocate(10).unwrap();
assert_eq!(*handle, 10);
*handle = 20;
assert_eq!(*handle, 20);
}
}