use crate::{
bundle::{Bundle, DynamicBundle, InsertMode, NoBundleEffect},
change_detection::MaybeLocation,
entity::Entity,
relationship::{RelatedSpawner, Relationship, RelationshipHookMode, RelationshipTarget},
world::{EntityWorldMut, World},
};
use alloc::vec::Vec;
use bevy_ptr::{move_as_ptr, MovingPtr};
use core::{
marker::PhantomData,
mem::{self, MaybeUninit},
};
use variadics_please::all_tuples_enumerated;
pub struct Spawn<B: Bundle>(pub B);
pub trait SpawnableList<R>: Sized {
fn spawn(this: MovingPtr<'_, 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(ptr: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
let mapped_bundles = ptr.read().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(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
#[track_caller]
fn spawn<B: Bundle, R: Relationship>(
this: MovingPtr<'_, Spawn<B>>,
world: &mut World,
entity: Entity,
) {
let caller = MaybeLocation::caller();
bevy_ptr::deconstruct_moving_ptr!({
let Spawn { 0: bundle } = this;
});
let r = R::from(entity);
move_as_ptr!(r);
let mut entity = world.spawn_with_caller(r, caller);
entity.insert_with_caller(
bundle,
InsertMode::Replace,
caller,
RelationshipHookMode::Run,
);
}
spawn::<B, R>(this, world, entity);
}
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(mut this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
for bundle in &mut this.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(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
world
.entity_mut(entity)
.with_related_entities(this.read().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(mut this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
let related = (&mut this.0).collect::<Vec<_>>();
world.entity_mut(entity).add_related::<R>(&related);
}
fn size_hint(&self) -> usize {
self.0.size_hint().0
}
}
pub struct WithOneRelated(pub Entity);
impl<R: Relationship> SpawnableList<R> for WithOneRelated {
fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) {
world.entity_mut(entity).add_one_related::<R>(this.read().0);
}
fn size_hint(&self) -> usize {
1
}
}
macro_rules! spawnable_list_impl {
($(#[$meta:meta])* $(($index:tt, $list: ident, $alias: ident)),*) => {
$(#[$meta])*
impl<R: Relationship, $($list: SpawnableList<R>),*> SpawnableList<R> for ($($list,)*) {
#[expect(
clippy::allow_attributes,
reason = "This is a tuple-related macro; as such, the lints below may not always apply."
)]
#[allow(unused_unsafe, reason = "The empty tuple will leave the unsafe blocks unused.")]
fn spawn(_this: MovingPtr<'_, Self>, _world: &mut World, _entity: Entity)
where
Self: Sized,
{
bevy_ptr::deconstruct_moving_ptr!({
let tuple { $($index: $alias),* } = _this;
});
$( SpawnableList::<R>::spawn($alias, _world, _entity); )*
}
fn size_hint(&self) -> usize {
let ($($alias,)*) = self;
0 $(+ $alias.size_hint())*
}
}
}
}
all_tuples_enumerated!(
#[doc(fake_variadic)]
spawnable_list_impl,
0,
12,
P,
field_
);
pub struct SpawnRelatedBundle<R: Relationship, L: SpawnableList<R>> {
list: L,
marker: PhantomData<R>,
}
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;
unsafe fn get_components(
ptr: MovingPtr<'_, Self>,
func: &mut impl FnMut(crate::component::StorageType, bevy_ptr::OwningPtr<'_>),
) {
let target =
<R::RelationshipTarget as RelationshipTarget>::with_capacity(ptr.list.size_hint());
move_as_ptr!(target);
<R::RelationshipTarget as DynamicBundle>::get_components(target, func);
mem::forget(ptr);
}
unsafe fn apply_effect(ptr: MovingPtr<'_, MaybeUninit<Self>>, entity: &mut EntityWorldMut) {
let effect = unsafe { ptr.assume_init() };
let id = entity.id();
entity.world_scope(|world: &mut World| {
bevy_ptr::deconstruct_moving_ptr!({
let Self { list, marker: _ } = effect;
});
L::spawn(list, world, id);
});
}
}
pub struct SpawnOneRelated<R: Relationship, B: Bundle> {
bundle: B,
marker: PhantomData<R>,
}
impl<R: Relationship, B: Bundle> DynamicBundle for SpawnOneRelated<R, B> {
type Effect = Self;
unsafe fn get_components(
ptr: MovingPtr<'_, Self>,
func: &mut impl FnMut(crate::component::StorageType, bevy_ptr::OwningPtr<'_>),
) {
let target = <R::RelationshipTarget as RelationshipTarget>::with_capacity(1);
move_as_ptr!(target);
<R::RelationshipTarget as DynamicBundle>::get_components(target, func);
mem::forget(ptr);
}
unsafe fn apply_effect(ptr: MovingPtr<'_, MaybeUninit<Self>>, entity: &mut EntityWorldMut) {
let effect = unsafe { ptr.assume_init() };
let effect = effect.read();
entity.with_related::<R>(effect.bundle);
}
}
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))
);
}
}