137 lines
3.4 KiB
Rust
137 lines
3.4 KiB
Rust
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
pub struct Entity {
|
|
pub id: u32,
|
|
pub generation: u32,
|
|
}
|
|
|
|
struct EntityEntry {
|
|
generation: u32,
|
|
alive: bool,
|
|
}
|
|
|
|
pub struct EntityAllocator {
|
|
entries: Vec<EntityEntry>,
|
|
free_list: Vec<u32>,
|
|
alive_count: usize,
|
|
}
|
|
|
|
impl EntityAllocator {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
entries: Vec::new(),
|
|
free_list: Vec::new(),
|
|
alive_count: 0,
|
|
}
|
|
}
|
|
|
|
pub fn allocate(&mut self) -> Entity {
|
|
self.alive_count += 1;
|
|
if let Some(id) = self.free_list.pop() {
|
|
let entry = &mut self.entries[id as usize];
|
|
// generation was already incremented on deallocate
|
|
entry.alive = true;
|
|
Entity {
|
|
id,
|
|
generation: entry.generation,
|
|
}
|
|
} else {
|
|
let id = self.entries.len() as u32;
|
|
self.entries.push(EntityEntry {
|
|
generation: 0,
|
|
alive: true,
|
|
});
|
|
Entity { id, generation: 0 }
|
|
}
|
|
}
|
|
|
|
pub fn deallocate(&mut self, entity: Entity) -> bool {
|
|
let Some(entry) = self.entries.get_mut(entity.id as usize) else {
|
|
return false;
|
|
};
|
|
if !entry.alive || entry.generation != entity.generation {
|
|
return false;
|
|
}
|
|
entry.alive = false;
|
|
entry.generation = entry.generation.wrapping_add(1);
|
|
self.free_list.push(entity.id);
|
|
self.alive_count -= 1;
|
|
true
|
|
}
|
|
|
|
pub fn is_alive(&self, entity: Entity) -> bool {
|
|
self.entries
|
|
.get(entity.id as usize)
|
|
.map_or(false, |e| e.alive && e.generation == entity.generation)
|
|
}
|
|
|
|
pub fn alive_count(&self) -> usize {
|
|
self.alive_count
|
|
}
|
|
}
|
|
|
|
impl Default for EntityAllocator {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_allocate() {
|
|
let mut alloc = EntityAllocator::new();
|
|
let e0 = alloc.allocate();
|
|
let e1 = alloc.allocate();
|
|
assert_eq!(e0.id, 0);
|
|
assert_eq!(e1.id, 1);
|
|
assert_eq!(e0.generation, 0);
|
|
assert_eq!(e1.generation, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_deallocate_and_reuse() {
|
|
let mut alloc = EntityAllocator::new();
|
|
let e0 = alloc.allocate();
|
|
let _e1 = alloc.allocate();
|
|
assert!(alloc.deallocate(e0));
|
|
let e0_new = alloc.allocate();
|
|
assert_eq!(e0_new.id, 0);
|
|
assert_eq!(e0_new.generation, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_is_alive() {
|
|
let mut alloc = EntityAllocator::new();
|
|
let e = alloc.allocate();
|
|
assert!(alloc.is_alive(e));
|
|
alloc.deallocate(e);
|
|
assert!(!alloc.is_alive(e));
|
|
}
|
|
|
|
#[test]
|
|
fn test_stale_entity_rejected() {
|
|
let mut alloc = EntityAllocator::new();
|
|
let e = alloc.allocate();
|
|
alloc.deallocate(e);
|
|
// stale entity not alive
|
|
assert!(!alloc.is_alive(e));
|
|
// double-delete fails
|
|
assert!(!alloc.deallocate(e));
|
|
}
|
|
|
|
#[test]
|
|
fn test_alive_count() {
|
|
let mut alloc = EntityAllocator::new();
|
|
assert_eq!(alloc.alive_count(), 0);
|
|
let e0 = alloc.allocate();
|
|
let e1 = alloc.allocate();
|
|
assert_eq!(alloc.alive_count(), 2);
|
|
alloc.deallocate(e0);
|
|
assert_eq!(alloc.alive_count(), 1);
|
|
alloc.deallocate(e1);
|
|
assert_eq!(alloc.alive_count(), 0);
|
|
}
|
|
}
|