use core::cell::UnsafeCell;
use core::fmt;
use core::fmt::{Debug, Formatter};
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug)]
pub struct MemoryPool<T, const SIZE: usize> {
chunks: [MemoryPoolInner<T>; SIZE],
}
impl<T, const SIZE: usize> Default for MemoryPool<T, SIZE> {
fn default() -> Self {
Self::new()
}
}
impl<T, const SIZE: usize> MemoryPool<T, SIZE> {
pub const fn new() -> Self {
const {
assert!(SIZE > 0, "empty ObjectPool");
}
Self {
chunks: [const { MemoryPoolInner::new() }; SIZE],
}
}
pub fn reserve(&self) -> Option<MemoryPoolToken<'_, T>> {
self.chunks.iter().find_map(|chunk| chunk.reserve())
}
pub fn chunk(&self, init_value: T) -> Result<Chunk<'_, T>, T> {
let token = self.reserve();
if let Some(token) = token {
Ok(token.init(init_value))
} else {
Err(init_value)
}
}
pub fn chunks_available(&self) -> usize {
self.chunks
.iter()
.map(|chunk| usize::from(chunk.is_available()))
.sum()
}
}
unsafe impl<T, const N: usize> Sync for MemoryPool<T, N> {}
#[derive(Debug)]
struct MemoryPoolInner<T> {
data: UnsafeCell<MaybeUninit<T>>,
available: AtomicBool,
}
impl<T> MemoryPoolInner<T> {
const fn new() -> Self {
Self {
data: UnsafeCell::new(MaybeUninit::uninit()),
available: AtomicBool::new(true),
}
}
fn reserve(&self) -> Option<MemoryPoolToken<'_, T>> {
if self.available.swap(false, Ordering::AcqRel) {
Some(MemoryPoolToken { inner: Some(self) })
} else {
None
}
}
fn is_available(&self) -> bool {
self.available.load(Ordering::Acquire)
}
}
#[derive(Debug)]
pub struct MemoryPoolToken<'a, T> {
inner: Option<&'a MemoryPoolInner<T>>,
}
impl<'a, T> MemoryPoolToken<'a, T> {
fn consume(&mut self) -> (&'a mut MaybeUninit<T>, &'a AtomicBool) {
let Some(inner) = self.inner.take() else {
unreachable!("`MemoryPoolToken` should only be consumed once");
};
let inner_data = {
let inner_data_ptr = inner.data.get();
unsafe { inner_data_ptr.as_mut() }
.expect("pointer to the contents of an `UnsafeCell` should not be null")
};
(inner_data, &inner.available)
}
pub fn init(mut self, init_value: T) -> Chunk<'a, T> {
let (inner_data, available) = self.consume();
inner_data.write(init_value);
unsafe { Chunk::new(inner_data, available) }
}
pub unsafe fn init_in_place(
mut self,
init_function: impl FnOnce(&mut MaybeUninit<T>),
) -> Chunk<'a, T> {
let (inner_data, available) = self.consume();
init_function(inner_data);
unsafe { Chunk::new(inner_data, available) }
}
}
impl<T> Drop for MemoryPoolToken<'_, T> {
fn drop(&mut self) {
if let Some(inner) = self.inner.take() {
inner.available.store(true, Ordering::Release);
}
}
}
pub struct Chunk<'a, T> {
inner: &'a mut MaybeUninit<T>,
token: &'a AtomicBool,
}
impl<T> Debug for Chunk<'_, T>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&**self, f)
}
}
impl<'a, T> Chunk<'a, T> {
unsafe fn new(chunk: &'a mut MaybeUninit<T>, token: &'a AtomicBool) -> Self {
Self {
inner: chunk,
token,
}
}
}
impl<T> Deref for Chunk<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.inner.assume_init_ref() }
}
}
impl<T> DerefMut for Chunk<'_, T> {
fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
unsafe { self.inner.assume_init_mut() }
}
}
impl<T> Drop for Chunk<'_, T> {
fn drop(&mut self) {
unsafe { self.inner.assume_init_drop() };
debug_assert!(
!self.token.swap(true, Ordering::AcqRel),
"chunk was made available a second time"
);
}
}
#[cfg(test)]
mod test {
use std::format;
use std::sync::atomic::AtomicUsize;
use super::*;
#[test]
fn pool() {
static POOL: MemoryPool<[u8; 10], 2> = MemoryPool::new();
let mut chunk = POOL.chunk([0; 10]).unwrap();
let chunk1 = POOL.chunk([0; 10]).unwrap();
assert!(POOL.chunk([0; 10]).is_err());
assert_eq!(chunk[0], 0);
chunk[0] += 1;
assert_eq!(chunk[0], 1);
assert_eq!(chunk1[0], 0);
}
#[test]
fn drop_test() {
#[derive(Debug)]
pub struct Dropper {}
impl Drop for Dropper {
fn drop(&mut self) {
COUNTER.fetch_add(1, Ordering::Relaxed);
}
}
static COUNTER: AtomicUsize = AtomicUsize::new(0);
{
let pool: MemoryPool<Dropper, 2> = MemoryPool::new();
let _ = pool.chunk(Dropper {});
assert_eq!(COUNTER.load(Ordering::Relaxed), 1);
{
let _dropper1 = pool.chunk(Dropper {}).unwrap();
let _dropper2 = pool.chunk(Dropper {}).unwrap();
assert!(pool.chunk(Dropper {}).is_err());
}
assert_eq!(COUNTER.load(Ordering::Relaxed), 4);
let _ = pool.chunk(Dropper {});
assert_eq!(COUNTER.load(Ordering::Relaxed), 5);
}
assert_eq!(COUNTER.load(Ordering::Relaxed), 5);
}
#[test]
fn drop_memory_pool_token() {
let pool = MemoryPool::<usize, 1>::new();
assert_eq!(pool.chunks_available(), 1);
{
let _token = pool.reserve().unwrap();
assert_eq!(pool.chunks_available(), 0);
}
assert_eq!(pool.chunks_available(), 1);
}
#[test]
fn chunks_available() {
let pool = MemoryPool::<usize, 2>::new();
assert_eq!(pool.chunks_available(), 2);
{
let _chunk = pool.chunk(0);
assert_eq!(pool.chunks_available(), 1);
let _chunk = pool.chunk(0);
assert_eq!(pool.chunks_available(), 0);
}
assert_eq!(pool.chunks_available(), 2);
}
#[test]
fn reserve_init() {
let pool = MemoryPool::<usize, 2>::new();
let token = pool.reserve().unwrap();
let chunk = token.init(2);
assert_eq!(*chunk, 2);
}
#[test]
fn reserve_init_in_place() {
let pool = MemoryPool::<usize, 2>::new();
let token = pool.reserve().unwrap();
let chunk = unsafe {
token.init_in_place(|m| {
m.write(2);
})
};
assert_eq!(*chunk, 2);
}
#[test]
#[should_panic(expected = "`MemoryPoolToken` should only be consumed once")]
fn consume_none() {
let pool = MemoryPool::<usize, 2>::new();
let mut token = pool.reserve().unwrap();
let _ = token.consume();
let _ = token.consume();
}
#[test]
fn send_sync() {
fn send<T>()
where
T: Send,
{
}
fn sync<T>()
where
T: Sync,
{
}
send::<MemoryPool<[u8; 10], 2>>();
sync::<MemoryPool<[u8; 10], 2>>();
send::<Chunk<[u8; 10]>>();
sync::<Chunk<[u8; 10]>>();
}
#[test]
fn debug_chunk() {
let pool = MemoryPool::<usize, 2>::new();
let chunk = pool.chunk(0).unwrap();
assert_eq!(format!("{chunk:?}"), "0");
}
#[test]
fn default_memory_pool() {
let pool: MemoryPool<usize, 2> = MemoryPool::default();
assert_eq!(pool.chunks_available(), 2);
}
}