use crate::error::{Error, Result};
use crate::raw::RawPoolView;
use crate::resources::id;
use crate::resources::id::ResourceId;
use std::fmt::Debug;
use std::marker::PhantomData;
pub trait ResourcePool<T, RR> {
type Iter<'a>: Iterator<Item = (RR, &'a T)>
where
T: 'a,
Self: 'a;
type IterMut<'a>: Iterator<Item = (RR, &'a mut T)>
where
T: 'a,
Self: 'a;
fn new() -> Self;
fn with_capacity(capacity: usize) -> Self;
fn add(&mut self, resource: T) -> Result<RR>;
fn get(&self, id: RR) -> Option<&T>;
fn get_mut(&mut self, id: RR) -> Option<&mut T>;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn remove(&mut self, id: RR) -> Option<T>;
fn is_valid(&self, id: RR) -> bool;
fn iter<'a>(&'a self) -> Self::Iter<'a>
where
T: 'a;
fn iter_mut<'a>(&'a mut self) -> Self::IterMut<'a>
where
T: 'a;
fn first(&self) -> Option<(RR, &T)>;
fn last(&self) -> Option<(RR, &T)>;
fn find(&self, target: &T) -> Option<RR>
where
T: PartialEq;
fn clear(&mut self);
}
#[derive(Debug, Clone)]
pub struct DefaultResourcePool<T, RR: ResourceId> {
resources: Vec<Option<T>>,
generations: Vec<u16>,
free_list: Vec<u32>,
active_count: usize,
_phantom: PhantomData<RR>,
}
impl<T, RR: ResourceId> DefaultResourcePool<T, RR> {
#[inline]
fn max_index() -> u32 {
RR::max_index()
}
#[inline]
fn max_slots() -> usize {
usize::try_from(Self::max_index())
.unwrap_or(usize::MAX)
.saturating_add(1)
}
#[inline]
fn usize_to_index(index: usize) -> Result<u32> {
let max_index = Self::max_index();
let index_u32 = u32::try_from(index).map_err(|_| Error::ResourcePoolFull {
attempted: index.saturating_add(1),
maximum: Self::max_slots(),
})?;
if index_u32 > max_index {
return Err(Error::ResourcePoolFull {
attempted: index.saturating_add(1),
maximum: Self::max_slots(),
});
}
Ok(index_u32)
}
#[inline]
fn id_for_slot(&self, index: usize) -> Option<RR> {
let generation = self.generations.get(index).copied()?;
let index_u32 = id::usize_to_resource_index::<RR>(index)?;
Some(RR::new(index_u32, generation))
}
#[inline]
fn debug_assert_invariants(&self) {
debug_assert_eq!(
self.resources.len(),
self.generations.len(),
"resource and generation vectors must stay aligned"
);
}
#[must_use]
pub fn new_pool() -> Self {
Self {
resources: Vec::new(),
generations: Vec::new(),
free_list: Vec::new(),
active_count: 0,
_phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub fn raw_view(&self) -> RawPoolView<'_, T> {
RawPoolView::new(&self.resources, &self.generations)
}
pub(crate) fn reserve(&mut self, additional: usize) -> Result<()> {
let reusable = self.free_list.len();
let needed_new_slots = additional.saturating_sub(reusable);
let attempted = self.resources.len().saturating_add(needed_new_slots);
if attempted > Self::max_slots() {
return Err(Error::ResourcePoolFull {
attempted,
maximum: Self::max_slots(),
});
}
self.resources.reserve(needed_new_slots);
self.generations.reserve(needed_new_slots);
Ok(())
}
}
pub struct DefaultResourcePoolIter<'a, T, RR: ResourceId> {
inner: std::iter::Enumerate<std::slice::Iter<'a, Option<T>>>,
generations: &'a [u16],
_phantom: PhantomData<RR>,
}
impl<'a, T, RR: ResourceId> Iterator for DefaultResourcePoolIter<'a, T, RR> {
type Item = (RR, &'a T);
fn next(&mut self) -> Option<Self::Item> {
for (index, opt) in &mut self.inner {
if let Some(r) = opt.as_ref() {
let Some(generation) = self.generations.get(index).copied() else {
debug_assert!(false, "generation vector shorter than resources");
return None;
};
let Some(index_u32) = id::usize_to_resource_index::<RR>(index) else {
debug_assert!(false, "resource index outside representable RR range");
return None;
};
return Some((RR::new(index_u32, generation), r));
}
}
None
}
}
pub struct DefaultResourcePoolIterMut<'a, T, RR: ResourceId> {
inner: std::iter::Enumerate<std::slice::IterMut<'a, Option<T>>>,
generations: &'a [u16],
_phantom: PhantomData<RR>,
}
impl<'a, T, RR: ResourceId> Iterator for DefaultResourcePoolIterMut<'a, T, RR> {
type Item = (RR, &'a mut T);
fn next(&mut self) -> Option<Self::Item> {
for (index, opt) in &mut self.inner {
if let Some(r) = opt.as_mut() {
let Some(generation) = self.generations.get(index).copied() else {
debug_assert!(false, "generation vector shorter than resources");
return None;
};
let Some(index_u32) = id::usize_to_resource_index::<RR>(index) else {
debug_assert!(false, "resource index outside representable RR range");
return None;
};
return Some((RR::new(index_u32, generation), r));
}
}
None
}
}
impl<T, RR: ResourceId> ResourcePool<T, RR> for DefaultResourcePool<T, RR> {
type Iter<'a>
= DefaultResourcePoolIter<'a, T, RR>
where
T: 'a,
RR: 'a;
type IterMut<'a>
= DefaultResourcePoolIterMut<'a, T, RR>
where
T: 'a,
RR: 'a;
fn new() -> Self {
Self::new_pool()
}
fn with_capacity(capacity: usize) -> Self {
Self {
resources: Vec::with_capacity(capacity),
generations: Vec::with_capacity(capacity),
free_list: Vec::new(),
active_count: 0,
_phantom: PhantomData,
}
}
fn add(&mut self, resource: T) -> Result<RR> {
self.debug_assert_invariants();
while let Some(free_index) = self.free_list.pop() {
let Some(slot_index) = usize::try_from(free_index).ok() else {
debug_assert!(false, "free-list index does not fit usize");
return Err(Error::ResourcePoolFull {
attempted: self.resources.len().saturating_add(1),
maximum: Self::max_slots(),
});
};
let Some(current_gen) = self.generations.get(slot_index).copied() else {
debug_assert!(false, "free-list index out of bounds");
return Err(Error::ResourcePoolFull {
attempted: self.resources.len().saturating_add(1),
maximum: Self::max_slots(),
});
};
if current_gen != u16::MAX {
let generation = current_gen + 1;
if let Some(slot_generation) = self.generations.get_mut(slot_index) {
*slot_generation = generation;
}
if let Some(slot_resource) = self.resources.get_mut(slot_index) {
*slot_resource = Some(resource);
}
let id = RR::new(free_index, generation);
self.active_count += 1;
self.debug_assert_invariants();
return Ok(id);
}
}
let index = self.resources.len();
let index_u32 = Self::usize_to_index(index)?;
self.resources.push(Some(resource));
self.generations.push(0);
self.active_count += 1;
self.debug_assert_invariants();
Ok(RR::new(index_u32, 0))
}
fn get(&self, id: RR) -> Option<&T> {
if self.is_valid(id) {
self.resources.get(id.index() as usize)?.as_ref()
} else {
None
}
}
fn get_mut(&mut self, id: RR) -> Option<&mut T> {
if self.is_valid(id) {
self.resources.get_mut(id.index() as usize)?.as_mut()
} else {
None
}
}
fn len(&self) -> usize {
self.active_count
}
fn remove(&mut self, id: RR) -> Option<T> {
self.debug_assert_invariants();
if !self.is_valid(id) {
return None;
}
let resource = self.resources[id.index() as usize].take()?;
self.free_list.push(id.index());
self.active_count -= 1;
Some(resource)
}
fn is_valid(&self, id: RR) -> bool {
self.debug_assert_invariants();
let index = id.index() as usize;
index < self.generations.len()
&& self.generations[index] == id.generation()
&& self.resources[index].is_some()
}
fn iter<'a>(&'a self) -> Self::Iter<'a>
where
T: 'a,
{
self.debug_assert_invariants();
DefaultResourcePoolIter {
inner: self.resources.iter().enumerate(),
generations: &self.generations,
_phantom: PhantomData,
}
}
fn iter_mut<'a>(&'a mut self) -> DefaultResourcePoolIterMut<'a, T, RR>
where
T: 'a,
{
self.debug_assert_invariants();
DefaultResourcePoolIterMut {
inner: self.resources.iter_mut().enumerate(),
generations: &self.generations,
_phantom: PhantomData,
}
}
fn first(&self) -> Option<(RR, &T)> {
self.debug_assert_invariants();
for (index, resource) in self.resources.iter().enumerate() {
if let Some(r) = resource.as_ref() {
if let Some(id) = self.id_for_slot(index) {
return Some((id, r));
}
debug_assert!(false, "resource index outside representable RR range");
return None;
}
}
None
}
fn last(&self) -> Option<(RR, &T)> {
self.debug_assert_invariants();
for (index, resource) in self.resources.iter().enumerate().rev() {
if let Some(r) = resource.as_ref() {
if let Some(id) = self.id_for_slot(index) {
return Some((id, r));
}
debug_assert!(false, "resource index outside representable RR range");
return None;
}
}
None
}
fn find(&self, target: &T) -> Option<RR>
where
T: PartialEq,
{
self.iter()
.find(|(_, resource)| *resource == target)
.map(|(id, _)| id)
}
fn clear(&mut self) {
self.resources.clear();
self.generations.clear();
self.free_list.clear();
self.active_count = 0;
}
}
#[cfg(test)]
mod tests_default_resource_pool {
use super::*;
use crate::resources::id::ResourceId32;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread;
type Pool = DefaultResourcePool<i32, ResourceId32>;
macro_rules! add_pool {
($pool:expr, $resource:expr) => {
$pool
.add($resource)
.expect("resource pool insertion should succeed in this test")
};
}
fn setup_test_pool() -> (Pool, Vec<ResourceId32>) {
let mut pool = Pool::new();
let ids = (1..=3).map(|i| add_pool!(pool, i)).collect();
(pool, ids)
}
mod initialization {
use super::*;
#[test]
fn test_new_pool() {
let pool = Pool::new();
assert!(pool.resources.is_empty());
assert!(pool.generations.is_empty());
assert!(pool.free_list.is_empty());
}
#[test]
fn test_with_capacity() {
let pool = Pool::with_capacity(10);
assert_eq!(pool.resources.capacity(), 10);
assert_eq!(pool.generations.capacity(), 10);
assert!(pool.free_list.is_empty());
}
}
mod operations {
use super::*;
#[test]
fn test_add_and_get() {
let mut pool = Pool::new();
let id = pool
.add(42)
.expect("resource pool insertion should succeed");
assert_eq!(pool.get(id), Some(&42));
}
#[test]
fn test_get_mut() {
let mut pool = Pool::new();
let id = add_pool!(pool, 42);
if let Some(value) = pool.get_mut(id) {
*value = 24;
}
assert_eq!(pool.get(id), Some(&24));
}
#[test]
fn test_len() {
let mut pool = Pool::new();
assert_eq!(pool.len(), 0);
add_pool!(pool, 42);
add_pool!(pool, 43);
assert_eq!(pool.len(), 2, "pool length should increase after addition");
let id = add_pool!(pool, 44);
assert_eq!(pool.len(), 3, "pool length should increase after addition");
pool.remove(id);
assert_eq!(pool.len(), 2, "pool length should decrease after removal");
}
#[test]
fn test_remove_is_empty() {
let mut pool = Pool::new();
let id = add_pool!(pool, 42);
assert_eq!(pool.remove(id), Some(42));
assert_eq!(pool.get(id), None);
assert!(
pool.is_empty(),
"pool should be empty after removing the last resource"
);
assert!(!pool.free_list.is_empty());
}
#[test]
fn test_is_valid() {
let mut pool = Pool::new();
let id = add_pool!(pool, 42);
assert!(
pool.is_valid(id),
"newly added resource identifier should be valid after adding it to the pool"
);
pool.remove(id);
assert!(
!pool.is_valid(id),
"removed resource identifier should be invalid after removal"
);
}
#[test]
fn test_iter_empty() {
let pool = Pool::new();
let collected: Vec<_> = pool.iter().collect();
assert_eq!(collected.len(), 0);
}
#[test]
fn test_iter() {
let (mut pool, ids) = setup_test_pool();
let collected: Vec<_> = pool.iter().collect();
assert_eq!(collected.len(), ids.len());
pool.remove(ids[1]);
let collected: Vec<_> = pool.iter().collect();
assert_eq!(collected.len(), 2);
assert_eq!(collected[0].1, &1);
assert_eq!(collected[1].1, &3);
assert_eq!(collected[0].0.index(), ids[0].index());
assert_eq!(collected[0].0.generation(), ids[0].generation());
assert_eq!(collected[1].0.index(), ids[2].index());
assert_eq!(collected[1].0.generation(), ids[2].generation());
}
#[test]
fn test_iter_mut() {
let (mut pool, ids) = setup_test_pool();
pool.remove(ids[1]);
for (_, value) in pool.iter_mut() {
*value *= 2;
}
assert_eq!(pool.get(ids[0]), Some(&2)); assert_eq!(pool.get(ids[1]), None); assert_eq!(pool.get(ids[2]), Some(&6)); }
#[test]
fn test_iter_mut_custom() {
#[derive(Debug, Clone, PartialEq)]
struct TestData {
value: String,
counter: i32,
}
let mut pool = DefaultResourcePool::<TestData, ResourceId32>::new();
let id1 = pool
.add(TestData {
value: "hello".to_string(),
counter: 0,
})
.unwrap();
let id2 = pool
.add(TestData {
value: "world".to_string(),
counter: 0,
})
.unwrap();
for (_, data) in pool.iter_mut() {
data.value = data.value.to_uppercase();
data.counter += 1;
}
assert_eq!(
pool.get(id1),
Some(&TestData {
value: "HELLO".to_string(),
counter: 1
})
);
assert_eq!(
pool.get(id2),
Some(&TestData {
value: "WORLD".to_string(),
counter: 1
})
);
}
#[test]
fn test_first() {
let (mut pool, ids) = setup_test_pool();
assert_eq!(pool.first(), Some((ids[0], &1)));
pool.remove(ids[0]);
assert_eq!(pool.first(), Some((ids[1], &2)));
}
#[test]
fn test_last() {
let (mut pool, ids) = setup_test_pool();
assert_eq!(pool.last(), Some((ids[2], &3)));
pool.remove(ids[2]);
assert_eq!(pool.last(), Some((ids[1], &2)));
}
#[test]
fn test_find() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 10);
let id2 = add_pool!(pool, 20);
let id3 = add_pool!(pool, 10);
assert_eq!(pool.find(&10), Some(id1));
assert_eq!(pool.find(&20), Some(id2));
assert_eq!(pool.find(&30), None);
pool.remove(id1);
assert_eq!(pool.find(&10), Some(id3));
pool.remove(id3);
assert_eq!(pool.find(&10), None);
}
#[test]
fn test_clear_basic() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 10);
let id2 = add_pool!(pool, 20);
let id3 = add_pool!(pool, 30);
assert_eq!(pool.len(), 3);
assert_eq!(pool.get(id1), Some(&10));
assert_eq!(pool.get(id2), Some(&20));
assert_eq!(pool.get(id3), Some(&30));
pool.clear();
assert_eq!(pool.len(), 0);
assert!(pool.is_empty());
assert!(pool.free_list.is_empty());
assert!(pool.generations.is_empty());
assert_eq!(pool.get(id1), None);
assert_eq!(pool.get(id2), None);
assert_eq!(pool.get(id3), None);
}
#[test]
fn test_add_after_clear() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 10);
let _id2 = add_pool!(pool, 20);
pool.remove(id1);
pool.clear();
let new_id = add_pool!(pool, 100);
assert_eq!(new_id.index(), 0);
assert_eq!(new_id.generation(), 0);
assert_eq!(pool.get(new_id), Some(&100));
assert_eq!(pool.len(), 1);
}
#[test]
fn test_clear_empty() {
let mut pool = Pool::new();
pool.clear();
assert_eq!(pool.len(), 0);
assert!(pool.is_empty());
}
}
mod edge_cases {
use super::*;
#[test]
fn test_invalid_id() {
let mut pool = Pool::new();
let invalid_id = ResourceId32::new(0, 0);
assert_eq!(pool.get(invalid_id), None);
assert_eq!(pool.get_mut(invalid_id), None);
assert_eq!(pool.remove(invalid_id), None);
let mut pool = Pool::new();
let invalid_id = ResourceId32::new(999, 0);
assert!(!pool.is_valid(invalid_id));
let id = add_pool!(pool, 42);
let wrong_gen_id = ResourceId32::new(id.index(), id.generation() + 1);
assert!(!pool.is_valid(wrong_gen_id));
}
#[test]
fn test_iter_with_all_removed() {
let (mut pool, ids) = setup_test_pool();
for id in ids {
pool.remove(id);
}
let collected: Vec<_> = pool.iter().collect();
assert_eq!(collected.len(), 0);
}
#[test]
fn test_iter_mut_on_empty_pool() {
let mut pool = Pool::new();
let mut count = 0;
for _ in pool.iter_mut() {
count += 1;
}
assert_eq!(count, 0);
}
}
mod resource_management {
use super::*;
#[test]
fn test_reuse_freed_slot() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 1);
add_pool!(pool, 2); pool.remove(id1);
let id3 = add_pool!(pool, 3);
assert_eq!(id3.index(), id1.index());
assert_eq!(id3.generation(), id1.generation() + 1);
assert_eq!(pool.get(id3), Some(&3));
}
#[test]
fn test_multiple_removals_and_additions() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 1);
let id2 = add_pool!(pool, 2);
let id3 = add_pool!(pool, 3);
pool.remove(id1); pool.remove(id2);
let id4 = add_pool!(pool, 4); let id5 = add_pool!(pool, 5);
assert_eq!(id4.index(), id2.index());
assert_eq!(id5.index(), id1.index());
assert_eq!(pool.get(id3), Some(&3));
assert_eq!(pool.get(id4), Some(&4));
assert_eq!(pool.get(id5), Some(&5));
assert_eq!(pool.get(id1), None);
assert_eq!(pool.get(id2), None);
}
#[test]
fn test_iter_mut_collects_all_valid_resources() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 1);
let id2 = add_pool!(pool, 2);
let id3 = add_pool!(pool, 3);
pool.remove(id2); let id4 = add_pool!(pool, 4);
let mut resources = Vec::new();
for (id, value) in pool.iter_mut() {
resources.push((id, *value));
}
assert_eq!(resources.len(), 3);
assert!(
resources
.iter()
.any(|(id, value)| id == &id1 && *value == 1)
);
assert!(
resources
.iter()
.any(|(id, value)| id.index() == id4.index() && *value == 4)
);
assert!(
resources
.iter()
.any(|(id, value)| id == &id3 && *value == 3)
);
assert!(!resources.iter().any(|(id, _)| id == &id2));
}
}
mod concurrency {
use super::*;
#[test]
fn test_concurrent_mutation() {
let pool = Arc::new(Mutex::new(Pool::new()));
let handles: Vec<_> = (0..4)
.map(|_| {
let pool = Arc::clone(&pool);
thread::spawn(move || {
for i in 0..100 {
let mut pool = pool.lock().unwrap();
let id = add_pool!(pool, i);
assert_eq!(pool.get(id), Some(&i));
pool.remove(id);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert!(
pool.lock()
.unwrap()
.resources
.iter()
.all(std::option::Option::is_none)
);
}
}
mod memory_safety {
use super::*;
struct LeakDetector {
counter: Rc<Cell<i32>>,
}
impl LeakDetector {
fn new(counter: Rc<Cell<i32>>) -> Self {
counter.set(counter.get() + 1);
Self { counter }
}
}
impl Drop for LeakDetector {
fn drop(&mut self) {
self.counter.set(self.counter.get() - 1);
}
}
#[test]
fn test_resource_lifetime() {
let counter = Rc::new(Cell::new(0));
{
let _detector = LeakDetector::new(Rc::clone(&counter));
assert_eq!(counter.get(), 1);
}
assert_eq!(counter.get(), 0);
let mut pool = DefaultResourcePool::<LeakDetector, ResourceId32>::new();
let id = add_pool!(pool, LeakDetector::new(Rc::clone(&counter)));
assert_eq!(counter.get(), 1);
let _ = pool.remove(id);
assert_eq!(counter.get(), 0);
let ids: Vec<_> = (0..10)
.map(|_| add_pool!(pool, LeakDetector::new(Rc::clone(&counter))))
.collect();
assert_eq!(counter.get(), 10);
for id in ids {
let _ = pool.remove(id);
}
assert_eq!(counter.get(), 0);
{
let mut scoped_pool = DefaultResourcePool::<LeakDetector, ResourceId32>::new();
for _ in 0..100 {
add_pool!(scoped_pool, LeakDetector::new(Rc::clone(&counter)));
}
assert_eq!(counter.get(), 100);
}
assert_eq!(counter.get(), 0);
}
}
mod boundary_conditions {
use super::*;
use crate::resources::id::ResourceId;
use std::fmt::{Display, Formatter};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
struct TinyResourceId {
index: u32,
generation: u16,
}
impl Display for TinyResourceId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "index: {}, generation: {}", self.index, self.generation)
}
}
impl ResourceId for TinyResourceId {
fn new(index: u32, generation: u16) -> Self {
Self { index, generation }
}
fn index(&self) -> u32 {
self.index
}
fn generation(&self) -> u16 {
self.generation
}
fn max_index() -> u32 {
3
}
}
#[test]
fn test_pool_full_error_at_tiny_capacity() {
let mut pool = DefaultResourcePool::<u32, TinyResourceId>::new();
for i in 0..=TinyResourceId::max_index() {
let id = pool.add(i).unwrap();
assert_eq!(id.index(), i);
}
let err = pool.add(999).unwrap_err();
assert_eq!(
err,
Error::ResourcePoolFull {
attempted: 5,
maximum: 4
}
);
}
#[test]
fn test_generation_wraparound() {
let mut pool = Pool::new();
let id = add_pool!(pool, 42);
assert_eq!(id.index(), 0);
assert_eq!(id.generation(), 0);
pool.generations[id.index() as usize] = u16::MAX;
pool.remove(id);
let id2 = add_pool!(pool, 43);
assert_eq!(id2.index(), 1);
assert_eq!(id2.generation(), 0);
assert_eq!(pool.get(id2), Some(&43));
}
#[test]
fn test_generation_overflow_prevention() {
let mut pool = Pool::new();
let id1 = add_pool!(pool, 100);
let index1 = id1.index();
pool.generations[index1 as usize] = u16::MAX - 1;
pool.remove(ResourceId32::new(index1, u16::MAX - 1));
let id2 = add_pool!(pool, 200);
assert_eq!(id2.index(), index1);
assert_eq!(id2.generation(), u16::MAX);
pool.remove(id2);
let id3 = add_pool!(pool, 300);
assert_ne!(
id3.index(),
index1,
"should allocate new slot, not reuse retired one"
);
assert_eq!(id3.generation(), 0);
assert_eq!(pool.get(id3), Some(&300));
assert_eq!(pool.get(ResourceId32::new(index1, u16::MAX)), None);
let index3 = id3.index();
pool.generations[index3 as usize] = u16::MAX;
pool.remove(ResourceId32::new(index3, u16::MAX));
let id4 = add_pool!(pool, 400);
assert_ne!(id4.index(), index1);
assert_ne!(id4.index(), index3);
assert_eq!(id4.generation(), 0);
assert_eq!(pool.len(), 1);
}
}
}