goud_engine/ecs/entity.rs
1//! Entity identifiers for the ECS.
2//!
3//! Entities are lightweight identifiers that serve as keys for component data.
4//! Unlike [`Handle<T>`](crate::core::handle::Handle), entities are not generic -
5//! they identify an entity across all component storages rather than a specific
6//! resource type.
7//!
8//! # Design Pattern: Generational Indices
9//!
10//! Entities use the same generational index pattern as handles:
11//!
12//! 1. Each entity has an index (slot in the entity array) and a generation
13//! 2. When an entity is despawned, its generation increments
14//! 3. Old entity references become stale (generation mismatch)
15//! 4. The slot can be reused for new entities with the new generation
16//!
17//! This prevents "dangling entity" bugs where code holds a reference to a
18//! despawned entity and accidentally accesses data from a new entity that
19//! reused the same slot.
20//!
21//! # Example
22//!
23//! ```
24//! use goud_engine::ecs::entity::Entity;
25//!
26//! // Entities are typically created by EntityAllocator, but can be constructed directly
27//! let entity = Entity::new(0, 1);
28//!
29//! assert_eq!(entity.index(), 0);
30//! assert_eq!(entity.generation(), 1);
31//!
32//! // PLACEHOLDER is a special sentinel value
33//! assert!(Entity::PLACEHOLDER.is_placeholder());
34//! ```
35//!
36//! # FFI Safety
37//!
38//! Entity uses `#[repr(C)]` for predictable memory layout across FFI boundaries.
39//! The struct is exactly 8 bytes: 4 bytes for index + 4 bytes for generation.
40
41use std::fmt;
42use std::hash::{Hash, Hasher};
43
44// =============================================================================
45// EntityAllocator
46// =============================================================================
47
48/// Manages entity ID allocation with generation counting and free-list recycling.
49///
50/// The `EntityAllocator` is responsible for creating and invalidating entities
51/// in a memory-efficient manner. It uses a generational index scheme where:
52///
53/// - Each slot has a generation counter that increments on deallocation
54/// - Deallocated slots are recycled via a free list
55/// - Entities carry their generation, allowing stale entity detection
56///
57/// # Design Pattern: Generational Arena Allocator
58///
59/// This pattern provides:
60/// - O(1) allocation (pop from free list or push new entry)
61/// - O(1) deallocation (push to free list, increment generation)
62/// - O(1) liveness check (compare generations)
63/// - Memory reuse without use-after-free bugs
64///
65/// # Difference from HandleAllocator
66///
67/// Unlike [`HandleAllocator<T>`](crate::core::handle::HandleAllocator), which is
68/// generic over a marker type for type safety, `EntityAllocator` is specifically
69/// designed for ECS entities and produces non-generic [`Entity`] values.
70///
71/// # Example
72///
73/// ```
74/// use goud_engine::ecs::entity::EntityAllocator;
75///
76/// let mut allocator = EntityAllocator::new();
77///
78/// // Allocate some entities
79/// let e1 = allocator.allocate();
80/// let e2 = allocator.allocate();
81///
82/// assert!(allocator.is_alive(e1));
83/// assert!(allocator.is_alive(e2));
84///
85/// // Deallocate e1
86/// assert!(allocator.deallocate(e1));
87/// assert!(!allocator.is_alive(e1)); // e1 is now stale
88///
89/// // Allocate again - may reuse e1's slot with new generation
90/// let e3 = allocator.allocate();
91/// assert!(allocator.is_alive(e3));
92///
93/// // e1 still references old generation, so it's still not alive
94/// assert!(!allocator.is_alive(e1));
95/// ```
96///
97/// # Thread Safety
98///
99/// `EntityAllocator` is NOT thread-safe. For concurrent access, wrap in
100/// appropriate synchronization primitives (Mutex, RwLock, etc.).
101pub struct EntityAllocator {
102 /// Generation counter for each slot.
103 ///
104 /// Index `i` stores the current generation for slot `i`.
105 /// Generation starts at 1 for new slots (0 is reserved for never-allocated).
106 generations: Vec<u32>,
107
108 /// Stack of free slot indices available for reuse.
109 ///
110 /// When an entity is deallocated, its index is pushed here.
111 /// On allocation, we prefer to pop from this list before growing.
112 free_list: Vec<u32>,
113}
114
115impl EntityAllocator {
116 /// Creates a new, empty entity allocator.
117 ///
118 /// The allocator starts with no pre-allocated capacity.
119 /// Use [`with_capacity`](Self::with_capacity) for bulk allocation scenarios.
120 ///
121 /// # Example
122 ///
123 /// ```
124 /// use goud_engine::ecs::entity::EntityAllocator;
125 ///
126 /// let allocator = EntityAllocator::new();
127 /// assert_eq!(allocator.len(), 0);
128 /// assert!(allocator.is_empty());
129 /// ```
130 #[inline]
131 pub fn new() -> Self {
132 Self {
133 generations: Vec::new(),
134 free_list: Vec::new(),
135 }
136 }
137
138 /// Creates a new entity allocator with pre-allocated capacity.
139 ///
140 /// This is useful when you know approximately how many entities you'll need,
141 /// as it avoids repeated reallocations during bulk allocation.
142 ///
143 /// Note that this only pre-allocates memory for the internal vectors;
144 /// it does not create any entities. Use [`allocate`](Self::allocate) to
145 /// actually create entities.
146 ///
147 /// # Arguments
148 ///
149 /// * `capacity` - The number of slots to pre-allocate
150 ///
151 /// # Example
152 ///
153 /// ```
154 /// use goud_engine::ecs::entity::EntityAllocator;
155 ///
156 /// // Pre-allocate space for 1000 entities
157 /// let mut allocator = EntityAllocator::with_capacity(1000);
158 ///
159 /// // No entities allocated yet, but memory is reserved
160 /// assert_eq!(allocator.len(), 0);
161 /// assert!(allocator.is_empty());
162 ///
163 /// // Allocations up to 1000 won't cause reallocation
164 /// for _ in 0..1000 {
165 /// allocator.allocate();
166 /// }
167 /// assert_eq!(allocator.len(), 1000);
168 /// ```
169 #[inline]
170 pub fn with_capacity(capacity: usize) -> Self {
171 Self {
172 generations: Vec::with_capacity(capacity),
173 free_list: Vec::new(), // Free list starts empty, no freed slots yet
174 }
175 }
176
177 /// Allocates a new entity.
178 ///
179 /// If there are slots in the free list, one is reused with an incremented
180 /// generation. Otherwise, a new slot is created with generation 1.
181 ///
182 /// # Returns
183 ///
184 /// A new, valid entity that can be used in ECS operations.
185 ///
186 /// # Panics
187 ///
188 /// Panics if the number of slots exceeds `u32::MAX - 1` (unlikely in practice,
189 /// as this would require over 4 billion entity allocations without reuse).
190 ///
191 /// # Example
192 ///
193 /// ```
194 /// use goud_engine::ecs::entity::EntityAllocator;
195 ///
196 /// let mut allocator = EntityAllocator::new();
197 ///
198 /// let e1 = allocator.allocate();
199 /// let e2 = allocator.allocate();
200 ///
201 /// assert_ne!(e1, e2);
202 /// assert!(!e1.is_placeholder());
203 /// assert!(!e2.is_placeholder());
204 /// ```
205 pub fn allocate(&mut self) -> Entity {
206 if let Some(index) = self.free_list.pop() {
207 // Reuse a slot from the free list
208 // Generation was already incremented during deallocation
209 let generation = self.generations[index as usize];
210 Entity::new(index, generation)
211 } else {
212 // Allocate a new slot
213 let index = self.generations.len();
214
215 // Ensure we don't exceed u32::MAX - 1 (reserve MAX for PLACEHOLDER)
216 assert!(
217 index < u32::MAX as usize,
218 "EntityAllocator exceeded maximum capacity"
219 );
220
221 // New slots start at generation 1 (0 is reserved for never-allocated/PLACEHOLDER)
222 self.generations.push(1);
223
224 Entity::new(index as u32, 1)
225 }
226 }
227
228 /// Deallocates an entity, making it invalid.
229 ///
230 /// The slot's generation is incremented, invalidating any entity references
231 /// that use the old generation. The slot is added to the free list for
232 /// future reuse.
233 ///
234 /// # Arguments
235 ///
236 /// * `entity` - The entity to deallocate
237 ///
238 /// # Returns
239 ///
240 /// - `true` if the entity was valid and successfully deallocated
241 /// - `false` if the entity was invalid (wrong generation, out of bounds, or PLACEHOLDER)
242 ///
243 /// # Double Deallocation
244 ///
245 /// Attempting to deallocate the same entity twice returns `false` on the
246 /// second attempt, as the generation will have already been incremented.
247 ///
248 /// # Example
249 ///
250 /// ```
251 /// use goud_engine::ecs::entity::EntityAllocator;
252 ///
253 /// let mut allocator = EntityAllocator::new();
254 /// let entity = allocator.allocate();
255 ///
256 /// assert!(allocator.is_alive(entity));
257 /// assert!(allocator.deallocate(entity)); // First deallocation succeeds
258 /// assert!(!allocator.is_alive(entity));
259 /// assert!(!allocator.deallocate(entity)); // Second deallocation fails
260 /// ```
261 pub fn deallocate(&mut self, entity: Entity) -> bool {
262 // PLACEHOLDER entities cannot be deallocated
263 if entity.is_placeholder() {
264 return false;
265 }
266
267 let index = entity.index() as usize;
268
269 // Check bounds
270 if index >= self.generations.len() {
271 return false;
272 }
273
274 // Check generation matches (entity is still alive)
275 if self.generations[index] != entity.generation() {
276 return false;
277 }
278
279 // Increment generation to invalidate existing entity references
280 // Wrap at u32::MAX to 1 (skip 0, which is reserved)
281 let new_gen = self.generations[index].wrapping_add(1);
282 self.generations[index] = if new_gen == 0 { 1 } else { new_gen };
283
284 // Add to free list for reuse
285 self.free_list.push(entity.index());
286
287 true
288 }
289
290 /// Checks if an entity is currently alive.
291 ///
292 /// An entity is "alive" if:
293 /// - It is not the PLACEHOLDER sentinel
294 /// - Its index is within bounds
295 /// - Its generation matches the current slot generation
296 ///
297 /// # Arguments
298 ///
299 /// * `entity` - The entity to check
300 ///
301 /// # Returns
302 ///
303 /// `true` if the entity is alive, `false` otherwise.
304 ///
305 /// # Example
306 ///
307 /// ```
308 /// use goud_engine::ecs::entity::EntityAllocator;
309 ///
310 /// let mut allocator = EntityAllocator::new();
311 /// let entity = allocator.allocate();
312 ///
313 /// assert!(allocator.is_alive(entity));
314 ///
315 /// allocator.deallocate(entity);
316 /// assert!(!allocator.is_alive(entity));
317 /// ```
318 #[inline]
319 pub fn is_alive(&self, entity: Entity) -> bool {
320 // PLACEHOLDER entities are never alive
321 if entity.is_placeholder() {
322 return false;
323 }
324
325 let index = entity.index() as usize;
326
327 // Check bounds and generation
328 index < self.generations.len() && self.generations[index] == entity.generation()
329 }
330
331 /// Returns the number of currently allocated (alive) entities.
332 ///
333 /// This is the total capacity minus the number of free slots.
334 ///
335 /// # Example
336 ///
337 /// ```
338 /// use goud_engine::ecs::entity::EntityAllocator;
339 ///
340 /// let mut allocator = EntityAllocator::new();
341 /// assert_eq!(allocator.len(), 0);
342 ///
343 /// let e1 = allocator.allocate();
344 /// let e2 = allocator.allocate();
345 /// assert_eq!(allocator.len(), 2);
346 ///
347 /// allocator.deallocate(e1);
348 /// assert_eq!(allocator.len(), 1);
349 /// ```
350 #[inline]
351 pub fn len(&self) -> usize {
352 self.generations.len() - self.free_list.len()
353 }
354
355 /// Returns the total number of slots (both allocated and free).
356 ///
357 /// This represents the high-water mark of allocations.
358 ///
359 /// # Example
360 ///
361 /// ```
362 /// use goud_engine::ecs::entity::EntityAllocator;
363 ///
364 /// let mut allocator = EntityAllocator::new();
365 /// let e1 = allocator.allocate();
366 /// let e2 = allocator.allocate();
367 ///
368 /// assert_eq!(allocator.capacity(), 2);
369 ///
370 /// allocator.deallocate(e1);
371 /// // Capacity remains 2, even though one slot is free
372 /// assert_eq!(allocator.capacity(), 2);
373 /// ```
374 #[inline]
375 pub fn capacity(&self) -> usize {
376 self.generations.len()
377 }
378
379 /// Returns `true` if no entities are currently allocated.
380 ///
381 /// # Example
382 ///
383 /// ```
384 /// use goud_engine::ecs::entity::EntityAllocator;
385 ///
386 /// let mut allocator = EntityAllocator::new();
387 /// assert!(allocator.is_empty());
388 ///
389 /// let entity = allocator.allocate();
390 /// assert!(!allocator.is_empty());
391 ///
392 /// allocator.deallocate(entity);
393 /// assert!(allocator.is_empty());
394 /// ```
395 #[inline]
396 pub fn is_empty(&self) -> bool {
397 self.len() == 0
398 }
399
400 // =========================================================================
401 // Bulk Operations
402 // =========================================================================
403
404 /// Allocates multiple entities at once.
405 ///
406 /// This is more efficient than calling [`allocate`](Self::allocate) in a loop
407 /// because it pre-allocates the result vector and minimizes reallocations.
408 ///
409 /// # Arguments
410 ///
411 /// * `count` - The number of entities to allocate
412 ///
413 /// # Returns
414 ///
415 /// A vector containing `count` newly allocated entities. All entities are
416 /// guaranteed to be valid and unique.
417 ///
418 /// # Panics
419 ///
420 /// Panics if allocating `count` entities would exceed `u32::MAX - 1` total slots.
421 ///
422 /// # Performance
423 ///
424 /// For large batch allocations, this method:
425 /// - Pre-allocates the result vector with exact capacity
426 /// - Reuses free slots first (LIFO order)
427 /// - Bulk-extends the generations vector for remaining slots
428 ///
429 /// # Example
430 ///
431 /// ```
432 /// use goud_engine::ecs::entity::EntityAllocator;
433 ///
434 /// let mut allocator = EntityAllocator::new();
435 ///
436 /// // Allocate 1000 entities in one call
437 /// let entities = allocator.allocate_batch(1000);
438 /// assert_eq!(entities.len(), 1000);
439 /// assert_eq!(allocator.len(), 1000);
440 ///
441 /// // All entities are unique and alive
442 /// for entity in &entities {
443 /// assert!(allocator.is_alive(*entity));
444 /// }
445 /// ```
446 pub fn allocate_batch(&mut self, count: usize) -> Vec<Entity> {
447 if count == 0 {
448 return Vec::new();
449 }
450
451 let mut entities = Vec::with_capacity(count);
452
453 // First, use up free slots
454 let from_free_list = count.min(self.free_list.len());
455 for _ in 0..from_free_list {
456 if let Some(index) = self.free_list.pop() {
457 let generation = self.generations[index as usize];
458 entities.push(Entity::new(index, generation));
459 }
460 }
461
462 // Then, allocate new slots for remaining count
463 let remaining = count - from_free_list;
464 if remaining > 0 {
465 let start_index = self.generations.len();
466 let end_index = start_index + remaining;
467
468 // Ensure we don't exceed maximum capacity
469 assert!(
470 end_index <= u32::MAX as usize,
471 "EntityAllocator would exceed maximum capacity"
472 );
473
474 // Bulk-extend generations vector with initial generation of 1
475 self.generations.resize(end_index, 1);
476
477 // Create entities for new slots
478 for index in start_index..end_index {
479 entities.push(Entity::new(index as u32, 1));
480 }
481 }
482
483 entities
484 }
485
486 /// Deallocates multiple entities at once.
487 ///
488 /// This method attempts to deallocate each entity in the slice. Invalid
489 /// entities (already deallocated, wrong generation, out of bounds, or
490 /// PLACEHOLDER) are skipped without error.
491 ///
492 /// # Arguments
493 ///
494 /// * `entities` - A slice of entities to deallocate
495 ///
496 /// # Returns
497 ///
498 /// The number of entities that were successfully deallocated.
499 ///
500 /// # Example
501 ///
502 /// ```
503 /// use goud_engine::ecs::entity::EntityAllocator;
504 ///
505 /// let mut allocator = EntityAllocator::new();
506 /// let entities = allocator.allocate_batch(100);
507 /// assert_eq!(allocator.len(), 100);
508 ///
509 /// // Deallocate all at once
510 /// let deallocated = allocator.deallocate_batch(&entities);
511 /// assert_eq!(deallocated, 100);
512 /// assert_eq!(allocator.len(), 0);
513 ///
514 /// // Second deallocation returns 0 (already dead)
515 /// let deallocated_again = allocator.deallocate_batch(&entities);
516 /// assert_eq!(deallocated_again, 0);
517 /// ```
518 pub fn deallocate_batch(&mut self, entities: &[Entity]) -> usize {
519 let mut success_count = 0;
520
521 for entity in entities {
522 if self.deallocate(*entity) {
523 success_count += 1;
524 }
525 }
526
527 success_count
528 }
529
530 /// Reserves capacity for at least `additional` more entities.
531 ///
532 /// This pre-allocates memory in the internal generations vector, avoiding
533 /// reallocations during subsequent allocations. Use this when you know
534 /// approximately how many entities you'll need.
535 ///
536 /// Note that this only affects the generations vector capacity. It does not
537 /// create any entities or affect the free list.
538 ///
539 /// # Arguments
540 ///
541 /// * `additional` - The number of additional slots to reserve
542 ///
543 /// # Example
544 ///
545 /// ```
546 /// use goud_engine::ecs::entity::EntityAllocator;
547 ///
548 /// let mut allocator = EntityAllocator::new();
549 ///
550 /// // Allocate some entities
551 /// let initial = allocator.allocate_batch(100);
552 /// assert_eq!(allocator.len(), 100);
553 ///
554 /// // Reserve space for 1000 more
555 /// allocator.reserve(1000);
556 ///
557 /// // Now allocations up to capacity won't cause reallocation
558 /// let more = allocator.allocate_batch(1000);
559 /// assert_eq!(allocator.len(), 1100);
560 /// ```
561 #[inline]
562 pub fn reserve(&mut self, additional: usize) {
563 self.generations.reserve(additional);
564 }
565}
566
567impl Default for EntityAllocator {
568 /// Creates an empty entity allocator.
569 ///
570 /// Equivalent to [`EntityAllocator::new()`].
571 #[inline]
572 fn default() -> Self {
573 Self::new()
574 }
575}
576
577impl fmt::Debug for EntityAllocator {
578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579 f.debug_struct("EntityAllocator")
580 .field("len", &self.len())
581 .field("capacity", &self.capacity())
582 .field("free_slots", &self.free_list.len())
583 .finish()
584 }
585}
586
587// =============================================================================
588// Entity
589// =============================================================================
590
591/// A lightweight identifier for an entity in the ECS.
592///
593/// Entities are the "E" in ECS - they are identifiers that components attach to.
594/// An entity by itself has no data; it's purely an ID used to look up components.
595///
596/// # Memory Layout
597///
598/// ```text
599/// Entity (8 bytes total):
600/// ┌────────────────┬────────────────┐
601/// │ index (u32) │ generation(u32)│
602/// └────────────────┴────────────────┘
603/// ```
604///
605/// # Thread Safety
606///
607/// Entity is `Copy`, `Clone`, `Send`, and `Sync`. Entity values can be freely
608/// shared across threads. However, operations on the ECS world that use entities
609/// require appropriate synchronization.
610#[repr(C)]
611#[derive(Clone, Copy, PartialEq, Eq)]
612pub struct Entity {
613 /// The index of this entity in the entity array.
614 ///
615 /// This corresponds to a slot in the entity allocator.
616 index: u32,
617
618 /// The generation of this entity.
619 ///
620 /// Incremented each time a slot is reused, allowing detection of stale
621 /// entity references.
622 generation: u32,
623}
624
625impl Entity {
626 /// A placeholder entity that should never be returned by the allocator.
627 ///
628 /// Use this as a sentinel value for "no entity" or uninitialized entity fields.
629 /// The placeholder uses `u32::MAX` for the index, which the allocator will never
630 /// return (it would require 4 billion+ entity allocations first).
631 ///
632 /// # Example
633 ///
634 /// ```
635 /// use goud_engine::ecs::entity::Entity;
636 ///
637 /// let mut maybe_entity = Entity::PLACEHOLDER;
638 /// assert!(maybe_entity.is_placeholder());
639 ///
640 /// // Later, assign a real entity
641 /// maybe_entity = Entity::new(0, 1);
642 /// assert!(!maybe_entity.is_placeholder());
643 /// ```
644 pub const PLACEHOLDER: Entity = Entity {
645 index: u32::MAX,
646 generation: 0,
647 };
648
649 /// Creates a new entity with the given index and generation.
650 ///
651 /// This is primarily used by [`EntityAllocator`]. Direct construction is
652 /// possible but not recommended for typical use.
653 ///
654 /// # Arguments
655 ///
656 /// * `index` - The slot index for this entity
657 /// * `generation` - The generation counter for this slot
658 ///
659 /// # Example
660 ///
661 /// ```
662 /// use goud_engine::ecs::entity::Entity;
663 ///
664 /// let entity = Entity::new(42, 1);
665 /// assert_eq!(entity.index(), 42);
666 /// assert_eq!(entity.generation(), 1);
667 /// ```
668 #[inline]
669 pub const fn new(index: u32, generation: u32) -> Self {
670 Self { index, generation }
671 }
672
673 /// Returns the index of this entity.
674 ///
675 /// The index is the slot in the entity allocator's internal array.
676 /// Multiple entities may share the same index (at different times),
677 /// distinguished by their generation.
678 #[inline]
679 pub const fn index(&self) -> u32 {
680 self.index
681 }
682
683 /// Returns the generation of this entity.
684 ///
685 /// The generation increments each time a slot is reused. Comparing generations
686 /// allows detecting stale entity references.
687 #[inline]
688 pub const fn generation(&self) -> u32 {
689 self.generation
690 }
691
692 /// Returns `true` if this is the placeholder entity.
693 ///
694 /// The placeholder entity should never be used in actual ECS operations.
695 /// It's a sentinel value for "no entity" situations.
696 #[inline]
697 pub const fn is_placeholder(&self) -> bool {
698 self.index == u32::MAX && self.generation == 0
699 }
700
701 /// Packs the entity into a single `u64` value.
702 ///
703 /// Format: upper 32 bits = generation, lower 32 bits = index.
704 ///
705 /// This is useful for FFI or when a single integer representation is needed.
706 ///
707 /// # Example
708 ///
709 /// ```
710 /// use goud_engine::ecs::entity::Entity;
711 ///
712 /// let entity = Entity::new(42, 7);
713 /// let packed = entity.to_bits();
714 ///
715 /// // Upper 32 bits: generation (7), Lower 32 bits: index (42)
716 /// assert_eq!(packed, (7_u64 << 32) | 42);
717 ///
718 /// // Round-trip
719 /// let unpacked = Entity::from_bits(packed);
720 /// assert_eq!(entity, unpacked);
721 /// ```
722 #[inline]
723 pub const fn to_bits(&self) -> u64 {
724 ((self.generation as u64) << 32) | (self.index as u64)
725 }
726
727 /// Creates an entity from a packed `u64` value.
728 ///
729 /// Format: upper 32 bits = generation, lower 32 bits = index.
730 ///
731 /// # Example
732 ///
733 /// ```
734 /// use goud_engine::ecs::entity::Entity;
735 ///
736 /// let packed = (3_u64 << 32) | 100;
737 /// let entity = Entity::from_bits(packed);
738 ///
739 /// assert_eq!(entity.index(), 100);
740 /// assert_eq!(entity.generation(), 3);
741 /// ```
742 #[inline]
743 pub const fn from_bits(bits: u64) -> Self {
744 Self {
745 index: bits as u32,
746 generation: (bits >> 32) as u32,
747 }
748 }
749}
750
751// =============================================================================
752// Trait Implementations
753// =============================================================================
754
755impl Hash for Entity {
756 /// Hashes the entity by combining index and generation.
757 ///
758 /// Two entities with the same index but different generations will hash
759 /// differently, which is important for using entities as map keys.
760 #[inline]
761 fn hash<H: Hasher>(&self, state: &mut H) {
762 // Hash as u64 for efficiency (single hash operation)
763 self.to_bits().hash(state);
764 }
765}
766
767impl fmt::Debug for Entity {
768 /// Formats the entity as `Entity(index:generation)`.
769 ///
770 /// # Example
771 ///
772 /// ```
773 /// use goud_engine::ecs::entity::Entity;
774 ///
775 /// let entity = Entity::new(42, 3);
776 /// assert_eq!(format!("{:?}", entity), "Entity(42:3)");
777 /// ```
778 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
779 write!(f, "Entity({}:{})", self.index, self.generation)
780 }
781}
782
783impl fmt::Display for Entity {
784 /// Formats the entity in a user-friendly way.
785 ///
786 /// Same format as Debug: `Entity(index:generation)`.
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 write!(f, "Entity({}:{})", self.index, self.generation)
789 }
790}
791
792impl Default for Entity {
793 /// Returns the placeholder entity.
794 ///
795 /// Using Default returns PLACEHOLDER, which should be treated as "no entity".
796 #[inline]
797 fn default() -> Self {
798 Self::PLACEHOLDER
799 }
800}
801
802impl From<Entity> for u64 {
803 /// Converts an entity to its packed `u64` representation.
804 #[inline]
805 fn from(entity: Entity) -> Self {
806 entity.to_bits()
807 }
808}
809
810impl From<u64> for Entity {
811 /// Creates an entity from a packed `u64` representation.
812 #[inline]
813 fn from(bits: u64) -> Self {
814 Entity::from_bits(bits)
815 }
816}
817
818// =============================================================================
819// Unit Tests
820// =============================================================================
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825 use std::collections::HashSet;
826
827 // -------------------------------------------------------------------------
828 // Structure Tests
829 // -------------------------------------------------------------------------
830
831 #[test]
832 fn test_entity_new() {
833 let entity = Entity::new(42, 7);
834 assert_eq!(entity.index(), 42);
835 assert_eq!(entity.generation(), 7);
836 }
837
838 #[test]
839 fn test_entity_placeholder() {
840 let placeholder = Entity::PLACEHOLDER;
841 assert_eq!(placeholder.index(), u32::MAX);
842 assert_eq!(placeholder.generation(), 0);
843 assert!(placeholder.is_placeholder());
844
845 // Non-placeholder entities
846 let entity = Entity::new(0, 0);
847 assert!(!entity.is_placeholder());
848
849 let entity = Entity::new(u32::MAX, 1);
850 assert!(!entity.is_placeholder());
851
852 let entity = Entity::new(0, 1);
853 assert!(!entity.is_placeholder());
854 }
855
856 #[test]
857 fn test_entity_size() {
858 // Entity should be exactly 8 bytes (2 x u32)
859 assert_eq!(std::mem::size_of::<Entity>(), 8);
860 assert_eq!(std::mem::align_of::<Entity>(), 4);
861 }
862
863 #[test]
864 fn test_entity_copy_clone() {
865 let entity1 = Entity::new(10, 5);
866 let entity2 = entity1; // Copy
867 let entity3 = entity1.clone(); // Clone
868
869 assert_eq!(entity1, entity2);
870 assert_eq!(entity1, entity3);
871 }
872
873 #[test]
874 fn test_entity_equality() {
875 let e1 = Entity::new(0, 1);
876 let e2 = Entity::new(0, 1);
877 let e3 = Entity::new(0, 2); // Different generation
878 let e4 = Entity::new(1, 1); // Different index
879
880 assert_eq!(e1, e2);
881 assert_ne!(e1, e3);
882 assert_ne!(e1, e4);
883 }
884
885 // -------------------------------------------------------------------------
886 // Bit Packing Tests
887 // -------------------------------------------------------------------------
888
889 #[test]
890 fn test_entity_to_bits() {
891 let entity = Entity::new(42, 7);
892 let bits = entity.to_bits();
893
894 // Upper 32 bits: generation (7), Lower 32 bits: index (42)
895 assert_eq!(bits, (7_u64 << 32) | 42);
896 }
897
898 #[test]
899 fn test_entity_from_bits() {
900 let bits = (3_u64 << 32) | 100;
901 let entity = Entity::from_bits(bits);
902
903 assert_eq!(entity.index(), 100);
904 assert_eq!(entity.generation(), 3);
905 }
906
907 #[test]
908 fn test_entity_bits_roundtrip() {
909 let original = Entity::new(999, 42);
910 let bits = original.to_bits();
911 let restored = Entity::from_bits(bits);
912
913 assert_eq!(original, restored);
914 }
915
916 #[test]
917 fn test_entity_bits_edge_cases() {
918 // Maximum values
919 let max = Entity::new(u32::MAX, u32::MAX);
920 assert_eq!(max, Entity::from_bits(max.to_bits()));
921
922 // Zero values
923 let zero = Entity::new(0, 0);
924 assert_eq!(zero, Entity::from_bits(zero.to_bits()));
925
926 // Mixed
927 let mixed = Entity::new(u32::MAX, 0);
928 assert_eq!(mixed, Entity::from_bits(mixed.to_bits()));
929 }
930
931 // -------------------------------------------------------------------------
932 // Trait Implementation Tests
933 // -------------------------------------------------------------------------
934
935 #[test]
936 fn test_entity_hash() {
937 let mut set = HashSet::new();
938
939 let e1 = Entity::new(0, 1);
940 let e2 = Entity::new(0, 1);
941 let e3 = Entity::new(0, 2);
942 let e4 = Entity::new(1, 1);
943
944 set.insert(e1);
945
946 // Same entity should be found
947 assert!(set.contains(&e2));
948
949 // Different entities should not be found
950 assert!(!set.contains(&e3));
951 assert!(!set.contains(&e4));
952 }
953
954 #[test]
955 fn test_entity_debug_format() {
956 let entity = Entity::new(42, 3);
957 assert_eq!(format!("{:?}", entity), "Entity(42:3)");
958
959 let placeholder = Entity::PLACEHOLDER;
960 assert_eq!(format!("{:?}", placeholder), "Entity(4294967295:0)");
961 }
962
963 #[test]
964 fn test_entity_display_format() {
965 let entity = Entity::new(100, 7);
966 assert_eq!(format!("{}", entity), "Entity(100:7)");
967 }
968
969 #[test]
970 fn test_entity_default() {
971 let default_entity: Entity = Default::default();
972 assert_eq!(default_entity, Entity::PLACEHOLDER);
973 assert!(default_entity.is_placeholder());
974 }
975
976 // -------------------------------------------------------------------------
977 // Conversion Tests
978 // -------------------------------------------------------------------------
979
980 #[test]
981 fn test_entity_from_into_u64() {
982 let entity = Entity::new(42, 7);
983
984 // Into<u64>
985 let bits: u64 = entity.into();
986 assert_eq!(bits, entity.to_bits());
987
988 // From<u64>
989 let restored: Entity = bits.into();
990 assert_eq!(restored, entity);
991 }
992
993 // -------------------------------------------------------------------------
994 // Thread Safety Tests
995 // -------------------------------------------------------------------------
996
997 #[test]
998 fn test_entity_send_sync() {
999 // Compile-time check that Entity is Send + Sync
1000 fn assert_send_sync<T: Send + Sync>() {}
1001 assert_send_sync::<Entity>();
1002 }
1003
1004 // =========================================================================
1005 // EntityAllocator Tests
1006 // =========================================================================
1007
1008 // -------------------------------------------------------------------------
1009 // Basic Allocator Operations
1010 // -------------------------------------------------------------------------
1011
1012 #[test]
1013 fn test_allocator_new() {
1014 let allocator = EntityAllocator::new();
1015 assert_eq!(allocator.len(), 0);
1016 assert_eq!(allocator.capacity(), 0);
1017 assert!(allocator.is_empty());
1018 }
1019
1020 #[test]
1021 fn test_allocator_with_capacity() {
1022 let allocator = EntityAllocator::with_capacity(100);
1023 assert_eq!(allocator.len(), 0);
1024 assert_eq!(allocator.capacity(), 0); // capacity() returns slots used, not reserved
1025 assert!(allocator.is_empty());
1026 }
1027
1028 #[test]
1029 fn test_allocator_default() {
1030 let allocator: EntityAllocator = Default::default();
1031 assert_eq!(allocator.len(), 0);
1032 assert!(allocator.is_empty());
1033 }
1034
1035 #[test]
1036 fn test_allocator_debug() {
1037 let mut allocator = EntityAllocator::new();
1038 let _e1 = allocator.allocate();
1039 let e2 = allocator.allocate();
1040 allocator.deallocate(e2);
1041
1042 let debug_str = format!("{:?}", allocator);
1043 assert!(debug_str.contains("EntityAllocator"));
1044 assert!(debug_str.contains("len"));
1045 assert!(debug_str.contains("capacity"));
1046 assert!(debug_str.contains("free_slots"));
1047 }
1048
1049 // -------------------------------------------------------------------------
1050 // Allocation Tests
1051 // -------------------------------------------------------------------------
1052
1053 #[test]
1054 fn test_allocator_allocate_basic() {
1055 let mut allocator = EntityAllocator::new();
1056
1057 let e1 = allocator.allocate();
1058 assert_eq!(e1.index(), 0);
1059 assert_eq!(e1.generation(), 1);
1060 assert!(!e1.is_placeholder());
1061
1062 let e2 = allocator.allocate();
1063 assert_eq!(e2.index(), 1);
1064 assert_eq!(e2.generation(), 1);
1065
1066 assert_ne!(e1, e2);
1067 assert_eq!(allocator.len(), 2);
1068 }
1069
1070 #[test]
1071 fn test_allocator_allocate_multiple() {
1072 let mut allocator = EntityAllocator::new();
1073 let mut entities = Vec::new();
1074
1075 for _ in 0..100 {
1076 entities.push(allocator.allocate());
1077 }
1078
1079 assert_eq!(allocator.len(), 100);
1080 assert_eq!(allocator.capacity(), 100);
1081
1082 // All entities should be unique
1083 let unique: HashSet<_> = entities.iter().collect();
1084 assert_eq!(unique.len(), 100);
1085
1086 // All entities should be alive
1087 for entity in &entities {
1088 assert!(allocator.is_alive(*entity));
1089 }
1090 }
1091
1092 #[test]
1093 fn test_allocator_first_generation_is_one() {
1094 let mut allocator = EntityAllocator::new();
1095
1096 // All newly allocated entities should have generation 1
1097 for _ in 0..10 {
1098 let entity = allocator.allocate();
1099 assert_eq!(entity.generation(), 1);
1100 }
1101 }
1102
1103 // -------------------------------------------------------------------------
1104 // Deallocation Tests
1105 // -------------------------------------------------------------------------
1106
1107 #[test]
1108 fn test_allocator_deallocate_basic() {
1109 let mut allocator = EntityAllocator::new();
1110 let entity = allocator.allocate();
1111
1112 assert!(allocator.is_alive(entity));
1113 assert!(allocator.deallocate(entity));
1114 assert!(!allocator.is_alive(entity));
1115 }
1116
1117 #[test]
1118 fn test_allocator_deallocate_returns_false_for_dead_entity() {
1119 let mut allocator = EntityAllocator::new();
1120 let entity = allocator.allocate();
1121
1122 // First deallocation succeeds
1123 assert!(allocator.deallocate(entity));
1124
1125 // Second deallocation fails
1126 assert!(!allocator.deallocate(entity));
1127 }
1128
1129 #[test]
1130 fn test_allocator_deallocate_returns_false_for_placeholder() {
1131 let mut allocator = EntityAllocator::new();
1132
1133 // PLACEHOLDER cannot be deallocated
1134 assert!(!allocator.deallocate(Entity::PLACEHOLDER));
1135 }
1136
1137 #[test]
1138 fn test_allocator_deallocate_returns_false_for_out_of_bounds() {
1139 let mut allocator = EntityAllocator::new();
1140 allocator.allocate(); // Allocate slot 0
1141
1142 // Entity with index 999 is out of bounds
1143 let fake_entity = Entity::new(999, 1);
1144 assert!(!allocator.deallocate(fake_entity));
1145 }
1146
1147 #[test]
1148 fn test_allocator_deallocate_returns_false_for_wrong_generation() {
1149 let mut allocator = EntityAllocator::new();
1150 let entity = allocator.allocate();
1151
1152 // Create a fake entity with same index but different generation
1153 let fake_entity = Entity::new(entity.index(), entity.generation() + 1);
1154 assert!(!allocator.deallocate(fake_entity));
1155
1156 // Original entity is still alive
1157 assert!(allocator.is_alive(entity));
1158 }
1159
1160 // -------------------------------------------------------------------------
1161 // is_alive Tests
1162 // -------------------------------------------------------------------------
1163
1164 #[test]
1165 fn test_allocator_is_alive() {
1166 let mut allocator = EntityAllocator::new();
1167 let entity = allocator.allocate();
1168
1169 assert!(allocator.is_alive(entity));
1170
1171 allocator.deallocate(entity);
1172 assert!(!allocator.is_alive(entity));
1173 }
1174
1175 #[test]
1176 fn test_allocator_is_alive_placeholder() {
1177 let allocator = EntityAllocator::new();
1178 assert!(!allocator.is_alive(Entity::PLACEHOLDER));
1179 }
1180
1181 #[test]
1182 fn test_allocator_is_alive_out_of_bounds() {
1183 let allocator = EntityAllocator::new();
1184 let fake_entity = Entity::new(999, 1);
1185 assert!(!allocator.is_alive(fake_entity));
1186 }
1187
1188 #[test]
1189 fn test_allocator_is_alive_wrong_generation() {
1190 let mut allocator = EntityAllocator::new();
1191 let entity = allocator.allocate();
1192
1193 // Wrong generation
1194 let stale = Entity::new(entity.index(), entity.generation() + 1);
1195 assert!(!allocator.is_alive(stale));
1196 }
1197
1198 // -------------------------------------------------------------------------
1199 // Slot Recycling Tests
1200 // -------------------------------------------------------------------------
1201
1202 #[test]
1203 fn test_allocator_recycling_basic() {
1204 let mut allocator = EntityAllocator::new();
1205
1206 // Allocate and deallocate
1207 let e1 = allocator.allocate();
1208 assert!(allocator.deallocate(e1));
1209
1210 // Allocate again - should reuse the slot
1211 let e2 = allocator.allocate();
1212
1213 // Same index, different generation
1214 assert_eq!(e1.index(), e2.index());
1215 assert_ne!(e1.generation(), e2.generation());
1216 assert_eq!(e2.generation(), 2); // Generation incremented
1217
1218 // e1 is dead, e2 is alive
1219 assert!(!allocator.is_alive(e1));
1220 assert!(allocator.is_alive(e2));
1221 }
1222
1223 #[test]
1224 fn test_allocator_recycling_multiple() {
1225 let mut allocator = EntityAllocator::new();
1226
1227 // Allocate 5 entities
1228 let entities: Vec<_> = (0..5).map(|_| allocator.allocate()).collect();
1229 assert_eq!(allocator.len(), 5);
1230 assert_eq!(allocator.capacity(), 5);
1231
1232 // Deallocate all
1233 for entity in &entities {
1234 assert!(allocator.deallocate(*entity));
1235 }
1236 assert_eq!(allocator.len(), 0);
1237 assert_eq!(allocator.capacity(), 5); // Capacity unchanged
1238
1239 // Allocate 5 more - should reuse slots
1240 let new_entities: Vec<_> = (0..5).map(|_| allocator.allocate()).collect();
1241 assert_eq!(allocator.len(), 5);
1242 assert_eq!(allocator.capacity(), 5); // Still 5, no new slots created
1243
1244 // All new entities should have generation 2
1245 for entity in &new_entities {
1246 assert_eq!(entity.generation(), 2);
1247 }
1248
1249 // Old entities are dead, new ones are alive
1250 for entity in &entities {
1251 assert!(!allocator.is_alive(*entity));
1252 }
1253 for entity in &new_entities {
1254 assert!(allocator.is_alive(*entity));
1255 }
1256 }
1257
1258 #[test]
1259 fn test_allocator_recycling_lifo_order() {
1260 let mut allocator = EntityAllocator::new();
1261
1262 // Allocate 3 entities
1263 let e0 = allocator.allocate();
1264 let e1 = allocator.allocate();
1265 let e2 = allocator.allocate();
1266
1267 // Deallocate in order: e0, e1, e2
1268 allocator.deallocate(e0);
1269 allocator.deallocate(e1);
1270 allocator.deallocate(e2);
1271
1272 // Reallocate - should come back in reverse order (LIFO)
1273 let new_e2 = allocator.allocate(); // Should reuse e2's slot
1274 let new_e1 = allocator.allocate(); // Should reuse e1's slot
1275 let new_e0 = allocator.allocate(); // Should reuse e0's slot
1276
1277 assert_eq!(new_e2.index(), e2.index());
1278 assert_eq!(new_e1.index(), e1.index());
1279 assert_eq!(new_e0.index(), e0.index());
1280 }
1281
1282 #[test]
1283 fn test_allocator_generation_increment() {
1284 let mut allocator = EntityAllocator::new();
1285
1286 // Allocate and deallocate the same slot multiple times
1287 let mut last_gen = 0;
1288 for expected_gen in 1..=10 {
1289 let entity = allocator.allocate();
1290 assert_eq!(entity.index(), 0); // Always slot 0
1291 assert_eq!(entity.generation(), expected_gen);
1292 assert!(entity.generation() > last_gen);
1293 last_gen = entity.generation();
1294
1295 allocator.deallocate(entity);
1296 }
1297 }
1298
1299 // -------------------------------------------------------------------------
1300 // len(), capacity(), is_empty() Tests
1301 // -------------------------------------------------------------------------
1302
1303 #[test]
1304 fn test_allocator_len() {
1305 let mut allocator = EntityAllocator::new();
1306 assert_eq!(allocator.len(), 0);
1307
1308 let e1 = allocator.allocate();
1309 assert_eq!(allocator.len(), 1);
1310
1311 let e2 = allocator.allocate();
1312 assert_eq!(allocator.len(), 2);
1313
1314 allocator.deallocate(e1);
1315 assert_eq!(allocator.len(), 1);
1316
1317 allocator.deallocate(e2);
1318 assert_eq!(allocator.len(), 0);
1319 }
1320
1321 #[test]
1322 fn test_allocator_capacity() {
1323 let mut allocator = EntityAllocator::new();
1324 assert_eq!(allocator.capacity(), 0);
1325
1326 let e1 = allocator.allocate();
1327 assert_eq!(allocator.capacity(), 1);
1328
1329 let e2 = allocator.allocate();
1330 assert_eq!(allocator.capacity(), 2);
1331
1332 // Capacity doesn't decrease on deallocation
1333 allocator.deallocate(e1);
1334 assert_eq!(allocator.capacity(), 2);
1335
1336 allocator.deallocate(e2);
1337 assert_eq!(allocator.capacity(), 2);
1338
1339 // Reusing slots doesn't increase capacity
1340 allocator.allocate();
1341 allocator.allocate();
1342 assert_eq!(allocator.capacity(), 2);
1343
1344 // New allocation beyond capacity increases it
1345 allocator.allocate();
1346 assert_eq!(allocator.capacity(), 3);
1347 }
1348
1349 #[test]
1350 fn test_allocator_is_empty() {
1351 let mut allocator = EntityAllocator::new();
1352 assert!(allocator.is_empty());
1353
1354 let entity = allocator.allocate();
1355 assert!(!allocator.is_empty());
1356
1357 allocator.deallocate(entity);
1358 assert!(allocator.is_empty());
1359 }
1360
1361 // -------------------------------------------------------------------------
1362 // Edge Cases and Stress Tests
1363 // -------------------------------------------------------------------------
1364
1365 #[test]
1366 fn test_allocator_many_allocations() {
1367 let mut allocator = EntityAllocator::new();
1368 const COUNT: usize = 10_000;
1369
1370 // Allocate many entities
1371 let entities: Vec<_> = (0..COUNT).map(|_| allocator.allocate()).collect();
1372 assert_eq!(allocator.len(), COUNT);
1373
1374 // All should be alive
1375 for entity in &entities {
1376 assert!(allocator.is_alive(*entity));
1377 }
1378
1379 // Deallocate half
1380 for entity in entities.iter().take(COUNT / 2) {
1381 allocator.deallocate(*entity);
1382 }
1383 assert_eq!(allocator.len(), COUNT / 2);
1384
1385 // Deallocated ones are dead
1386 for entity in entities.iter().take(COUNT / 2) {
1387 assert!(!allocator.is_alive(*entity));
1388 }
1389
1390 // Remaining are alive
1391 for entity in entities.iter().skip(COUNT / 2) {
1392 assert!(allocator.is_alive(*entity));
1393 }
1394 }
1395
1396 #[test]
1397 fn test_allocator_stress_allocate_deallocate_cycle() {
1398 let mut allocator = EntityAllocator::new();
1399 const CYCLES: usize = 100;
1400 const ENTITIES_PER_CYCLE: usize = 100;
1401
1402 for _ in 0..CYCLES {
1403 // Allocate
1404 let entities: Vec<_> = (0..ENTITIES_PER_CYCLE)
1405 .map(|_| allocator.allocate())
1406 .collect();
1407
1408 // Verify all alive
1409 for entity in &entities {
1410 assert!(allocator.is_alive(*entity));
1411 }
1412
1413 // Deallocate all
1414 for entity in &entities {
1415 assert!(allocator.deallocate(*entity));
1416 }
1417
1418 // Verify all dead
1419 for entity in &entities {
1420 assert!(!allocator.is_alive(*entity));
1421 }
1422 }
1423
1424 // After all cycles, should be empty but have capacity
1425 assert!(allocator.is_empty());
1426 assert_eq!(allocator.capacity(), ENTITIES_PER_CYCLE);
1427 }
1428
1429 #[test]
1430 fn test_allocator_unique_entities() {
1431 let mut allocator = EntityAllocator::new();
1432 let mut seen = HashSet::new();
1433
1434 // Allocate, deallocate, and reallocate many times
1435 for _ in 0..1000 {
1436 let entity = allocator.allocate();
1437
1438 // Each entity should be unique (index + generation combination)
1439 let key = entity.to_bits();
1440 assert!(
1441 seen.insert(key),
1442 "Duplicate entity: {:?} (bits: {})",
1443 entity,
1444 key
1445 );
1446
1447 // 50% chance of deallocating
1448 if seen.len() % 2 == 0 {
1449 allocator.deallocate(entity);
1450 }
1451 }
1452 }
1453
1454 // =========================================================================
1455 // Bulk Operations Tests
1456 // =========================================================================
1457
1458 #[test]
1459 fn test_allocate_batch_empty() {
1460 let mut allocator = EntityAllocator::new();
1461
1462 let entities = allocator.allocate_batch(0);
1463 assert!(entities.is_empty());
1464 assert_eq!(allocator.len(), 0);
1465 assert_eq!(allocator.capacity(), 0);
1466 }
1467
1468 #[test]
1469 fn test_allocate_batch_basic() {
1470 let mut allocator = EntityAllocator::new();
1471
1472 let entities = allocator.allocate_batch(100);
1473 assert_eq!(entities.len(), 100);
1474 assert_eq!(allocator.len(), 100);
1475 assert_eq!(allocator.capacity(), 100);
1476
1477 // All entities should be unique
1478 let unique: HashSet<_> = entities.iter().collect();
1479 assert_eq!(unique.len(), 100);
1480
1481 // All entities should be alive
1482 for entity in &entities {
1483 assert!(allocator.is_alive(*entity));
1484 assert!(!entity.is_placeholder());
1485 }
1486
1487 // All should have generation 1 (first allocation)
1488 for entity in &entities {
1489 assert_eq!(entity.generation(), 1);
1490 }
1491 }
1492
1493 #[test]
1494 fn test_allocate_batch_reuses_free_slots() {
1495 let mut allocator = EntityAllocator::new();
1496
1497 // Allocate 50 entities, then deallocate them all
1498 let first_batch = allocator.allocate_batch(50);
1499 assert_eq!(allocator.len(), 50);
1500
1501 for entity in &first_batch {
1502 allocator.deallocate(*entity);
1503 }
1504 assert_eq!(allocator.len(), 0);
1505 assert_eq!(allocator.capacity(), 50);
1506
1507 // Allocate 50 more - should reuse all freed slots
1508 let second_batch = allocator.allocate_batch(50);
1509 assert_eq!(allocator.len(), 50);
1510 assert_eq!(allocator.capacity(), 50); // No new slots created
1511
1512 // All should have generation 2 (recycled)
1513 for entity in &second_batch {
1514 assert_eq!(entity.generation(), 2);
1515 }
1516
1517 // All original entities should be dead
1518 for entity in &first_batch {
1519 assert!(!allocator.is_alive(*entity));
1520 }
1521
1522 // All new entities should be alive
1523 for entity in &second_batch {
1524 assert!(allocator.is_alive(*entity));
1525 }
1526 }
1527
1528 #[test]
1529 fn test_allocate_batch_mixed_reuse_and_new() {
1530 let mut allocator = EntityAllocator::new();
1531
1532 // Allocate 30, deallocate all
1533 let first = allocator.allocate_batch(30);
1534 for e in &first {
1535 allocator.deallocate(*e);
1536 }
1537 assert_eq!(allocator.capacity(), 30);
1538
1539 // Now allocate 50 - should reuse 30, create 20 new
1540 let second = allocator.allocate_batch(50);
1541 assert_eq!(second.len(), 50);
1542 assert_eq!(allocator.len(), 50);
1543 assert_eq!(allocator.capacity(), 50); // Grew by 20
1544
1545 // Count generations
1546 let gen1_count = second.iter().filter(|e| e.generation() == 1).count();
1547 let gen2_count = second.iter().filter(|e| e.generation() == 2).count();
1548
1549 assert_eq!(gen1_count, 20); // New slots
1550 assert_eq!(gen2_count, 30); // Recycled slots
1551 }
1552
1553 #[test]
1554 fn test_allocate_batch_large() {
1555 let mut allocator = EntityAllocator::new();
1556
1557 let entities = allocator.allocate_batch(10_000);
1558 assert_eq!(entities.len(), 10_000);
1559 assert_eq!(allocator.len(), 10_000);
1560
1561 // All should be unique
1562 let unique: HashSet<_> = entities.iter().collect();
1563 assert_eq!(unique.len(), 10_000);
1564
1565 // All should be alive
1566 for entity in &entities {
1567 assert!(allocator.is_alive(*entity));
1568 }
1569 }
1570
1571 #[test]
1572 fn test_deallocate_batch_empty() {
1573 let mut allocator = EntityAllocator::new();
1574
1575 let deallocated = allocator.deallocate_batch(&[]);
1576 assert_eq!(deallocated, 0);
1577 }
1578
1579 #[test]
1580 fn test_deallocate_batch_basic() {
1581 let mut allocator = EntityAllocator::new();
1582
1583 let entities = allocator.allocate_batch(100);
1584 assert_eq!(allocator.len(), 100);
1585
1586 let deallocated = allocator.deallocate_batch(&entities);
1587 assert_eq!(deallocated, 100);
1588 assert_eq!(allocator.len(), 0);
1589
1590 // All should be dead
1591 for entity in &entities {
1592 assert!(!allocator.is_alive(*entity));
1593 }
1594 }
1595
1596 #[test]
1597 fn test_deallocate_batch_partial_invalid() {
1598 let mut allocator = EntityAllocator::new();
1599
1600 let entities = allocator.allocate_batch(10);
1601
1602 // Deallocate some individually first
1603 allocator.deallocate(entities[0]);
1604 allocator.deallocate(entities[2]);
1605 allocator.deallocate(entities[4]);
1606
1607 // Now batch deallocate all - should only succeed for 7
1608 let deallocated = allocator.deallocate_batch(&entities);
1609 assert_eq!(deallocated, 7); // 10 - 3 already deallocated
1610
1611 // All should be dead now
1612 for entity in &entities {
1613 assert!(!allocator.is_alive(*entity));
1614 }
1615 }
1616
1617 #[test]
1618 fn test_deallocate_batch_all_invalid() {
1619 let mut allocator = EntityAllocator::new();
1620
1621 let entities = allocator.allocate_batch(10);
1622
1623 // Deallocate all individually
1624 for entity in &entities {
1625 allocator.deallocate(*entity);
1626 }
1627
1628 // Batch deallocate should return 0
1629 let deallocated = allocator.deallocate_batch(&entities);
1630 assert_eq!(deallocated, 0);
1631 }
1632
1633 #[test]
1634 fn test_deallocate_batch_with_placeholder() {
1635 let mut allocator = EntityAllocator::new();
1636
1637 let entities = allocator.allocate_batch(5);
1638 let mut with_placeholder: Vec<Entity> = entities.clone();
1639 with_placeholder.push(Entity::PLACEHOLDER);
1640 with_placeholder.push(Entity::PLACEHOLDER);
1641
1642 let deallocated = allocator.deallocate_batch(&with_placeholder);
1643 assert_eq!(deallocated, 5); // Only the 5 valid entities
1644
1645 assert!(allocator.is_empty());
1646 }
1647
1648 #[test]
1649 fn test_deallocate_batch_with_out_of_bounds() {
1650 let mut allocator = EntityAllocator::new();
1651
1652 let entities = allocator.allocate_batch(5);
1653 let mut with_invalid: Vec<Entity> = entities.clone();
1654 with_invalid.push(Entity::new(9999, 1)); // Out of bounds
1655 with_invalid.push(Entity::new(10000, 1)); // Out of bounds
1656
1657 let deallocated = allocator.deallocate_batch(&with_invalid);
1658 assert_eq!(deallocated, 5); // Only the 5 valid entities
1659 }
1660
1661 #[test]
1662 fn test_reserve_basic() {
1663 let mut allocator = EntityAllocator::new();
1664
1665 allocator.reserve(1000);
1666
1667 // No entities allocated, but memory reserved
1668 assert_eq!(allocator.len(), 0);
1669 assert_eq!(allocator.capacity(), 0);
1670
1671 // Now allocate - no reallocation should occur
1672 let entities = allocator.allocate_batch(500);
1673 assert_eq!(entities.len(), 500);
1674 assert_eq!(allocator.len(), 500);
1675 }
1676
1677 #[test]
1678 fn test_reserve_after_allocations() {
1679 let mut allocator = EntityAllocator::new();
1680
1681 // Allocate some entities first
1682 let _first = allocator.allocate_batch(100);
1683 assert_eq!(allocator.capacity(), 100);
1684
1685 // Reserve more
1686 allocator.reserve(1000);
1687
1688 // Allocate many more
1689 let second = allocator.allocate_batch(1000);
1690 assert_eq!(second.len(), 1000);
1691 assert_eq!(allocator.len(), 1100);
1692 }
1693
1694 #[test]
1695 fn test_reserve_zero() {
1696 let mut allocator = EntityAllocator::new();
1697
1698 allocator.reserve(0);
1699 assert_eq!(allocator.len(), 0);
1700 assert_eq!(allocator.capacity(), 0);
1701 }
1702
1703 #[test]
1704 fn test_batch_stress_test() {
1705 let mut allocator = EntityAllocator::new();
1706 const BATCH_SIZE: usize = 1000;
1707 const ITERATIONS: usize = 100;
1708
1709 for _ in 0..ITERATIONS {
1710 // Allocate batch
1711 let entities = allocator.allocate_batch(BATCH_SIZE);
1712 assert_eq!(entities.len(), BATCH_SIZE);
1713
1714 // Verify all alive
1715 for entity in &entities {
1716 assert!(allocator.is_alive(*entity));
1717 }
1718
1719 // Deallocate batch
1720 let deallocated = allocator.deallocate_batch(&entities);
1721 assert_eq!(deallocated, BATCH_SIZE);
1722
1723 // Verify all dead
1724 for entity in &entities {
1725 assert!(!allocator.is_alive(*entity));
1726 }
1727 }
1728
1729 // After all iterations, should be empty
1730 assert!(allocator.is_empty());
1731
1732 // Capacity should be exactly BATCH_SIZE (slots reused each iteration)
1733 assert_eq!(allocator.capacity(), BATCH_SIZE);
1734 }
1735
1736 #[test]
1737 fn test_batch_vs_individual_equivalence() {
1738 // Verify that batch operations produce equivalent results to individual ops
1739
1740 let mut batch_allocator = EntityAllocator::new();
1741 let mut individual_allocator = EntityAllocator::new();
1742
1743 // Allocate same count via batch vs individual
1744 let batch_entities = batch_allocator.allocate_batch(100);
1745 let individual_entities: Vec<_> =
1746 (0..100).map(|_| individual_allocator.allocate()).collect();
1747
1748 assert_eq!(batch_allocator.len(), individual_allocator.len());
1749 assert_eq!(batch_allocator.capacity(), individual_allocator.capacity());
1750
1751 // Same number of unique entities
1752 assert_eq!(batch_entities.len(), individual_entities.len());
1753
1754 // All entities should match in structure (index 0-99, generation 1)
1755 for i in 0..100 {
1756 // Since both allocate sequentially with no free slots, indices match
1757 assert_eq!(batch_entities[i].index() as usize, i);
1758 assert_eq!(individual_entities[i].index() as usize, i);
1759 assert_eq!(batch_entities[i].generation(), 1);
1760 assert_eq!(individual_entities[i].generation(), 1);
1761 }
1762
1763 // Deallocate via batch vs individual
1764 let batch_count = batch_allocator.deallocate_batch(&batch_entities);
1765 let individual_count = individual_entities
1766 .iter()
1767 .filter(|e| individual_allocator.deallocate(**e))
1768 .count();
1769
1770 assert_eq!(batch_count, individual_count);
1771 assert_eq!(batch_allocator.len(), individual_allocator.len());
1772 }
1773}