use std::sync::atomic::{AtomicU32, Ordering};
const DEFAULT_ENTITY_CAPACITY: u32 = 1024;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Entity {
pub id: u32,
pub generation: u32,
}
impl Entity {
#[inline]
pub const fn new(id: u32, generation: u32) -> Self {
Self { id, generation }
}
}
impl std::fmt::Debug for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Entity({}, gen{})", self.id, self.generation)
}
}
pub struct EntityAllocator {
next_id: AtomicU32,
free_list: std::sync::Mutex<Vec<u32>>,
generations: std::sync::RwLock<Vec<u32>>,
}
impl EntityAllocator {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_ENTITY_CAPACITY)
}
pub fn with_capacity(capacity: u32) -> Self {
let mut generations = Vec::with_capacity(capacity as usize);
generations.resize(capacity as usize, 0);
Self {
next_id: AtomicU32::new(0),
free_list: std::sync::Mutex::new(Vec::new()),
generations: std::sync::RwLock::new(generations),
}
}
pub fn alloc(&self) -> Entity {
let id = {
let mut free = self.free_list.lock().expect("entity allocator lock");
if let Some(id) = free.pop() {
id
} else {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let mut gen = self.generations.write().expect("generations rwlock");
let len = gen.len();
if (id as usize) >= len {
gen.resize(len.max(1) * 2, 0);
}
id
}
};
let generation = {
let gen = self.generations.read().expect("generations rwlock");
gen.get(id as usize).copied().unwrap_or(0)
};
Entity::new(id, generation)
}
pub fn free(&self, entity: Entity) {
let id = entity.id;
{
let mut gen = self.generations.write().expect("generations rwlock");
let len = gen.len();
if (id as usize) >= len {
gen.resize(len.max(1) * 2, 0);
}
let old = gen.get(id as usize).copied().unwrap_or(0);
gen[id as usize] = old.wrapping_add(1);
}
self.free_list
.lock()
.expect("entity allocator lock")
.push(id);
}
pub fn is_valid(&self, entity: Entity) -> bool {
let gen = self.generations.read().expect("generations rwlock");
gen.get(entity.id as usize)
.copied()
.map(|g| g == entity.generation)
.unwrap_or(false)
}
}
impl Default for EntityAllocator {
fn default() -> Self {
Self::new()
}
}
unsafe impl Send for EntityAllocator {}
unsafe impl Sync for EntityAllocator {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_and_free() {
let alloc = EntityAllocator::new();
let e1 = alloc.alloc();
let e2 = alloc.alloc();
assert_ne!(e1.id, e2.id);
assert!(alloc.is_valid(e1));
assert!(alloc.is_valid(e2));
alloc.free(e1);
assert!(!alloc.is_valid(e1));
let e3 = alloc.alloc();
assert_eq!(e3.id, e1.id);
assert_ne!(e3.generation, e1.generation);
}
}