use crate::{
IndexedID, PtrID, SlicePtrErr, SlicePtrRes,
bitalloc::IBitAlloc,
chunk::{Chunk, Unit},
gen_index::GenIndex,
iter::{EntityAllocConsumeIter, EntityAllocEditIter, EntityAllocReadIter},
policy::{AllocPolicy256, IAllocPolicy},
};
use std::{
cell::{BorrowMutError, Cell, RefCell},
ptr::NonNull,
};
#[derive(Debug)]
pub enum EntityAccessErr {
UseAfterFree,
InvalidReference,
}
impl std::fmt::Display for EntityAccessErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self {
EntityAccessErr::UseAfterFree => "Entity has been freed (use-after-free).",
EntityAccessErr::InvalidReference => "Index is invalid (no entity referenced).",
};
f.write_str(msg)
}
}
impl std::error::Error for EntityAccessErr {}
pub type EntityAccessRes<T = ()> = Result<T, EntityAccessErr>;
pub struct EntityAlloc<E, P: IAllocPolicy = AllocPolicy256> {
pub(crate) chunks: RefCell<Vec<Chunk<E, P>>>,
pub(crate) free_head: Cell<u32>,
pub(crate) num_allocated: Cell<usize>,
}
impl<E, P: IAllocPolicy> Default for EntityAlloc<E, P> {
fn default() -> Self {
Self {
chunks: RefCell::new(Vec::new()),
free_head: Cell::new(0),
num_allocated: Cell::new(0),
}
}
}
impl<E, P: IAllocPolicy> EntityAlloc<E, P> {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(cap: usize) -> Self {
let chunks = {
let nchunks = cap.div_ceil(P::CHUNK_SIZE);
let mut chunks = Vec::with_capacity(nchunks);
for chunk_id in 0..(nchunks as u32) {
let chunk = Chunk::new(chunk_id, chunk_id + 1);
chunks.push(chunk);
}
RefCell::new(chunks)
};
Self {
chunks,
free_head: Cell::new(0),
num_allocated: Cell::new(0),
}
}
pub(crate) fn try_allocate_unit(&self, val: E) -> Result<*const Unit<E>, (BorrowMutError, E)> {
let mut chunks = match self.chunks.try_borrow_mut() {
Ok(c) => c,
Err(e) => return Err((e, val)),
};
let chunk = Self::find_chunk(self.free_head.get(), &mut chunks);
let Ok(ptr) = chunk.allocate(val) else {
unreachable!("Internal error: Cannot allocate inside a non-full chunk");
};
self.num_allocated.set(self.num_allocated.get() + 1);
if chunk.is_full() {
self.free_head.set(chunk.free_next);
}
Ok(ptr)
}
fn find_chunk(free: u32, chunks: &mut Vec<Chunk<E, P>>) -> &mut Chunk<E, P> {
assert_ne!(free, u32::MAX, "OOM detected");
let free = free as usize;
while chunks.len() <= free {
let id = chunks.len() as u32;
chunks.push(Chunk::new(id, id + 1));
}
&mut chunks[free]
}
pub(crate) fn next_index(&self) -> GenIndex {
let chunks = self.chunks.borrow();
let free = self.free_head.get() as usize;
let Some(chunk) = chunks.get(free) else {
return GenIndex::compose(free << P::CHUNK_SIZE_LOG2, GenIndex::GEN_1);
};
let unit_id = chunk.next_available().unwrap();
chunk.indexed_id_of_unit(unit_id)
}
pub(crate) fn free_gen_index(&mut self, index: GenIndex) -> Option<E> {
let (chunk_id, unit_id, gene) = P::tear(index);
let chunks = self.chunks.get_mut();
let chunk = chunks.get_mut(chunk_id as usize)?;
let chunk_full = chunk.is_full();
let val = chunk.deallocate_checked(unit_id, gene)?;
self.num_allocated.set(self.num_allocated.get() - 1);
if chunk_full {
chunk.free_next = self.free_head.get();
self.free_head.set(chunk_id);
}
Some(val)
}
pub(crate) fn free_unit_ptr(&mut self, ptr: *const Unit<E>) -> Option<E> {
let indexed = self.index_of_unit_ptr(ptr).ok()?;
self.free_gen_index(indexed)
}
pub(crate) fn check_unit_validity(&self, ptr: *const Unit<E>) -> SlicePtrRes<GenIndex> {
let chunks = self.chunks.borrow();
for chunk in chunks.iter() {
let unit_id = match chunk.unit_of_ptr(ptr) {
Ok(id) => id,
Err(SlicePtrErr::OutOfRange) => continue,
Err(SlicePtrErr::NotAlignedWith) => return Err(SlicePtrErr::NotAlignedWith),
};
let unit = chunk.unit(unit_id);
return if unit.indexed.is_invalid() {
Err(SlicePtrErr::OutOfRange)
} else {
Ok(unit.indexed)
};
}
Err(SlicePtrErr::OutOfRange)
}
pub(crate) fn index_of_unit_ptr(&self, ptr: *const Unit<E>) -> SlicePtrRes<GenIndex> {
if cfg!(debug_assertions) {
self.check_unit_validity(ptr)
} else {
let indexed = unsafe { ptr.as_ref().ok_or(SlicePtrErr::OutOfRange)?.indexed };
if indexed.is_invalid() {
Err(SlicePtrErr::OutOfRange)
} else {
Ok(indexed)
}
}
}
pub(crate) fn unit_ptr_of_indexed(
&self,
indexed: GenIndex,
) -> EntityAccessRes<NonNull<Unit<E>>> {
if indexed.is_invalid() {
return Err(EntityAccessErr::InvalidReference);
}
let (chunk_id, unit_id, _) = P::tear(indexed);
let chunks = self.chunks.borrow();
let Some(chunk) = chunks.get(chunk_id as usize) else {
return Err(EntityAccessErr::UseAfterFree);
};
let unit = chunk.unit(unit_id);
if unit.indexed != indexed {
return Err(EntityAccessErr::UseAfterFree);
}
Ok(NonNull::new(unit as *const _ as *mut Unit<E>).unwrap())
}
pub(crate) fn unit_mut_of_indexed(
&mut self,
indexed: GenIndex,
) -> EntityAccessRes<&mut Unit<E>> {
if indexed.is_invalid() {
return Err(EntityAccessErr::InvalidReference);
}
let (chunk_id, unit_id, _) = P::tear(indexed);
let chunks = self.chunks.get_mut();
let Some(chunk) = chunks.get_mut(chunk_id as usize) else {
return Err(EntityAccessErr::UseAfterFree);
};
let unit = chunk.unit_mut(unit_id);
if unit.indexed != indexed {
return Err(EntityAccessErr::UseAfterFree);
}
Ok(unit)
}
pub fn remake_free_chain(&mut self) {
let chunks = self.chunks.get_mut();
let free_chunks = {
let mut free_chunks = Vec::with_capacity(chunks.len());
for chunk in chunks.iter_mut() {
if !chunk.is_full() {
free_chunks.push(chunk.chunk_id);
}
}
free_chunks
};
for i in 1..free_chunks.len() {
let chunk_id = free_chunks[i - 1];
let next_chunk_id = free_chunks[i];
let chunk = &mut chunks[chunk_id as usize];
chunk.free_next = next_chunk_id;
}
if let Some(&chunk_id) = free_chunks.first() {
self.free_head.set(chunk_id);
} else {
self.free_head.set(chunks.len() as u32);
}
}
}
impl<E, P: IAllocPolicy> EntityAlloc<E, P> {
pub fn free_if(&mut self, pred: impl FnMut(&E, PtrID<E, P>, IndexedID<E, P>) -> bool) {
self.fully_free_if(pred, drop);
}
pub fn fully_free_if(
&mut self,
mut should_free: impl FnMut(&E, PtrID<E, P>, IndexedID<E, P>) -> bool,
mut consume: impl FnMut(E),
) {
let chunks = self.chunks.get_mut();
let mut num_last = self.num_allocated.get();
for chunk in chunks.iter_mut() {
if chunk.is_empty() {
continue;
}
if num_last == 0 {
break;
}
let chunk_full_previously = chunk.is_full();
let chunk_num_allocated = chunk.num_allocated as usize;
let num_freed = if chunk_num_allocated * 3 >= P::CHUNK_SIZE {
Self::densely_free_chunk(&mut should_free, &mut consume, chunk)
} else {
Self::sparsely_free_chunk(&mut should_free, &mut consume, chunk)
};
num_last -= chunk_num_allocated;
if num_freed > 0 {
self.num_allocated
.set(self.num_allocated.get().saturating_sub(num_freed));
}
if chunk_full_previously && !chunk.is_full() {
chunk.free_next = self.free_head.get();
self.free_head.set(chunk.chunk_id);
}
}
}
fn densely_free_chunk(
should_free: &mut impl FnMut(&E, PtrID<E, P>, IndexedID<E, P>) -> bool,
consume: &mut impl FnMut(E),
chunk: &mut Chunk<E, P>,
) -> usize {
let mut num_freed = 0;
let mut count_down = chunk.num_allocated;
for unit_id in 0..(P::CHUNK_SIZE as u16) {
if count_down == 0 {
break;
}
let index = chunk.indexed_id_of_unit(unit_id);
if !chunk.allocated.is_allocated(unit_id) {
continue;
}
count_down -= 1;
let unit = chunk.unit_ptr_mut(unit_id);
let ptr = PtrID::from(unit);
let elem = unsafe { &*unit }.as_init_ref().unwrap();
if !should_free(elem, ptr, IndexedID::from(index)) {
continue;
}
let Some(e) = chunk.deallocate_unchecked(unit_id) else {
unreachable!("Deallocation failed unexpectedly");
};
consume(e);
num_freed += 1;
}
num_freed
}
fn sparsely_free_chunk(
should_free: &mut impl FnMut(&E, PtrID<E, P>, IndexedID<E, P>) -> bool,
consume: &mut impl FnMut(E),
chunk: &mut Chunk<E, P>,
) -> usize {
let mut num_freed = 0usize;
let mut next = chunk.allocated.next_allocated(0);
while let Some(unit_id) = next {
let index = chunk.indexed_id_of_unit(unit_id);
let unit = chunk.unit_ptr_mut(unit_id);
let ptr = PtrID::from(unit);
let elem = unsafe { &*unit }.as_init_ref().unwrap();
if should_free(elem, ptr, IndexedID::from(index)) {
if let Some(e) = chunk.deallocate_unchecked(unit_id) {
consume(e);
num_freed += 1;
} else {
unreachable!("Deallocation failed unexpectedly");
}
let next_start = unit_id.saturating_add(1);
next = chunk.allocated.next_allocated(next_start);
} else {
let next_start = unit_id.saturating_add(1);
next = chunk.allocated.next_allocated(next_start);
}
}
num_freed
}
#[inline]
pub fn len(&self) -> usize {
self.num_allocated.get()
}
#[inline]
pub fn capacity(&self) -> usize {
self.chunks.borrow().len() * P::CHUNK_SIZE
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
self.chunks.get_mut().clear();
self.free_head.set(0);
self.num_allocated.set(0);
}
#[inline]
pub fn iter(&self) -> EntityAllocReadIter<'_, E, P> {
EntityAllocReadIter::new(self)
}
#[inline]
pub fn iter_mut(&mut self) -> EntityAllocEditIter<'_, E, P> {
EntityAllocEditIter::new(self)
}
}
impl<'alloc, E, P: IAllocPolicy> IntoIterator for &'alloc EntityAlloc<E, P> {
type Item = (IndexedID<E, P>, PtrID<E, P>, &'alloc E);
type IntoIter = EntityAllocReadIter<'alloc, E, P>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'alloc, E, P: IAllocPolicy> IntoIterator for &'alloc mut EntityAlloc<E, P> {
type Item = (IndexedID<E, P>, PtrID<E, P>, &'alloc mut E);
type IntoIter = EntityAllocEditIter<'alloc, E, P>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<E, P: IAllocPolicy> IntoIterator for EntityAlloc<E, P> {
type Item = (GenIndex, E);
type IntoIter = EntityAllocConsumeIter<E, P>;
fn into_iter(self) -> Self::IntoIter {
EntityAllocConsumeIter::new(self)
}
}