Skip to main content

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}