use crate::{
bundle::{Bundle, BundleEffect, DynamicBundle, NoBundleEffect},
entity::Entity,
relationship::{RelatedSpawner, Relationship, RelationshipTarget},
world::{EntityWorldMut, World},
};
use alloc::vec::Vec;
use core::marker::PhantomData;
use variadics_please::all_tuples;
pub struct Spawn<B: Bundle>(pub B);
pub trait SpawnableList<R> {
fn spawn(self, world: &mut World, entity: Entity);
fn size_hint(&self) -> usize;
}
impl<R: Relationship, B: Bundle<Effect: NoBundleEffect>> SpawnableList<R> for Vec<B> {
fn spawn(self, world: &mut World, entity: Entity) {
let mapped_bundles = self.into_iter().map(|b| (R::from(entity), b));
world.spawn_batch(mapped_bundles);
}
fn size_hint(&self) -> usize {
self.len()
}
}
impl<R: Relationship, B: Bundle> SpawnableList<R> for Spawn<B> {
fn spawn(self, world: &mut World, entity: Entity) {
world.spawn((R::from(entity), self.0));
}
fn size_hint(&self) -> usize {
1
}
}
pub struct SpawnIter<I>(pub I);
impl<R: Relationship, I: Iterator<Item = B> + Send + Sync + 'static, B: Bundle> SpawnableList<R>
for SpawnIter<I>
{
fn spawn(self, world: &mut World, entity: Entity) {
for bundle in self.0 {
world.spawn((R::from(entity), bundle));
}
}
fn size_hint(&self) -> usize {
self.0.size_hint().0
}
}
pub struct SpawnWith<F>(pub F);
impl<R: Relationship, F: FnOnce(&mut RelatedSpawner<R>) + Send + Sync + 'static> SpawnableList<R>
for SpawnWith<F>
{
fn spawn(self, world: &mut World, entity: Entity) {
world.entity_mut(entity).with_related_entities(self.0);
}
fn size_hint(&self) -> usize {
1
}
}
pub struct WithRelated<I>(pub I);
impl<I> WithRelated<I> {
pub fn new(iter: impl IntoIterator<IntoIter = I>) -> Self {
Self(iter.into_iter())
}
}
impl<R: Relationship, I: Iterator<Item = Entity>> SpawnableList<R> for WithRelated<I> {
fn spawn(self, world: &mut World, entity: Entity) {
world
.entity_mut(entity)
.add_related::<R>(&self.0.collect::<Vec<_>>());
}
fn size_hint(&self) -> usize {
self.0.size_hint().0
}
}
pub struct WithOneRelated(pub Entity);
impl<R: Relationship> SpawnableList<R> for WithOneRelated {
fn spawn(self, world: &mut World, entity: Entity) {
world.entity_mut(entity).add_one_related::<R>(self.0);
}
fn size_hint(&self) -> usize {
1
}
}
macro_rules! spawnable_list_impl {
($(#[$meta:meta])* $($list: ident),*) => {
#[expect(
clippy::allow_attributes,
reason = "This is a tuple-related macro; as such, the lints below may not always apply."
)]
$(#[$meta])*
impl<R: Relationship, $($list: SpawnableList<R>),*> SpawnableList<R> for ($($list,)*) {
fn spawn(self, _world: &mut World, _entity: Entity) {
#[allow(
non_snake_case,
reason = "The names of these variables are provided by the caller, not by us."
)]
let ($($list,)*) = self;
$($list.spawn(_world, _entity);)*
}
fn size_hint(&self) -> usize {
#[allow(
non_snake_case,
reason = "The names of these variables are provided by the caller, not by us."
)]
let ($($list,)*) = self;
0 $(+ $list.size_hint())*
}
}
}
}
all_tuples!(
#[doc(fake_variadic)]
spawnable_list_impl,
0,
12,
P
);
pub struct SpawnRelatedBundle<R: Relationship, L: SpawnableList<R>> {
list: L,
marker: PhantomData<R>,
}
impl<R: Relationship, L: SpawnableList<R>> BundleEffect for SpawnRelatedBundle<R, L> {
fn apply(self, entity: &mut EntityWorldMut) {
let id = entity.id();
entity.world_scope(|world: &mut World| {
self.list.spawn(world, id);
});
}
}
unsafe impl<R: Relationship, L: SpawnableList<R> + Send + Sync + 'static> Bundle
for SpawnRelatedBundle<R, L>
{
fn component_ids(
components: &mut crate::component::ComponentsRegistrator,
ids: &mut impl FnMut(crate::component::ComponentId),
) {
<R::RelationshipTarget as Bundle>::component_ids(components, ids);
}
fn get_component_ids(
components: &crate::component::Components,
ids: &mut impl FnMut(Option<crate::component::ComponentId>),
) {
<R::RelationshipTarget as Bundle>::get_component_ids(components, ids);
}
}
impl<R: Relationship, L: SpawnableList<R>> DynamicBundle for SpawnRelatedBundle<R, L> {
type Effect = Self;
fn get_components(
self,
func: &mut impl FnMut(crate::component::StorageType, bevy_ptr::OwningPtr<'_>),
) -> Self::Effect {
<R::RelationshipTarget as RelationshipTarget>::with_capacity(self.list.size_hint())
.get_components(func);
self
}
}
pub struct SpawnOneRelated<R: Relationship, B: Bundle> {
bundle: B,
marker: PhantomData<R>,
}
impl<R: Relationship, B: Bundle> BundleEffect for SpawnOneRelated<R, B> {
fn apply(self, entity: &mut EntityWorldMut) {
entity.with_related::<R>(self.bundle);
}
}
impl<R: Relationship, B: Bundle> DynamicBundle for SpawnOneRelated<R, B> {
type Effect = Self;
fn get_components(
self,
func: &mut impl FnMut(crate::component::StorageType, bevy_ptr::OwningPtr<'_>),
) -> Self::Effect {
<R::RelationshipTarget as RelationshipTarget>::with_capacity(1).get_components(func);
self
}
}
unsafe impl<R: Relationship, B: Bundle> Bundle for SpawnOneRelated<R, B> {
fn component_ids(
components: &mut crate::component::ComponentsRegistrator,
ids: &mut impl FnMut(crate::component::ComponentId),
) {
<R::RelationshipTarget as Bundle>::component_ids(components, ids);
}
fn get_component_ids(
components: &crate::component::Components,
ids: &mut impl FnMut(Option<crate::component::ComponentId>),
) {
<R::RelationshipTarget as Bundle>::get_component_ids(components, ids);
}
}
pub trait SpawnRelated: RelationshipTarget {
fn spawn<L: SpawnableList<Self::Relationship>>(
list: L,
) -> SpawnRelatedBundle<Self::Relationship, L>;
fn spawn_one<B: Bundle>(bundle: B) -> SpawnOneRelated<Self::Relationship, B>;
}
impl<T: RelationshipTarget> SpawnRelated for T {
fn spawn<L: SpawnableList<Self::Relationship>>(
list: L,
) -> SpawnRelatedBundle<Self::Relationship, L> {
SpawnRelatedBundle {
list,
marker: PhantomData,
}
}
fn spawn_one<B: Bundle>(bundle: B) -> SpawnOneRelated<Self::Relationship, B> {
SpawnOneRelated {
bundle,
marker: PhantomData,
}
}
}
#[macro_export]
macro_rules! related {
($relationship_target:ty [$($child:expr),*$(,)?]) => {
<$relationship_target>::spawn($crate::recursive_spawn!($($child),*))
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! recursive_spawn {
($a:expr) => {
$crate::spawn::Spawn($a)
};
($a:expr, $b:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
)
};
($a:expr, $b:expr, $c:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
)
};
($a:expr, $b:expr, $c:expr, $d:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
$crate::spawn::Spawn($g),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
$crate::spawn::Spawn($g),
$crate::spawn::Spawn($h),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
$crate::spawn::Spawn($g),
$crate::spawn::Spawn($h),
$crate::spawn::Spawn($i),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
$crate::spawn::Spawn($g),
$crate::spawn::Spawn($h),
$crate::spawn::Spawn($i),
$crate::spawn::Spawn($j),
)
};
($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr, $k:expr) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
$crate::spawn::Spawn($g),
$crate::spawn::Spawn($h),
$crate::spawn::Spawn($i),
$crate::spawn::Spawn($j),
$crate::spawn::Spawn($k),
)
};
(
$a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr,
$g:expr, $h:expr, $i:expr, $j:expr, $k:expr, $($rest:expr),*
) => {
(
$crate::spawn::Spawn($a),
$crate::spawn::Spawn($b),
$crate::spawn::Spawn($c),
$crate::spawn::Spawn($d),
$crate::spawn::Spawn($e),
$crate::spawn::Spawn($f),
$crate::spawn::Spawn($g),
$crate::spawn::Spawn($h),
$crate::spawn::Spawn($i),
$crate::spawn::Spawn($j),
$crate::spawn::Spawn($k),
$crate::recursive_spawn!($($rest),*)
)
};
}
#[cfg(test)]
mod tests {
use crate::{
name::Name,
prelude::{ChildOf, Children, RelationshipTarget},
relationship::RelatedSpawner,
world::World,
};
use super::{Spawn, SpawnIter, SpawnRelated, SpawnWith, WithOneRelated, WithRelated};
#[test]
fn spawn() {
let mut world = World::new();
let parent = world
.spawn((
Name::new("Parent"),
Children::spawn(Spawn(Name::new("Child1"))),
))
.id();
let children = world
.query::<&Children>()
.get(&world, parent)
.expect("An entity with Children should exist");
assert_eq!(children.iter().count(), 1);
for ChildOf(child) in world.query::<&ChildOf>().iter(&world) {
assert_eq!(child, &parent);
}
}
#[test]
fn spawn_iter() {
let mut world = World::new();
let parent = world
.spawn((
Name::new("Parent"),
Children::spawn(SpawnIter(["Child1", "Child2"].into_iter().map(Name::new))),
))
.id();
let children = world
.query::<&Children>()
.get(&world, parent)
.expect("An entity with Children should exist");
assert_eq!(children.iter().count(), 2);
for ChildOf(child) in world.query::<&ChildOf>().iter(&world) {
assert_eq!(child, &parent);
}
}
#[test]
fn spawn_with() {
let mut world = World::new();
let parent = world
.spawn((
Name::new("Parent"),
Children::spawn(SpawnWith(|parent: &mut RelatedSpawner<ChildOf>| {
parent.spawn(Name::new("Child1"));
})),
))
.id();
let children = world
.query::<&Children>()
.get(&world, parent)
.expect("An entity with Children should exist");
assert_eq!(children.iter().count(), 1);
for ChildOf(child) in world.query::<&ChildOf>().iter(&world) {
assert_eq!(child, &parent);
}
}
#[test]
fn with_related() {
let mut world = World::new();
let child1 = world.spawn(Name::new("Child1")).id();
let child2 = world.spawn(Name::new("Child2")).id();
let parent = world
.spawn((
Name::new("Parent"),
Children::spawn(WithRelated::new([child1, child2])),
))
.id();
let children = world
.query::<&Children>()
.get(&world, parent)
.expect("An entity with Children should exist");
assert_eq!(children.iter().count(), 2);
assert_eq!(
world.entity(child1).get::<ChildOf>(),
Some(&ChildOf(parent))
);
assert_eq!(
world.entity(child2).get::<ChildOf>(),
Some(&ChildOf(parent))
);
}
#[test]
fn with_one_related() {
let mut world = World::new();
let child1 = world.spawn(Name::new("Child1")).id();
let parent = world
.spawn((Name::new("Parent"), Children::spawn(WithOneRelated(child1))))
.id();
let children = world
.query::<&Children>()
.get(&world, parent)
.expect("An entity with Children should exist");
assert_eq!(children.iter().count(), 1);
assert_eq!(
world.entity(child1).get::<ChildOf>(),
Some(&ChildOf(parent))
);
}
}