Skip to main content

renew_ecs/
entity.rs

1//! Entity handles, and the allocator that recycles them.
2//!
3//! An entity is an index plus a generation. The index is a slot; the
4//! generation counts how many times that slot has been reused. Both are
5//! needed, and the reason is the bug the pair exists to prevent: without
6//! a generation, a handle to a despawned entity silently becomes a handle
7//! to whatever was spawned into its slot next — a use-after-free that the
8//! borrow checker cannot see, because nothing here is a reference.
9
10use core::fmt;
11
12/// A handle to an entity, valid only while its generation matches.
13///
14/// `Copy` and 64 bits, so passing one costs nothing and storing a million
15/// costs 8 MB. Deliberately not `Default`: a zeroed handle would name
16/// slot 0 at generation 0, which is a real entity, and a defaulted handle
17/// that accidentally works is worse than one that will not compile.
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct Entity {
20    index: u32,
21    generation: u32,
22}
23
24impl Entity {
25    /// The slot this entity occupies. Stable for the entity's lifetime,
26    /// and reused afterwards.
27    #[must_use]
28    pub const fn index(self) -> u32 {
29        self.index
30    }
31
32    /// How many times this slot had been used when the handle was issued.
33    #[must_use]
34    pub const fn generation(self) -> u32 {
35        self.generation
36    }
37
38    /// Construct a handle. Crate-internal: only the allocator may mint
39    /// one, because a hand-made handle could name a live entity it has no
40    /// right to.
41    pub(crate) const fn new(index: u32, generation: u32) -> Self {
42        Self { index, generation }
43    }
44}
45
46impl fmt::Display for Entity {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "e{}v{}", self.index, self.generation)
49    }
50}
51
52/// Hands out entity slots and recycles them.
53#[derive(Debug, Default)]
54pub struct Entities {
55    /// Generation per slot. A slot is alive when its generation is even
56    /// after the first spawn — see `alive`, which is tracked explicitly
57    /// rather than encoded in parity, because parity is the kind of
58    /// cleverness that is wrong once and then wrong forever.
59    generations: Vec<u32>,
60    alive: Vec<bool>,
61    /// Slots ready for reuse, newest first.
62    free: Vec<u32>,
63    live_count: usize,
64}
65
66impl Entities {
67    /// An allocator with no slots yet.
68    #[must_use]
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// How many entities are alive.
74    #[must_use]
75    pub fn len(&self) -> usize {
76        self.live_count
77    }
78
79    /// Whether no entity is alive.
80    #[must_use]
81    pub fn is_empty(&self) -> bool {
82        self.live_count == 0
83    }
84
85    /// The highest slot ever allocated, which bounds an ordered walk.
86    #[must_use]
87    pub fn capacity(&self) -> usize {
88        self.generations.len()
89    }
90
91    /// Allocate an entity, reusing a free slot when one exists.
92    ///
93    /// Reuse is newest-first, which keeps the index range compact: an
94    /// ordered query walks slots rather than entities, so a store whose
95    /// indices are spread thin pays for the gaps.
96    ///
97    /// # Panics
98    ///
99    /// Never in practice: the slot count is bounded by `u32::MAX`, and a
100    /// tree with four billion live entities has other problems. Written
101    /// as a saturating count rather than an unwrap so there is no panic
102    /// to reason about.
103    pub fn spawn(&mut self) -> Entity {
104        self.live_count = self.live_count.saturating_add(1);
105        if let Some(index) = self.free.pop() {
106            let slot = index as usize;
107            if let Some(alive) = self.alive.get_mut(slot) {
108                *alive = true;
109            }
110            let generation = self.generations.get(slot).copied().unwrap_or_default();
111            return Entity::new(index, generation);
112        }
113        let index = u32::try_from(self.generations.len()).unwrap_or(u32::MAX);
114        self.generations.push(0);
115        self.alive.push(true);
116        Entity::new(index, 0)
117    }
118
119    /// Whether this exact handle still names a live entity.
120    ///
121    /// Both halves are checked. A handle whose slot is alive but whose
122    /// generation is stale names an entity that no longer exists, and is
123    /// the whole reason the generation is there.
124    #[must_use]
125    pub fn is_alive(&self, entity: Entity) -> bool {
126        let slot = entity.index() as usize;
127        self.alive.get(slot).copied().unwrap_or(false)
128            && self.generations.get(slot).copied() == Some(entity.generation())
129    }
130
131    /// Free an entity's slot. Returns whether it was alive to begin with.
132    ///
133    /// Despawning an already-dead handle is a no-op rather than an error:
134    /// it is the natural outcome of two systems both deciding something
135    /// should go, and turning it into a failure would make every caller
136    /// check first.
137    pub fn despawn(&mut self, entity: Entity) -> bool {
138        if !self.is_alive(entity) {
139            return false;
140        }
141        let slot = entity.index() as usize;
142        if let Some(alive) = self.alive.get_mut(slot) {
143            *alive = false;
144        }
145        if let Some(generation) = self.generations.get_mut(slot) {
146            // Wrapping is the honest choice: at four billion reuses of one
147            // slot a handle from the first pass could alias, and there is
148            // no cheaper fix that does not leak slots forever. A
149            // saturating counter would stop detecting staleness at the
150            // same point while looking safe, which is worse. The bound is
151            // stated in the crate README beside the property it limits;
152            // it cannot be tested, because reaching it needs 2^32
153            // despawns of one slot.
154            *generation = generation.wrapping_add(1);
155        }
156        self.free.push(entity.index());
157        self.live_count = self.live_count.saturating_sub(1);
158        true
159    }
160
161    /// Every live entity, in ascending slot order.
162    ///
163    /// The order is part of the contract, not an accident of the
164    /// representation: see the crate docs.
165    pub fn iter(&self) -> impl Iterator<Item = Entity> + '_ {
166        self.alive
167            .iter()
168            .enumerate()
169            .filter(|(_, alive)| **alive)
170            .filter_map(|(slot, _)| {
171                let index = u32::try_from(slot).ok()?;
172                let generation = self.generations.get(slot).copied()?;
173                Some(Entity::new(index, generation))
174            })
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn a_fresh_allocator_is_empty() {
184        let entities = Entities::new();
185        assert!(entities.is_empty());
186        assert_eq!(entities.len(), 0);
187        assert_eq!(entities.capacity(), 0);
188    }
189
190    #[test]
191    fn spawning_hands_out_distinct_slots() {
192        let mut entities = Entities::new();
193        let first = entities.spawn();
194        let second = entities.spawn();
195        assert_ne!(first, second);
196        assert_eq!(first.index(), 0);
197        assert_eq!(second.index(), 1);
198        assert_eq!(entities.len(), 2);
199        assert!(entities.is_alive(first));
200        assert!(entities.is_alive(second));
201    }
202
203    /// The bug the generation exists to prevent: a stale handle must not
204    /// name whatever took its slot.
205    #[test]
206    fn a_stale_handle_does_not_name_the_slots_new_owner() {
207        let mut entities = Entities::new();
208        let old = entities.spawn();
209        assert!(entities.despawn(old));
210
211        let new = entities.spawn();
212        assert_eq!(new.index(), old.index(), "the slot must be reused");
213        assert_ne!(new.generation(), old.generation());
214
215        assert!(!entities.is_alive(old), "the stale handle must be dead");
216        assert!(entities.is_alive(new));
217    }
218
219    #[test]
220    fn despawning_twice_is_a_no_op_the_second_time() {
221        let mut entities = Entities::new();
222        let entity = entities.spawn();
223        assert!(entities.despawn(entity));
224        assert!(!entities.despawn(entity));
225        assert_eq!(entities.len(), 0);
226    }
227
228    #[test]
229    fn a_handle_from_another_allocator_is_not_alive_here() {
230        let mut one = Entities::new();
231        let mut other = Entities::new();
232        let _ = one.spawn();
233        let stranger = other.spawn();
234        // Same slot, same generation, different world. This is the one
235        // case the generation cannot catch, and the test exists to say so
236        // rather than to claim otherwise.
237        assert!(one.is_alive(stranger), "documented limit, not a promise");
238    }
239
240    #[test]
241    fn iteration_is_in_ascending_slot_order() {
242        let mut entities = Entities::new();
243        let made: Vec<Entity> = (0..8).map(|_| entities.spawn()).collect();
244        // Despawn a scattering, so the live set has gaps.
245        for index in [1usize, 4, 5] {
246            assert!(entities.despawn(made[index]));
247        }
248        let seen: Vec<u32> = entities.iter().map(Entity::index).collect();
249        assert_eq!(seen, vec![0, 2, 3, 6, 7]);
250    }
251
252    #[test]
253    fn reuse_keeps_the_slot_range_compact() {
254        let mut entities = Entities::new();
255        let first = entities.spawn();
256        let second = entities.spawn();
257        entities.despawn(first);
258        entities.despawn(second);
259        let a = entities.spawn();
260        let b = entities.spawn();
261        assert_eq!(entities.capacity(), 2, "no new slots were needed");
262        assert!(entities.is_alive(a));
263        assert!(entities.is_alive(b));
264    }
265
266    #[test]
267    fn a_handle_prints_its_slot_and_generation() {
268        let mut entities = Entities::new();
269        let entity = entities.spawn();
270        assert_eq!(entity.to_string(), "e0v0");
271    }
272}