use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Entity {
index: u32,
generation: u32,
}
impl Entity {
#[must_use]
pub const fn index(self) -> u32 {
self.index
}
#[must_use]
pub const fn generation(self) -> u32 {
self.generation
}
pub(crate) const fn new(index: u32, generation: u32) -> Self {
Self { index, generation }
}
}
impl fmt::Display for Entity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "e{}v{}", self.index, self.generation)
}
}
#[derive(Debug, Default)]
pub struct Entities {
generations: Vec<u32>,
alive: Vec<bool>,
free: Vec<u32>,
live_count: usize,
}
impl Entities {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.live_count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.live_count == 0
}
#[must_use]
pub fn capacity(&self) -> usize {
self.generations.len()
}
pub fn spawn(&mut self) -> Entity {
self.live_count = self.live_count.saturating_add(1);
if let Some(index) = self.free.pop() {
let slot = index as usize;
if let Some(alive) = self.alive.get_mut(slot) {
*alive = true;
}
let generation = self.generations.get(slot).copied().unwrap_or_default();
return Entity::new(index, generation);
}
let index = u32::try_from(self.generations.len()).unwrap_or(u32::MAX);
self.generations.push(0);
self.alive.push(true);
Entity::new(index, 0)
}
#[must_use]
pub fn is_alive(&self, entity: Entity) -> bool {
let slot = entity.index() as usize;
self.alive.get(slot).copied().unwrap_or(false)
&& self.generations.get(slot).copied() == Some(entity.generation())
}
pub fn despawn(&mut self, entity: Entity) -> bool {
if !self.is_alive(entity) {
return false;
}
let slot = entity.index() as usize;
if let Some(alive) = self.alive.get_mut(slot) {
*alive = false;
}
if let Some(generation) = self.generations.get_mut(slot) {
*generation = generation.wrapping_add(1);
}
self.free.push(entity.index());
self.live_count = self.live_count.saturating_sub(1);
true
}
pub fn iter(&self) -> impl Iterator<Item = Entity> + '_ {
self.alive
.iter()
.enumerate()
.filter(|(_, alive)| **alive)
.filter_map(|(slot, _)| {
let index = u32::try_from(slot).ok()?;
let generation = self.generations.get(slot).copied()?;
Some(Entity::new(index, generation))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fresh_allocator_is_empty() {
let entities = Entities::new();
assert!(entities.is_empty());
assert_eq!(entities.len(), 0);
assert_eq!(entities.capacity(), 0);
}
#[test]
fn spawning_hands_out_distinct_slots() {
let mut entities = Entities::new();
let first = entities.spawn();
let second = entities.spawn();
assert_ne!(first, second);
assert_eq!(first.index(), 0);
assert_eq!(second.index(), 1);
assert_eq!(entities.len(), 2);
assert!(entities.is_alive(first));
assert!(entities.is_alive(second));
}
#[test]
fn a_stale_handle_does_not_name_the_slots_new_owner() {
let mut entities = Entities::new();
let old = entities.spawn();
assert!(entities.despawn(old));
let new = entities.spawn();
assert_eq!(new.index(), old.index(), "the slot must be reused");
assert_ne!(new.generation(), old.generation());
assert!(!entities.is_alive(old), "the stale handle must be dead");
assert!(entities.is_alive(new));
}
#[test]
fn despawning_twice_is_a_no_op_the_second_time() {
let mut entities = Entities::new();
let entity = entities.spawn();
assert!(entities.despawn(entity));
assert!(!entities.despawn(entity));
assert_eq!(entities.len(), 0);
}
#[test]
fn a_handle_from_another_allocator_is_not_alive_here() {
let mut one = Entities::new();
let mut other = Entities::new();
let _ = one.spawn();
let stranger = other.spawn();
assert!(one.is_alive(stranger), "documented limit, not a promise");
}
#[test]
fn iteration_is_in_ascending_slot_order() {
let mut entities = Entities::new();
let made: Vec<Entity> = (0..8).map(|_| entities.spawn()).collect();
for index in [1usize, 4, 5] {
assert!(entities.despawn(made[index]));
}
let seen: Vec<u32> = entities.iter().map(Entity::index).collect();
assert_eq!(seen, vec![0, 2, 3, 6, 7]);
}
#[test]
fn reuse_keeps_the_slot_range_compact() {
let mut entities = Entities::new();
let first = entities.spawn();
let second = entities.spawn();
entities.despawn(first);
entities.despawn(second);
let a = entities.spawn();
let b = entities.spawn();
assert_eq!(entities.capacity(), 2, "no new slots were needed");
assert!(entities.is_alive(a));
assert!(entities.is_alive(b));
}
#[test]
fn a_handle_prints_its_slot_and_generation() {
let mut entities = Entities::new();
let entity = entities.spawn();
assert_eq!(entity.to_string(), "e0v0");
}
}