Skip to main content

goud_engine/ecs/entity/
allocator.rs

1//! Entity ID allocation with generation counting and free-list recycling.
2
3use std::fmt;
4
5use super::types::Entity;
6
7// =============================================================================
8// EntityAllocator
9// =============================================================================
10
11/// Manages entity ID allocation with generation counting and free-list recycling.
12///
13/// The `EntityAllocator` is responsible for creating and invalidating entities
14/// in a memory-efficient manner. It uses a generational index scheme where:
15///
16/// - Each slot has a generation counter that increments on deallocation
17/// - Deallocated slots are recycled via a free list
18/// - Entities carry their generation, allowing stale entity detection
19///
20/// # Design Pattern: Generational Arena Allocator
21///
22/// This pattern provides:
23/// - O(1) allocation (pop from free list or push new entry)
24/// - O(1) deallocation (push to free list, increment generation)
25/// - O(1) liveness check (compare generations)
26/// - Memory reuse without use-after-free bugs
27///
28/// # Difference from HandleAllocator
29///
30/// Unlike [`HandleAllocator<T>`](crate::core::handle::HandleAllocator), which is
31/// generic over a marker type for type safety, `EntityAllocator` is specifically
32/// designed for ECS entities and produces non-generic [`Entity`] values.
33///
34/// # Example
35///
36/// ```
37/// use goud_engine::ecs::entity::EntityAllocator;
38///
39/// let mut allocator = EntityAllocator::new();
40///
41/// // Allocate some entities
42/// let e1 = allocator.allocate();
43/// let e2 = allocator.allocate();
44///
45/// assert!(allocator.is_alive(e1));
46/// assert!(allocator.is_alive(e2));
47///
48/// // Deallocate e1
49/// assert!(allocator.deallocate(e1));
50/// assert!(!allocator.is_alive(e1));  // e1 is now stale
51///
52/// // Allocate again - may reuse e1's slot with new generation
53/// let e3 = allocator.allocate();
54/// assert!(allocator.is_alive(e3));
55///
56/// // e1 still references old generation, so it's still not alive
57/// assert!(!allocator.is_alive(e1));
58/// ```
59///
60/// # Thread Safety
61///
62/// `EntityAllocator` is NOT thread-safe. For concurrent access, wrap in
63/// appropriate synchronization primitives (Mutex, RwLock, etc.).
64pub struct EntityAllocator {
65    /// Generation counter for each slot.
66    ///
67    /// Index `i` stores the current generation for slot `i`.
68    /// Generation starts at 1 for new slots (0 is reserved for never-allocated).
69    pub(super) generations: Vec<u32>,
70
71    /// Stack of free slot indices available for reuse.
72    ///
73    /// When an entity is deallocated, its index is pushed here.
74    /// On allocation, we prefer to pop from this list before growing.
75    pub(super) free_list: Vec<u32>,
76}
77
78impl EntityAllocator {
79    /// Creates a new, empty entity allocator.
80    ///
81    /// The allocator starts with no pre-allocated capacity.
82    /// Use [`with_capacity`](Self::with_capacity) for bulk allocation scenarios.
83    ///
84    /// # Example
85    ///
86    /// ```
87    /// use goud_engine::ecs::entity::EntityAllocator;
88    ///
89    /// let allocator = EntityAllocator::new();
90    /// assert_eq!(allocator.len(), 0);
91    /// assert!(allocator.is_empty());
92    /// ```
93    #[inline]
94    pub fn new() -> Self {
95        Self {
96            generations: Vec::new(),
97            free_list: Vec::new(),
98        }
99    }
100
101    /// Creates a new entity allocator with pre-allocated capacity.
102    ///
103    /// This is useful when you know approximately how many entities you'll need,
104    /// as it avoids repeated reallocations during bulk allocation.
105    ///
106    /// Note that this only pre-allocates memory for the internal vectors;
107    /// it does not create any entities. Use [`allocate`](Self::allocate) to
108    /// actually create entities.
109    ///
110    /// # Arguments
111    ///
112    /// * `capacity` - The number of slots to pre-allocate
113    ///
114    /// # Example
115    ///
116    /// ```
117    /// use goud_engine::ecs::entity::EntityAllocator;
118    ///
119    /// // Pre-allocate space for 1000 entities
120    /// let mut allocator = EntityAllocator::with_capacity(1000);
121    ///
122    /// // No entities allocated yet, but memory is reserved
123    /// assert_eq!(allocator.len(), 0);
124    /// assert!(allocator.is_empty());
125    ///
126    /// // Allocations up to 1000 won't cause reallocation
127    /// for _ in 0..1000 {
128    ///     allocator.allocate();
129    /// }
130    /// assert_eq!(allocator.len(), 1000);
131    /// ```
132    #[inline]
133    pub fn with_capacity(capacity: usize) -> Self {
134        Self {
135            generations: Vec::with_capacity(capacity),
136            free_list: Vec::new(), // Free list starts empty, no freed slots yet
137        }
138    }
139
140    /// Allocates a new entity.
141    ///
142    /// If there are slots in the free list, one is reused with an incremented
143    /// generation. Otherwise, a new slot is created with generation 1.
144    ///
145    /// # Returns
146    ///
147    /// A new, valid entity that can be used in ECS operations.
148    ///
149    /// # Panics
150    ///
151    /// Panics if the number of slots exceeds `u32::MAX - 1` (unlikely in practice,
152    /// as this would require over 4 billion entity allocations without reuse).
153    ///
154    /// # Example
155    ///
156    /// ```
157    /// use goud_engine::ecs::entity::EntityAllocator;
158    ///
159    /// let mut allocator = EntityAllocator::new();
160    ///
161    /// let e1 = allocator.allocate();
162    /// let e2 = allocator.allocate();
163    ///
164    /// assert_ne!(e1, e2);
165    /// assert!(!e1.is_placeholder());
166    /// assert!(!e2.is_placeholder());
167    /// ```
168    pub fn allocate(&mut self) -> Entity {
169        if let Some(index) = self.free_list.pop() {
170            // Reuse a slot from the free list
171            // Generation was already incremented during deallocation
172            let generation = self.generations[index as usize];
173            Entity::new(index, generation)
174        } else {
175            // Allocate a new slot
176            let index = self.generations.len();
177
178            // Ensure we don't exceed u32::MAX - 1 (reserve MAX for PLACEHOLDER)
179            assert!(
180                index < u32::MAX as usize,
181                "EntityAllocator exceeded maximum capacity"
182            );
183
184            // New slots start at generation 1 (0 is reserved for never-allocated/PLACEHOLDER)
185            self.generations.push(1);
186
187            Entity::new(index as u32, 1)
188        }
189    }
190
191    /// Deallocates an entity, making it invalid.
192    ///
193    /// The slot's generation is incremented, invalidating any entity references
194    /// that use the old generation. The slot is added to the free list for
195    /// future reuse.
196    ///
197    /// # Arguments
198    ///
199    /// * `entity` - The entity to deallocate
200    ///
201    /// # Returns
202    ///
203    /// - `true` if the entity was valid and successfully deallocated
204    /// - `false` if the entity was invalid (wrong generation, out of bounds, or PLACEHOLDER)
205    ///
206    /// # Double Deallocation
207    ///
208    /// Attempting to deallocate the same entity twice returns `false` on the
209    /// second attempt, as the generation will have already been incremented.
210    ///
211    /// # Example
212    ///
213    /// ```
214    /// use goud_engine::ecs::entity::EntityAllocator;
215    ///
216    /// let mut allocator = EntityAllocator::new();
217    /// let entity = allocator.allocate();
218    ///
219    /// assert!(allocator.is_alive(entity));
220    /// assert!(allocator.deallocate(entity));  // First deallocation succeeds
221    /// assert!(!allocator.is_alive(entity));
222    /// assert!(!allocator.deallocate(entity)); // Second deallocation fails
223    /// ```
224    pub fn deallocate(&mut self, entity: Entity) -> bool {
225        // PLACEHOLDER entities cannot be deallocated
226        if entity.is_placeholder() {
227            return false;
228        }
229
230        let index = entity.index() as usize;
231
232        // Check bounds
233        if index >= self.generations.len() {
234            return false;
235        }
236
237        // Check generation matches (entity is still alive)
238        if self.generations[index] != entity.generation() {
239            return false;
240        }
241
242        // Increment generation to invalidate existing entity references
243        // Wrap at u32::MAX to 1 (skip 0, which is reserved)
244        let new_gen = self.generations[index].wrapping_add(1);
245        self.generations[index] = if new_gen == 0 { 1 } else { new_gen };
246
247        // Add to free list for reuse
248        self.free_list.push(entity.index());
249
250        true
251    }
252
253    /// Checks if an entity is currently alive.
254    ///
255    /// An entity is "alive" if:
256    /// - It is not the PLACEHOLDER sentinel
257    /// - Its index is within bounds
258    /// - Its generation matches the current slot generation
259    ///
260    /// # Arguments
261    ///
262    /// * `entity` - The entity to check
263    ///
264    /// # Returns
265    ///
266    /// `true` if the entity is alive, `false` otherwise.
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// use goud_engine::ecs::entity::EntityAllocator;
272    ///
273    /// let mut allocator = EntityAllocator::new();
274    /// let entity = allocator.allocate();
275    ///
276    /// assert!(allocator.is_alive(entity));
277    ///
278    /// allocator.deallocate(entity);
279    /// assert!(!allocator.is_alive(entity));
280    /// ```
281    #[inline]
282    pub fn is_alive(&self, entity: Entity) -> bool {
283        // PLACEHOLDER entities are never alive
284        if entity.is_placeholder() {
285            return false;
286        }
287
288        let index = entity.index() as usize;
289
290        // Check bounds and generation
291        index < self.generations.len() && self.generations[index] == entity.generation()
292    }
293
294    /// Returns the number of currently allocated (alive) entities.
295    ///
296    /// This is the total capacity minus the number of free slots.
297    ///
298    /// # Example
299    ///
300    /// ```
301    /// use goud_engine::ecs::entity::EntityAllocator;
302    ///
303    /// let mut allocator = EntityAllocator::new();
304    /// assert_eq!(allocator.len(), 0);
305    ///
306    /// let e1 = allocator.allocate();
307    /// let e2 = allocator.allocate();
308    /// assert_eq!(allocator.len(), 2);
309    ///
310    /// allocator.deallocate(e1);
311    /// assert_eq!(allocator.len(), 1);
312    /// ```
313    #[inline]
314    pub fn len(&self) -> usize {
315        self.generations.len() - self.free_list.len()
316    }
317
318    /// Returns the total number of slots (both allocated and free).
319    ///
320    /// This represents the high-water mark of allocations.
321    ///
322    /// # Example
323    ///
324    /// ```
325    /// use goud_engine::ecs::entity::EntityAllocator;
326    ///
327    /// let mut allocator = EntityAllocator::new();
328    /// let e1 = allocator.allocate();
329    /// let e2 = allocator.allocate();
330    ///
331    /// assert_eq!(allocator.capacity(), 2);
332    ///
333    /// allocator.deallocate(e1);
334    /// // Capacity remains 2, even though one slot is free
335    /// assert_eq!(allocator.capacity(), 2);
336    /// ```
337    #[inline]
338    pub fn capacity(&self) -> usize {
339        self.generations.len()
340    }
341
342    /// Returns `true` if no entities are currently allocated.
343    ///
344    /// # Example
345    ///
346    /// ```
347    /// use goud_engine::ecs::entity::EntityAllocator;
348    ///
349    /// let mut allocator = EntityAllocator::new();
350    /// assert!(allocator.is_empty());
351    ///
352    /// let entity = allocator.allocate();
353    /// assert!(!allocator.is_empty());
354    ///
355    /// allocator.deallocate(entity);
356    /// assert!(allocator.is_empty());
357    /// ```
358    #[inline]
359    pub fn is_empty(&self) -> bool {
360        self.len() == 0
361    }
362
363    /// Reserves capacity for at least `additional` more entities.
364    ///
365    /// This pre-allocates memory in the internal generations vector, avoiding
366    /// reallocations during subsequent allocations. Use this when you know
367    /// approximately how many entities you'll need.
368    ///
369    /// Note that this only affects the generations vector capacity. It does not
370    /// create any entities or affect the free list.
371    ///
372    /// # Arguments
373    ///
374    /// * `additional` - The number of additional slots to reserve
375    ///
376    /// # Example
377    ///
378    /// ```
379    /// use goud_engine::ecs::entity::EntityAllocator;
380    ///
381    /// let mut allocator = EntityAllocator::new();
382    ///
383    /// // Allocate some entities
384    /// let initial = allocator.allocate_batch(100);
385    /// assert_eq!(allocator.len(), 100);
386    ///
387    /// // Reserve space for 1000 more
388    /// allocator.reserve(1000);
389    ///
390    /// // Now allocations up to capacity won't cause reallocation
391    /// let more = allocator.allocate_batch(1000);
392    /// assert_eq!(allocator.len(), 1100);
393    /// ```
394    #[inline]
395    pub fn reserve(&mut self, additional: usize) {
396        self.generations.reserve(additional);
397    }
398}
399
400impl Default for EntityAllocator {
401    /// Creates an empty entity allocator.
402    ///
403    /// Equivalent to [`EntityAllocator::new()`].
404    #[inline]
405    fn default() -> Self {
406        Self::new()
407    }
408}
409
410impl fmt::Debug for EntityAllocator {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        f.debug_struct("EntityAllocator")
413            .field("len", &self.len())
414            .field("capacity", &self.capacity())
415            .field("free_slots", &self.free_list.len())
416            .finish()
417    }
418}