goud_engine/ecs/entity/bulk.rs
1//! Bulk entity allocation and deallocation operations.
2//!
3//! These methods extend [`EntityAllocator`] with batch operations that are
4//! more efficient than repeated single-entity calls when operating on many
5//! entities at once.
6
7use super::allocator::EntityAllocator;
8use super::types::Entity;
9
10impl EntityAllocator {
11 /// Allocates multiple entities at once.
12 ///
13 /// This is more efficient than calling [`allocate`](Self::allocate) in a loop
14 /// because it pre-allocates the result vector and minimizes reallocations.
15 ///
16 /// # Arguments
17 ///
18 /// * `count` - The number of entities to allocate
19 ///
20 /// # Returns
21 ///
22 /// A vector containing `count` newly allocated entities. All entities are
23 /// guaranteed to be valid and unique.
24 ///
25 /// # Panics
26 ///
27 /// Panics if allocating `count` entities would exceed `u32::MAX - 1` total slots.
28 ///
29 /// # Performance
30 ///
31 /// For large batch allocations, this method:
32 /// - Pre-allocates the result vector with exact capacity
33 /// - Reuses free slots first (LIFO order)
34 /// - Bulk-extends the generations vector for remaining slots
35 ///
36 /// # Example
37 ///
38 /// ```
39 /// use goud_engine::ecs::entity::EntityAllocator;
40 ///
41 /// let mut allocator = EntityAllocator::new();
42 ///
43 /// // Allocate 1000 entities in one call
44 /// let entities = allocator.allocate_batch(1000);
45 /// assert_eq!(entities.len(), 1000);
46 /// assert_eq!(allocator.len(), 1000);
47 ///
48 /// // All entities are unique and alive
49 /// for entity in &entities {
50 /// assert!(allocator.is_alive(*entity));
51 /// }
52 /// ```
53 pub fn allocate_batch(&mut self, count: usize) -> Vec<Entity> {
54 if count == 0 {
55 return Vec::new();
56 }
57
58 let mut entities = Vec::with_capacity(count);
59
60 // First, use up free slots
61 let from_free_list = count.min(self.free_list.len());
62 for _ in 0..from_free_list {
63 if let Some(index) = self.free_list.pop() {
64 let generation = self.generations[index as usize];
65 entities.push(Entity::new(index, generation));
66 }
67 }
68
69 // Then, allocate new slots for remaining count
70 let remaining = count - from_free_list;
71 if remaining > 0 {
72 let start_index = self.generations.len();
73 let end_index = start_index + remaining;
74
75 // Ensure we don't exceed maximum capacity
76 assert!(
77 end_index <= u32::MAX as usize,
78 "EntityAllocator would exceed maximum capacity"
79 );
80
81 // Bulk-extend generations vector with initial generation of 1
82 self.generations.resize(end_index, 1);
83
84 // Create entities for new slots
85 for index in start_index..end_index {
86 entities.push(Entity::new(index as u32, 1));
87 }
88 }
89
90 entities
91 }
92
93 /// Deallocates multiple entities at once.
94 ///
95 /// This method attempts to deallocate each entity in the slice. Invalid
96 /// entities (already deallocated, wrong generation, out of bounds, or
97 /// PLACEHOLDER) are skipped without error.
98 ///
99 /// # Arguments
100 ///
101 /// * `entities` - A slice of entities to deallocate
102 ///
103 /// # Returns
104 ///
105 /// The number of entities that were successfully deallocated.
106 ///
107 /// # Example
108 ///
109 /// ```
110 /// use goud_engine::ecs::entity::EntityAllocator;
111 ///
112 /// let mut allocator = EntityAllocator::new();
113 /// let entities = allocator.allocate_batch(100);
114 /// assert_eq!(allocator.len(), 100);
115 ///
116 /// // Deallocate all at once
117 /// let deallocated = allocator.deallocate_batch(&entities);
118 /// assert_eq!(deallocated, 100);
119 /// assert_eq!(allocator.len(), 0);
120 ///
121 /// // Second deallocation returns 0 (already dead)
122 /// let deallocated_again = allocator.deallocate_batch(&entities);
123 /// assert_eq!(deallocated_again, 0);
124 /// ```
125 pub fn deallocate_batch(&mut self, entities: &[Entity]) -> usize {
126 let mut success_count = 0;
127
128 for entity in entities {
129 if self.deallocate(*entity) {
130 success_count += 1;
131 }
132 }
133
134 success_count
135 }
136}