Skip to main content

libdqg/ecs/
entity.rs

1/// A generational handle to an entity in a [`crate::world::World`].
2///
3/// The generation guards against a stale `Entity` (held after despawn) silently referring to
4/// whatever new entity was later allocated at the same index.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub struct Entity {
7    pub(crate) index: u32,
8    pub(crate) generation: u32,
9}
10
11/// Allocates and recycles [`Entity`] indices, bumping an index's generation on despawn so any
12/// `Entity` handle still held to it goes stale rather than aliasing whatever is spawned next.
13#[derive(Clone)]
14pub(crate) struct EntityAllocator {
15    generations: Vec<u32>,
16    free_list: Vec<u32>,
17}
18
19impl EntityAllocator {
20    pub fn new() -> Self {
21        Self { generations: Vec::new(), free_list: Vec::new() }
22    }
23
24    pub fn spawn(&mut self) -> Entity {
25        if let Some(index) = self.free_list.pop() {
26            Entity { index, generation: self.generations[index as usize] }
27        } else {
28            let index = self.generations.len() as u32;
29            self.generations.push(0);
30            Entity { index, generation: 0 }
31        }
32    }
33
34    pub fn despawn(&mut self, entity: Entity) -> bool {
35        if !self.is_alive(entity) {
36            return false;
37        }
38        self.generations[entity.index as usize] = self.generations[entity.index as usize].wrapping_add(1);
39        self.free_list.push(entity.index);
40        true
41    }
42
43    pub fn is_alive(&self, entity: Entity) -> bool {
44        self.generations.get(entity.index as usize).copied() == Some(entity.generation)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn recycled_index_gets_a_new_generation() {
54        let mut allocator = EntityAllocator::new();
55        let a = allocator.spawn();
56        allocator.despawn(a);
57        let b = allocator.spawn();
58
59        assert_eq!(a.index, b.index);
60        assert_ne!(a.generation, b.generation);
61        assert!(!allocator.is_alive(a));
62        assert!(allocator.is_alive(b));
63    }
64
65    #[test]
66    fn distinct_spawns_get_distinct_indices() {
67        let mut allocator = EntityAllocator::new();
68        let a = allocator.spawn();
69        let b = allocator.spawn();
70        assert_ne!(a.index, b.index);
71    }
72}