bevy_ecs/world/
spawn_batch.rs1use bevy_ptr::move_as_ptr;
2
3use crate::{
4 bundle::{Bundle, BundleSpawner, NoBundleEffect},
5 change_detection::MaybeLocation,
6 entity::{AllocEntitiesIterator, Entity, EntitySetIterator},
7 world::World,
8};
9use core::iter::FusedIterator;
10
11pub struct SpawnBatchIter<'w, I>
16where
17 I: Iterator,
18 I::Item: Bundle<Effect: NoBundleEffect>,
19{
20 inner: I,
21 spawner: BundleSpawner<'w>,
22 allocator: AllocEntitiesIterator<'w>,
23 caller: MaybeLocation,
24}
25
26impl<'w, I> SpawnBatchIter<'w, I>
27where
28 I: Iterator,
29 I::Item: Bundle<Effect: NoBundleEffect>,
30{
31 #[inline]
32 #[track_caller]
33 pub(crate) fn new(world: &'w mut World, iter: I, caller: MaybeLocation) -> Self {
34 let change_tick = world.change_tick();
35
36 let (lower, upper) = iter.size_hint();
37 let length = upper.unwrap_or(lower);
38
39 let mut spawner = BundleSpawner::new::<I::Item>(world, change_tick);
40 spawner.reserve_storage(length);
41 let allocator = spawner.allocator().alloc_many(length as u32);
42
43 Self {
44 inner: iter,
45 allocator,
46 spawner,
47 caller,
48 }
49 }
50}
51
52impl<I> Drop for SpawnBatchIter<'_, I>
53where
54 I: Iterator,
55 I::Item: Bundle<Effect: NoBundleEffect>,
56{
57 fn drop(&mut self) {
58 for _ in &mut *self {}
60 for e in self.allocator.by_ref() {
62 self.spawner.allocator().free(e);
63 }
64 unsafe { self.spawner.flush_commands() };
67 }
68}
69
70impl<I> Iterator for SpawnBatchIter<'_, I>
71where
72 I: Iterator,
73 I::Item: Bundle<Effect: NoBundleEffect>,
74{
75 type Item = Entity;
76
77 fn next(&mut self) -> Option<Entity> {
78 let bundle = self.inner.next()?;
79 move_as_ptr!(bundle);
80 Some(if let Some(bulk) = self.allocator.next() {
81 unsafe {
85 self.spawner.spawn_at(bulk, bundle, self.caller);
86 }
87 bulk
88 } else {
89 unsafe { self.spawner.spawn(bundle, self.caller) }
93 })
94 }
95
96 #[inline]
97 fn size_hint(&self) -> (usize, Option<usize>) {
98 self.inner.size_hint()
99 }
100}
101
102impl<I, T> ExactSizeIterator for SpawnBatchIter<'_, I>
103where
104 I: ExactSizeIterator<Item = T>,
105 T: Bundle<Effect: NoBundleEffect>,
106{
107 fn len(&self) -> usize {
108 self.inner.len()
109 }
110}
111
112impl<I, T> FusedIterator for SpawnBatchIter<'_, I>
113where
114 I: FusedIterator<Item = T>,
115 T: Bundle<Effect: NoBundleEffect>,
116{
117}
118
119unsafe impl<I: Iterator, T> EntitySetIterator for SpawnBatchIter<'_, I>
121where
122 I: FusedIterator<Item = T>,
123 T: Bundle<Effect: NoBundleEffect>,
124{
125}
126
127#[cfg(test)]
128mod tests {
129 use bevy_ecs_macros::Component;
130
131 use super::*;
132
133 #[derive(Clone, Copy, Component)]
134 struct ComponentA;
135
136 #[test]
137 fn spawn_batch_does_not_leak_entities() {
138 let mut world = World::new();
139 world.spawn_batch((0u32..50).filter(|&i| i & 1 > 0).map(|_| ComponentA));
140 let total_allocated = world.entity_allocator().inner.total_entity_indices();
141 world.entity_allocator_mut().inner.flush_freed();
142 world.entity_allocator_mut().alloc();
143 let reused = world.entity_allocator().inner.total_entity_indices() == total_allocated;
144 assert!(reused);
145 }
146}