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 {
83 self.spawner.spawn_at(bulk, bundle, self.caller);
84 }
85 bulk
86 } else {
87 unsafe { self.spawner.spawn(bundle, self.caller) }
89 })
90 }
91
92 #[inline]
93 fn size_hint(&self) -> (usize, Option<usize>) {
94 self.inner.size_hint()
95 }
96}
97
98impl<I, T> ExactSizeIterator for SpawnBatchIter<'_, I>
99where
100 I: ExactSizeIterator<Item = T>,
101 T: Bundle<Effect: NoBundleEffect>,
102{
103 fn len(&self) -> usize {
104 self.inner.len()
105 }
106}
107
108impl<I, T> FusedIterator for SpawnBatchIter<'_, I>
109where
110 I: FusedIterator<Item = T>,
111 T: Bundle<Effect: NoBundleEffect>,
112{
113}
114
115unsafe impl<I: Iterator, T> EntitySetIterator for SpawnBatchIter<'_, I>
117where
118 I: FusedIterator<Item = T>,
119 T: Bundle<Effect: NoBundleEffect>,
120{
121}
122
123#[cfg(test)]
124mod tests {
125 use bevy_ecs_macros::Component;
126
127 use super::*;
128
129 #[derive(Clone, Copy, Component)]
130 struct ComponentA;
131
132 #[test]
133 fn spawn_batch_does_not_leak_entities() {
134 let mut world = World::new();
135 world.spawn_batch((0u32..50).filter(|&i| i & 1 > 0).map(|_| ComponentA));
136 let total_allocated = world.entity_allocator().inner.total_entity_indices();
137 world.entity_allocator_mut().inner.flush_freed();
138 world.entity_allocator_mut().alloc();
139 let reused = world.entity_allocator().inner.total_entity_indices() == total_allocated;
140 assert!(reused);
141 }
142}