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}