use crate::archetype::EntityLocation;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Entity {
pub(crate) index: u32,
pub(crate) generation: u32,
}
impl Entity {
pub fn from_raw(index: u32, generation: u32) -> Self {
Self { index, generation }
}
pub fn index(self) -> u32 {
self.index
}
pub fn generation(self) -> u32 {
self.generation
}
}
#[derive(Clone, Debug)]
pub(crate) struct EntityMeta {
pub generation: u32,
pub alive: bool,
pub location: Option<EntityLocation>,
}
impl EntityMeta {
fn new_alive() -> Self {
Self {
generation: 0,
alive: true,
location: None,
}
}
}
pub(crate) struct EntityMetaStore {
metas: Vec<EntityMeta>,
free: Vec<u32>,
}
impl EntityMetaStore {
pub fn new() -> Self {
Self {
metas: Vec::new(),
free: Vec::new(),
}
}
pub fn alloc(&mut self) -> Entity {
if let Some(index) = self.free.pop() {
let meta = &mut self.metas[index as usize];
meta.alive = true;
meta.location = None;
Entity {
index,
generation: meta.generation,
}
} else {
let index = self.metas.len() as u32;
self.metas.push(EntityMeta::new_alive());
Entity {
index,
generation: 0,
}
}
}
pub fn dealloc(&mut self, entity: Entity) -> bool {
let idx = entity.index as usize;
if idx < self.metas.len() {
let meta = &mut self.metas[idx];
if meta.generation == entity.generation && meta.alive {
meta.alive = false;
meta.generation += 1;
meta.location = None;
self.free.push(entity.index);
return true;
}
}
false
}
pub fn is_alive(&self, entity: Entity) -> bool {
let idx = entity.index as usize;
idx < self.metas.len()
&& self.metas[idx].generation == entity.generation
&& self.metas[idx].alive
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.metas.len()
}
pub fn set_location(&mut self, entity: Entity, location: EntityLocation) {
let idx = entity.index as usize;
debug_assert!(self.is_alive(entity), "set_location on dead entity");
self.metas[idx].location = Some(location);
}
pub fn get_location(&self, entity: Entity) -> Option<EntityLocation> {
let idx = entity.index as usize;
if self.is_alive(entity) {
self.metas[idx].location
} else {
None
}
}
#[allow(dead_code)]
pub fn entity_at(&self, index: u32) -> Option<Entity> {
let idx = index as usize;
if idx < self.metas.len() && self.metas[idx].alive {
Some(Entity {
index,
generation: self.metas[idx].generation,
})
} else {
None
}
}
pub fn alive_entities(&self) -> impl Iterator<Item = Entity> + '_ {
self.metas
.iter()
.enumerate()
.filter(|(_, meta)| meta.alive)
.map(|(i, meta)| Entity {
index: i as u32,
generation: meta.generation,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::archetype::{ArchetypeId, EntityLocation};
#[test]
fn meta_store_alloc_returns_sequential_indices() {
let mut store = EntityMetaStore::new();
let e0 = store.alloc();
let e1 = store.alloc();
assert_eq!(e0.index, 0);
assert_eq!(e1.index, 1);
assert_eq!(e0.generation, 0);
assert_eq!(e1.generation, 0);
}
#[test]
fn meta_store_dealloc_and_reuse_bumps_generation() {
let mut store = EntityMetaStore::new();
let e0 = store.alloc();
assert!(store.dealloc(e0));
let e0_reused = store.alloc();
assert_eq!(e0_reused.index, 0);
assert_eq!(e0_reused.generation, 1);
}
#[test]
fn meta_store_is_alive_returns_false_after_dealloc() {
let mut store = EntityMetaStore::new();
let e = store.alloc();
assert!(store.is_alive(e));
store.dealloc(e);
assert!(!store.is_alive(e));
}
#[test]
fn meta_store_stale_handle_is_not_alive() {
let mut store = EntityMetaStore::new();
let old = store.alloc();
store.dealloc(old);
let _new = store.alloc();
assert!(!store.is_alive(old));
}
#[test]
fn meta_store_double_dealloc_returns_false() {
let mut store = EntityMetaStore::new();
let e = store.alloc();
assert!(store.dealloc(e));
assert!(!store.dealloc(e));
}
#[test]
fn meta_store_alive_entities_iterates_only_living() {
let mut store = EntityMetaStore::new();
let _e0 = store.alloc();
let e1 = store.alloc();
let _e2 = store.alloc();
store.dealloc(e1);
let alive: Vec<_> = store.alive_entities().collect();
assert_eq!(alive.len(), 2);
assert_eq!(alive[0].index, 0);
assert_eq!(alive[1].index, 2);
}
#[test]
fn meta_store_location_tracking() {
let mut store = EntityMetaStore::new();
let e = store.alloc();
assert_eq!(store.get_location(e), None);
let loc = EntityLocation {
archetype_id: ArchetypeId(0),
row: 3,
};
store.set_location(e, loc);
assert_eq!(store.get_location(e), Some(loc));
store.dealloc(e);
assert_eq!(store.get_location(e), None);
}
#[test]
fn meta_store_realloc_clears_location() {
let mut store = EntityMetaStore::new();
let e = store.alloc();
store.set_location(
e,
EntityLocation {
archetype_id: ArchetypeId(5),
row: 2,
},
);
store.dealloc(e);
let e2 = store.alloc(); assert_eq!(e2.index, 0);
assert_eq!(store.get_location(e2), None); }
}