diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index 85f2f4cea01ba..44dbb2b88ea35 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -14,7 +14,7 @@ use core::{ marker::PhantomData, mem::{self, MaybeUninit}, }; -use variadics_please::all_tuples_enumerated; +use variadics_please::{all_tuples_enumerated, all_tuples_with_size}; /// A wrapper over a [`Bundle`] indicating that an entity should be spawned with that [`Bundle`]. /// This is intended to be used for hierarchical spawning via traits like [`SpawnableList`] and [`SpawnRelated`]. @@ -132,6 +132,68 @@ impl + Send + Sync + 'static, B: Bundle> } } +/// A [`SpawnableList`] that combines tuples of [`SpawnableList`] allowing for inserting multiple +/// entities per iteration. +/// +/// ``` +/// # use bevy_ecs::hierarchy::Children; +/// # use bevy_ecs::spawn::{Spawn, FlatSpawnVec, SpawnRelated}; +/// # use bevy_ecs::name::Name; +/// # use bevy_ecs::world::World; +/// let mut world = World::new(); +/// world.spawn(( +/// Name::new("Root"), +/// Children::spawn(( +/// FlatSpawnVec([1, 2].into_iter().map(|i| ( +/// Spawn(Name::new(format!("{i}.1"))), +/// Spawn(Name::new(format!("{i}.2"))), +/// )).collect()) +/// )), +/// )); +/// ``` +pub struct FlatSpawnVec(pub Vec); + +macro_rules! flat_spawn_vec_impl { + ($N:expr, $(#[$meta:meta])* $(($list:ident, $alias:ident)),*) => { + $(#[$meta])* + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + impl),*> SpawnableList + for FlatSpawnVec<($($list,)*)> + { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + fn spawn(_this: MovingPtr<'_, Self>, _world: &mut World, _entity: Entity) { + for ($($alias,)*) in _this.read().0 {$( + move_as_ptr!($alias); + SpawnableList::::spawn($alias, _world, _entity); + )*} + } + + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + fn size_hint(&self) -> usize { + self.0.len() * $N + } + } + }; +} + +all_tuples_with_size!( + #[doc(fake_variadic)] + flat_spawn_vec_impl, + 0, + 12, + P, + field_ +); + /// A [`SpawnableList`] that spawns entities using a [`FnOnce`] with a [`RelatedSpawner`] as an argument: /// /// ``` @@ -643,7 +705,9 @@ mod tests { world::World, }; - use super::{Spawn, SpawnIter, SpawnRelated, SpawnWith, WithOneRelated, WithRelated}; + use super::{ + FlatSpawnVec, Spawn, SpawnIter, SpawnRelated, SpawnWith, WithOneRelated, WithRelated, + }; #[test] fn spawn() { @@ -691,6 +755,39 @@ mod tests { } } + #[test] + fn flat_spawn_vec() { + let mut world = World::new(); + + let parent = world + .spawn(( + Name::new("Parent"), + Children::spawn(FlatSpawnVec( + [1, 2] + .into_iter() + .map(|i| { + ( + Spawn(Name::new(std::format!("Child {i}.1"))), + Spawn(Name::new(std::format!("Child {i}.2"))), + ) + }) + .collect(), + )), + )) + .id(); + + let children = world + .query::<&Children>() + .get(&world, parent) + .expect("An entity with Children should exist"); + + assert_eq!(children.iter().count(), 4); + + for ChildOf(child) in world.query::<&ChildOf>().iter(&world) { + assert_eq!(child, &parent); + } + } + #[test] fn spawn_with() { let mut world = World::new();