From 2e267bba5a3392e3817c1f3a755ca31f4c36fd0d Mon Sep 17 00:00:00 2001 From: eugineerd <70062110+eugineerd@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:38:10 +0300 Subject: [PATCH] Entity cloning (#16132) ## Objective Fixes #1515 This PR implements a flexible entity cloning system. The primary use case for it is to clone dynamically-generated entities. Example: ```rs #[derive(Component, Clone)] pub struct Projectile; #[derive(Component, Clone)] pub struct Damage { value: f32, } fn player_input( mut commands: Commands, projectiles: Query>, input: Res>, ) { // Fire a projectile if input.just_pressed(KeyCode::KeyF) { commands.spawn((Projectile, Damage { value: 10.0 })); } // Triplicate all active projectiles if input.just_pressed(KeyCode::KeyT) { for projectile in projectiles.iter() { // To triplicate a projectile we need to create 2 more clones for _ in 0..2{ commands.clone_entity(projectile) } } } } ``` ## Solution ### Commands Add a `clone_entity` command to create a clone of an entity with all components that can be cloned. Components that can't be cloned will be ignored. ```rs commands.clone_entity(entity) ``` If there is a need to configure the cloning process (like set to clone recursively), there is a second command: ```rs commands.clone_entity_with(entity, |builder| { builder.recursive(true) }); ``` Both of these commands return `EntityCommands` of the cloned entity, so the copy can be modified afterwards. ### Builder All these commands use `EntityCloneBuilder` internally. If there is a need to clone an entity using `World` instead, it is also possible: ```rs let entity = world.spawn(Component).id(); let entity_clone = world.spawn_empty().id(); EntityCloneBuilder::new(&mut world).clone_entity(entity, entity_clone); ``` Builder has methods to `allow` or `deny` certain components during cloning if required and can be extended by implementing traits on it. This PR includes two `EntityCloneBuilder` extensions: `CloneEntityWithObserversExt` to configure adding cloned entity to observers of the original entity, and `CloneEntityRecursiveExt` to configure cloning an entity recursively. ### Clone implementations By default, all components that implement either `Clone` or `Reflect` will be cloned (with `Clone`-based implementation preferred in case component implements both). This can be overriden on a per-component basis: ```rs impl Component for SomeComponent { const STORAGE_TYPE: StorageType = StorageType::Table; fn get_component_clone_handler() -> ComponentCloneHandler { // Don't clone this component ComponentCloneHandler::Ignore } } ``` ### `ComponentCloneHandlers` Clone implementation specified in `get_component_clone_handler` will get registered in `ComponentCloneHandlers` (stored in `bevy_ecs::component::Components`) at component registration time. The clone handler implementation provided by a component can be overriden after registration like so: ```rs let component_id = world.components().component_id::().unwrap() world.get_component_clone_handlers_mut() .set_component_handler(component_id, ComponentCloneHandler::Custom(component_clone_custom)) ``` The default clone handler for all components that do not explicitly define one (or don't derive `Component`) is `component_clone_via_reflect` if `bevy_reflect` feature is enabled, and `component_clone_ignore` (noop) otherwise. Default handler can be overriden using `ComponentCloneHandlers::set_default_handler` ### Handlers Component clone handlers can be used to modify component cloning behavior. The general signature for a handler that can be used in `ComponentCloneHandler::Custom` is as follows: ```rs pub fn component_clone_custom( world: &mut DeferredWorld, entity_cloner: &EntityCloner, ) { // implementation } ``` The `EntityCloner` implementation (used internally by `EntityCloneBuilder`) assumes that after calling this custom handler, the `target` entity has the desired version of the component from the `source` entity. ### Builder handler overrides Besides component-defined and world-overriden handlers, `EntityCloneBuilder` also has a way to override handlers locally. It is mainly used to allow configuration methods like `recursive` and `add_observers`. ```rs // From observer clone handler implementation impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> { fn add_observers(&mut self, add_observers: bool) -> &mut Self { if add_observers { self.override_component_clone_handler::(ComponentCloneHandler::Custom( component_clone_observed_by, )) } else { self.remove_component_clone_handler_override::() } } } ``` ## Testing Includes some basic functionality tests and doctests. Performance-wise this feature is the same as calling `clone` followed by `insert` for every entity component. There is also some inherent overhead due to every component clone handler having to access component data through `World`, but this can be reduced without breaking current public API in a later PR. --- crates/bevy_ecs/macros/src/component.rs | 6 + crates/bevy_ecs/src/component.rs | 201 ++++++- crates/bevy_ecs/src/entity/clone_entities.rs | 522 ++++++++++++++++++ crates/bevy_ecs/src/entity/mod.rs | 2 + crates/bevy_ecs/src/lib.rs | 2 +- .../bevy_ecs/src/observer/entity_observer.rs | 111 +++- crates/bevy_ecs/src/observer/mod.rs | 1 + crates/bevy_ecs/src/system/commands/mod.rs | 65 ++- crates/bevy_ecs/src/world/mod.rs | 34 +- .../bevy_hierarchy/src/components/children.rs | 12 +- .../bevy_hierarchy/src/components/parent.rs | 12 +- crates/bevy_hierarchy/src/hierarchy.rs | 157 +++++- 12 files changed, 1111 insertions(+), 14 deletions(-) create mode 100644 crates/bevy_ecs/src/entity/clone_entities.rs diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 7797011019..cb53dfe0a7 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -163,6 +163,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #on_replace #on_remove } + + fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { + use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; + (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()) + .get_component_clone_handler() + } } }) } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index cdf93adb6d..acffd5b3d1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -5,7 +5,7 @@ use crate::{ archetype::ArchetypeFlags, bundle::BundleInfo, change_detection::MAX_CHANGE_AGE, - entity::Entity, + entity::{Entity, EntityCloner}, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, system::{Local, Resource, SystemParam}, @@ -390,6 +390,13 @@ pub trait Component: Send + Sync + 'static { _inheritance_depth: u16, ) { } + + /// Called when registering this component, allowing to override clone function (or disable cloning altogether) for this component. + /// + /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. + fn get_component_clone_handler() -> ComponentCloneHandler { + ComponentCloneHandler::default() + } } /// The storage used for a specific component type. @@ -875,12 +882,96 @@ impl ComponentDescriptor { } } +/// Function type that can be used to clone an entity. +pub type ComponentCloneFn = fn(&mut DeferredWorld, &EntityCloner); + +/// An enum instructing how to clone a component. +#[derive(Debug, Default)] +pub enum ComponentCloneHandler { + #[default] + /// Use the global default function to clone the component with this handler. + Default, + /// Do not clone the component. When a command to clone an entity is issued, component with this handler will be skipped. + Ignore, + /// Set a custom handler for the component. + Custom(ComponentCloneFn), +} + +/// A registry of component clone handlers. Allows to set global default and per-component clone function for all components in the world. +#[derive(Debug)] +pub struct ComponentCloneHandlers { + handlers: Vec>, + default_handler: ComponentCloneFn, +} + +impl ComponentCloneHandlers { + /// Sets the default handler for this registry. All components with [`Default`](ComponentCloneHandler::Default) handler, as well as any component that does not have an + /// explicitly registered clone function will use this handler. + /// + /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. + pub fn set_default_handler(&mut self, handler: ComponentCloneFn) { + self.default_handler = handler; + } + + /// Returns the currently registered default handler. + pub fn get_default_handler(&self) -> ComponentCloneFn { + self.default_handler + } + + /// Sets a handler for a specific component. + /// + /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. + pub fn set_component_handler(&mut self, id: ComponentId, handler: ComponentCloneHandler) { + if id.0 >= self.handlers.len() { + self.handlers.resize(id.0 + 1, None); + } + match handler { + ComponentCloneHandler::Default => self.handlers[id.0] = None, + ComponentCloneHandler::Ignore => self.handlers[id.0] = Some(component_clone_ignore), + ComponentCloneHandler::Custom(handler) => self.handlers[id.0] = Some(handler), + }; + } + + /// Checks if the specified component is registered. If not, the component will use the default global handler. + /// + /// This will return an incorrect result if `id` did not come from the same world as `self`. + pub fn is_handler_registered(&self, id: ComponentId) -> bool { + self.handlers.get(id.0).is_some_and(Option::is_some) + } + + /// Gets a handler to clone a component. This can be one of the following: + /// - Custom clone function for this specific component. + /// - Default global handler. + /// - A [`component_clone_ignore`] (no cloning). + /// + /// This will return an incorrect result if `id` did not come from the same world as `self`. + pub fn get_handler(&self, id: ComponentId) -> ComponentCloneFn { + match self.handlers.get(id.0) { + Some(Some(handler)) => *handler, + Some(None) | None => self.default_handler, + } + } +} + +impl Default for ComponentCloneHandlers { + fn default() -> Self { + Self { + handlers: Default::default(), + #[cfg(feature = "bevy_reflect")] + default_handler: component_clone_via_reflect, + #[cfg(not(feature = "bevy_reflect"))] + default_handler: component_clone_ignore, + } + } +} + /// Stores metadata associated with each kind of [`Component`] in a given [`World`]. #[derive(Debug, Default)] pub struct Components { components: Vec, indices: TypeIdMap, resource_indices: TypeIdMap, + component_clone_handlers: ComponentCloneHandlers, } impl Components { @@ -918,6 +1009,9 @@ impl Components { let info = &mut self.components[id.index()]; T::register_component_hooks(&mut info.hooks); info.required_components = required_components; + let clone_handler = T::get_component_clone_handler(); + self.component_clone_handlers + .set_component_handler(id, clone_handler); } id } @@ -1276,6 +1370,16 @@ impl Components { .map(|info| &mut info.required_by) } + /// Retrieves the [`ComponentCloneHandlers`]. Can be used to get clone functions for components. + pub fn get_component_clone_handlers(&self) -> &ComponentCloneHandlers { + &self.component_clone_handlers + } + + /// Retrieves a mutable reference to the [`ComponentCloneHandlers`]. Can be used to set and update clone functions for components. + pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers { + &mut self.component_clone_handlers + } + /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { @@ -1853,3 +1957,98 @@ impl RequiredComponents { } } } + +/// Component [clone handler function](ComponentCloneFn) implemented using the [`Clone`] trait. +/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for the specific component it is implemented for. +/// It will panic if set as handler for any other component. +/// +/// See [`ComponentCloneHandlers`] for more details. +pub fn component_clone_via_clone( + world: &mut DeferredWorld, + entity_cloner: &EntityCloner, +) { + let component = world + .entity(entity_cloner.source()) + .get::() + .expect("Component must exists on source entity") + .clone(); + world + .commands() + .entity(entity_cloner.target()) + .insert(component); +} + +/// Component [clone handler function](ComponentCloneFn) implemented using reflect. +/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for any registered component, +/// but only reflected components will be cloned. +/// +/// See [`ComponentCloneHandlers`] for more details. +#[cfg(feature = "bevy_reflect")] +pub fn component_clone_via_reflect(world: &mut DeferredWorld, entity_cloner: &EntityCloner) { + let component_id = entity_cloner.component_id(); + let source = entity_cloner.source(); + let target = entity_cloner.target(); + world.commands().queue(move |world: &mut World| { + world.resource_scope::(|world, registry| { + let registry = registry.read(); + + let component_info = world + .components() + .get_info(component_id) + .expect("Component must be registered"); + let Some(type_id) = component_info.type_id() else { + return; + }; + let Some(reflect_component) = + registry.get_type_data::(type_id) + else { + return; + }; + let source_component = reflect_component + .reflect(world.get_entity(source).expect("Source entity must exist")) + .expect("Source entity must have reflected component") + .clone_value(); + let mut target = world + .get_entity_mut(target) + .expect("Target entity must exist"); + reflect_component.apply_or_insert(&mut target, &*source_component, ®istry); + }); + }); +} + +/// Noop implementation of component clone handler function. +/// +/// See [`ComponentCloneHandlers`] for more details. +pub fn component_clone_ignore(_world: &mut DeferredWorld, _entity_cloner: &EntityCloner) {} + +/// Wrapper for components clone specialization using autoderef. +#[doc(hidden)] +pub struct ComponentCloneSpecializationWrapper(PhantomData); + +impl Default for ComponentCloneSpecializationWrapper { + fn default() -> Self { + Self(PhantomData) + } +} + +/// Base trait for components clone specialization using autoderef. +#[doc(hidden)] +pub trait ComponentCloneBase { + fn get_component_clone_handler(&self) -> ComponentCloneHandler; +} +impl ComponentCloneBase for ComponentCloneSpecializationWrapper { + fn get_component_clone_handler(&self) -> ComponentCloneHandler { + ComponentCloneHandler::default() + } +} + +/// Specialized trait for components clone specialization using autoderef. +#[doc(hidden)] +pub trait ComponentCloneViaClone { + fn get_component_clone_handler(&self) -> ComponentCloneHandler; +} +impl ComponentCloneViaClone for &ComponentCloneSpecializationWrapper { + fn get_component_clone_handler(&self) -> ComponentCloneHandler { + ComponentCloneHandler::Custom(component_clone_via_clone::) + } +} diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs new file mode 100644 index 0000000000..710b96e5a3 --- /dev/null +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -0,0 +1,522 @@ +use alloc::sync::Arc; +use core::any::TypeId; + +use bevy_utils::{HashMap, HashSet}; + +use crate::{ + bundle::Bundle, + component::{component_clone_ignore, Component, ComponentCloneHandler, ComponentId}, + entity::Entity, + world::World, +}; + +/// A helper struct to clone an entity. Used internally by [`EntityCloneBuilder::clone_entity`] and custom clone handlers. +pub struct EntityCloner { + source: Entity, + target: Entity, + component_id: Option, + filter_allows_components: bool, + filter: Arc>, + clone_handlers_overrides: Arc>, +} + +impl EntityCloner { + /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. + pub fn clone_entity(&mut self, world: &mut World) { + let source_entity = world + .get_entity(self.source) + .expect("Source entity must exist"); + let archetype = source_entity.archetype(); + + let mut components = Vec::with_capacity(archetype.component_count()); + components.extend( + archetype + .components() + .filter(|id| self.is_cloning_allowed(id)), + ); + + for component in components { + let global_handlers = world.components().get_component_clone_handlers(); + let handler = match self.clone_handlers_overrides.get(&component) { + None => global_handlers.get_handler(component), + Some(ComponentCloneHandler::Default) => global_handlers.get_default_handler(), + Some(ComponentCloneHandler::Ignore) => component_clone_ignore, + Some(ComponentCloneHandler::Custom(handler)) => *handler, + }; + self.component_id = Some(component); + (handler)(&mut world.into(), self); + } + } + + fn is_cloning_allowed(&self, component: &ComponentId) -> bool { + (self.filter_allows_components && self.filter.contains(component)) + || (!self.filter_allows_components && !self.filter.contains(component)) + } + + /// Returns the current source entity. + pub fn source(&self) -> Entity { + self.source + } + + /// Returns the current target entity. + pub fn target(&self) -> Entity { + self.target + } + + /// Returns the [`ComponentId`] of currently cloned component. + pub fn component_id(&self) -> ComponentId { + self.component_id + .expect("ComponentId must be set in clone_entity") + } + + /// Reuse existing [`EntityCloner`] configuration with new source and target. + pub fn with_source_and_target(&self, source: Entity, target: Entity) -> EntityCloner { + EntityCloner { + source, + target, + filter: self.filter.clone(), + clone_handlers_overrides: self.clone_handlers_overrides.clone(), + ..*self + } + } +} + +/// Builder struct to clone an entity. Allows configuring which components to clone, as well as how to clone them. +/// After configuration is complete an entity can be cloned using [`Self::clone_entity`]. +/// +///``` +/// use bevy_ecs::prelude::*; +/// use bevy_ecs::entity::EntityCloneBuilder; +/// +/// #[derive(Component, Clone, PartialEq, Eq)] +/// struct A { +/// field: usize, +/// } +/// +/// let mut world = World::default(); +/// +/// let component = A { field: 5 }; +/// +/// let entity = world.spawn(component.clone()).id(); +/// let entity_clone = world.spawn_empty().id(); +/// +/// EntityCloneBuilder::new(&mut world).clone_entity(entity, entity_clone); +/// +/// assert!(world.get::(entity_clone).is_some_and(|c| *c == component)); +///``` +/// +/// # Default cloning strategy +/// By default, all types that derive [`Component`] and implement either [`Clone`] or `Reflect` (with `ReflectComponent`) will be cloned +/// (with `Clone`-based implementation preferred in case component implements both). +/// +/// It should be noted that if `Component` is implemented manually or if `Clone` implementation is conditional +/// (like when deriving `Clone` for a type with a generic parameter without `Clone` bound), +/// the component will be cloned using the [default cloning strategy](crate::component::ComponentCloneHandlers::get_default_handler). +/// To use `Clone`-based handler ([`component_clone_via_clone`](crate::component::component_clone_via_clone)) in this case it should be set manually using one +/// of the methods mentioned in the [Handlers](#handlers) section +/// +/// Here's an example of how to do it using [`get_component_clone_handler`](Component::get_component_clone_handler): +/// ``` +/// # use bevy_ecs::prelude::*; +/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler}; +/// #[derive(Clone)] +/// struct SomeComponent; +/// +/// impl Component for SomeComponent { +/// const STORAGE_TYPE: StorageType = StorageType::Table; +/// fn get_component_clone_handler() -> ComponentCloneHandler { +/// ComponentCloneHandler::Custom(component_clone_via_clone::) +/// } +/// } +/// ``` +/// +/// # Handlers +/// `EntityCloneBuilder` clones entities by cloning components using [`handlers`](ComponentCloneHandler), and there are multiple layers +/// to decide which handler to use for which component. The overall hierarchy looks like this (priority from most to least): +/// 1. local overrides using [`override_component_clone_handler`](Self::override_component_clone_handler) +/// 2. global overrides using [`set_component_handler`](crate::component::ComponentCloneHandlers::set_component_handler) +/// 3. component-defined handler using [`get_component_clone_handler`](Component::get_component_clone_handler) +/// 4. default handler override using [`set_default_handler`](crate::component::ComponentCloneHandlers::set_default_handler) +/// 5. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not. +#[derive(Debug)] +pub struct EntityCloneBuilder<'w> { + world: &'w mut World, + filter_allows_components: bool, + filter: HashSet, + clone_handlers_overrides: HashMap, +} + +impl<'w> EntityCloneBuilder<'w> { + /// Creates a new [`EntityCloneBuilder`] for world. + pub fn new(world: &'w mut World) -> Self { + Self { + world, + filter_allows_components: false, + filter: Default::default(), + clone_handlers_overrides: Default::default(), + } + } + + /// Finishes configuring the builder and clones `source` entity to `target`. + pub fn clone_entity(self, source: Entity, target: Entity) { + let EntityCloneBuilder { + world, + filter_allows_components, + filter, + clone_handlers_overrides, + .. + } = self; + + EntityCloner { + source, + target, + component_id: None, + filter_allows_components, + filter: Arc::new(filter), + clone_handlers_overrides: Arc::new(clone_handlers_overrides), + } + .clone_entity(world); + + world.flush_commands(); + } + + /// Adds all components of the bundle to the list of components to clone. + /// + /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call + /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. + pub fn allow(&mut self) -> &mut Self { + if self.filter_allows_components { + T::get_component_ids(self.world.components(), &mut |id| { + if let Some(id) = id { + self.filter.insert(id); + } + }); + } else { + T::get_component_ids(self.world.components(), &mut |id| { + if let Some(id) = id { + self.filter.remove(&id); + } + }); + } + self + } + + /// Extends the list of components to clone. + /// + /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call + /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. + pub fn allow_by_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + if self.filter_allows_components { + self.filter.extend(ids); + } else { + ids.into_iter().for_each(|id| { + self.filter.remove(&id); + }); + } + self + } + + /// Extends the list of components to clone using [`TypeId`]s. + /// + /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call + /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. + pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + let ids = ids + .into_iter() + .filter_map(|id| self.world.components().get_id(id)); + if self.filter_allows_components { + self.filter.extend(ids); + } else { + ids.into_iter().for_each(|id| { + self.filter.remove(&id); + }); + } + self + } + + /// Resets the filter to allow all components to be cloned. + pub fn allow_all(&mut self) -> &mut Self { + self.filter_allows_components = false; + self.filter.clear(); + self + } + + /// Disallows all components of the bundle from being cloned. + pub fn deny(&mut self) -> &mut Self { + if self.filter_allows_components { + T::get_component_ids(self.world.components(), &mut |id| { + if let Some(id) = id { + self.filter.remove(&id); + } + }); + } else { + T::get_component_ids(self.world.components(), &mut |id| { + if let Some(id) = id { + self.filter.insert(id); + } + }); + } + self + } + + /// Extends the list of components that shouldn't be cloned. + pub fn deny_by_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + if self.filter_allows_components { + ids.into_iter().for_each(|id| { + self.filter.remove(&id); + }); + } else { + self.filter.extend(ids); + } + self + } + + /// Extends the list of components that shouldn't be cloned by type ids. + pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + let ids = ids + .into_iter() + .filter_map(|id| self.world.components().get_id(id)); + if self.filter_allows_components { + ids.into_iter().for_each(|id| { + self.filter.remove(&id); + }); + } else { + self.filter.extend(ids); + } + self + } + + /// Sets the filter to deny all components. + pub fn deny_all(&mut self) -> &mut Self { + self.filter_allows_components = true; + self.filter.clear(); + self + } + + /// Overrides the [`ComponentCloneHandler`] for a component in this builder. + /// This handler will be used to clone the component instead of the global one defined by [`ComponentCloneHandlers`](crate::component::ComponentCloneHandlers) + /// + /// See [Handlers section of `EntityCloneBuilder`](EntityCloneBuilder#handlers) to understand how this affects handler priority. + pub fn override_component_clone_handler( + &mut self, + handler: ComponentCloneHandler, + ) -> &mut Self { + if let Some(id) = self.world.components().component_id::() { + self.clone_handlers_overrides.insert(id, handler); + } + self + } + + /// Removes a previously set override of [`ComponentCloneHandler`] for a component in this builder. + pub fn remove_component_clone_handler_override(&mut self) -> &mut Self { + if let Some(id) = self.world.components().component_id::() { + self.clone_handlers_overrides.remove(&id); + } + self + } +} + +#[cfg(test)] +mod tests { + use crate::{self as bevy_ecs, component::Component, entity::EntityCloneBuilder, world::World}; + + #[cfg(feature = "bevy_reflect")] + #[test] + fn clone_entity_using_reflect() { + use crate::reflect::{AppTypeRegistry, ReflectComponent}; + use bevy_reflect::Reflect; + + #[derive(Component, Reflect, Clone, PartialEq, Eq)] + #[reflect(Component)] + struct A { + field: usize, + } + + let mut world = World::default(); + world.init_resource::(); + let registry = world.get_resource::().unwrap(); + registry.write().register::(); + + let component = A { field: 5 }; + + let e = world.spawn(component.clone()).id(); + let e_clone = world.spawn_empty().id(); + + EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + + assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + } + + #[test] + fn clone_entity_using_clone() { + #[derive(Component, Clone, PartialEq, Eq)] + struct A { + field: usize, + } + + let mut world = World::default(); + + let component = A { field: 5 }; + + let e = world.spawn(component.clone()).id(); + let e_clone = world.spawn_empty().id(); + + EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + + assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + } + + #[cfg(feature = "bevy_reflect")] + #[test] + fn clone_entity_specialization() { + use crate::reflect::{AppTypeRegistry, ReflectComponent}; + use bevy_reflect::Reflect; + + #[derive(Component, Reflect, PartialEq, Eq)] + #[reflect(Component)] + struct A { + field: usize, + } + + impl Clone for A { + fn clone(&self) -> Self { + Self { field: 10 } + } + } + + let mut world = World::default(); + world.init_resource::(); + let registry = world.get_resource::().unwrap(); + registry.write().register::(); + + let component = A { field: 5 }; + + let e = world.spawn(component.clone()).id(); + let e_clone = world.spawn_empty().id(); + + EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + + assert!(world + .get::(e_clone) + .is_some_and(|comp| *comp == A { field: 10 })); + } + + #[test] + fn clone_entity_with_allow_filter() { + #[derive(Component, Clone, PartialEq, Eq)] + struct A { + field: usize, + } + + #[derive(Component, Clone)] + struct B; + + let mut world = World::default(); + + let component = A { field: 5 }; + + let e = world.spawn((component.clone(), B)).id(); + let e_clone = world.spawn_empty().id(); + + let mut builder = EntityCloneBuilder::new(&mut world); + builder.deny_all(); + builder.allow::(); + builder.clone_entity(e, e_clone); + + assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + assert!(world.get::(e_clone).is_none()); + } + + #[test] + fn clone_entity_with_deny_filter() { + #[derive(Component, Clone, PartialEq, Eq)] + struct A { + field: usize, + } + + #[derive(Component, Clone)] + struct B; + + #[derive(Component, Clone)] + struct C; + + let mut world = World::default(); + + let component = A { field: 5 }; + + let e = world.spawn((component.clone(), B, C)).id(); + let e_clone = world.spawn_empty().id(); + + let mut builder = EntityCloneBuilder::new(&mut world); + builder.deny::(); + builder.clone_entity(e, e_clone); + + assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + assert!(world.get::(e_clone).is_none()); + assert!(world.get::(e_clone).is_some()); + } + + #[test] + fn clone_entity_with_override_allow_filter() { + #[derive(Component, Clone, PartialEq, Eq)] + struct A { + field: usize, + } + + #[derive(Component, Clone)] + struct B; + + #[derive(Component, Clone)] + struct C; + + let mut world = World::default(); + + let component = A { field: 5 }; + + let e = world.spawn((component.clone(), B, C)).id(); + let e_clone = world.spawn_empty().id(); + + let mut builder = EntityCloneBuilder::new(&mut world); + builder.deny_all(); + builder.allow::(); + builder.allow::(); + builder.allow::(); + builder.deny::(); + builder.clone_entity(e, e_clone); + + assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + assert!(world.get::(e_clone).is_none()); + assert!(world.get::(e_clone).is_some()); + } + + #[test] + fn clone_entity_with_override_bundle() { + #[derive(Component, Clone, PartialEq, Eq)] + struct A { + field: usize, + } + + #[derive(Component, Clone)] + struct B; + + #[derive(Component, Clone)] + struct C; + + let mut world = World::default(); + + let component = A { field: 5 }; + + let e = world.spawn((component.clone(), B, C)).id(); + let e_clone = world.spawn_empty().id(); + + let mut builder = EntityCloneBuilder::new(&mut world); + builder.deny_all(); + builder.allow::<(A, B, C)>(); + builder.deny::<(B, C)>(); + builder.clone_entity(e, e_clone); + + assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + assert!(world.get::(e_clone).is_none()); + assert!(world.get::(e_clone).is_none()); + } +} diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index c2f687af14..163d2775d0 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -35,12 +35,14 @@ //! [`World::despawn`]: crate::world::World::despawn //! [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert //! [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove +mod clone_entities; mod map_entities; mod visit_entities; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; #[cfg(all(feature = "bevy_reflect", feature = "serialize"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; +pub use clone_entities::*; pub use map_entities::*; pub use visit_entities::*; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3a2e24d904..09845d1446 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -49,7 +49,7 @@ pub mod prelude { component::Component, entity::{Entity, EntityMapper}, event::{Event, EventMutator, EventReader, EventWriter, Events}, - observer::{Observer, Trigger}, + observer::{CloneEntityWithObserversExt, Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, schedule::{ diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index a5f332e6f3..8433d3cea3 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,7 +1,8 @@ use crate::{ - component::{Component, ComponentHooks, StorageType}, - entity::Entity, + component::{Component, ComponentCloneHandler, ComponentHooks, StorageType}, + entity::{Entity, EntityCloneBuilder, EntityCloner}, observer::ObserverState, + world::{DeferredWorld, World}, }; /// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to. @@ -39,4 +40,110 @@ impl Component for ObservedBy { } }); } + + fn get_component_clone_handler() -> ComponentCloneHandler { + ComponentCloneHandler::Ignore + } +} + +/// Trait that holds functions for configuring interaction with observers during entity cloning. +pub trait CloneEntityWithObserversExt { + /// Sets the option to automatically add cloned entities to the obsevers targeting source entity. + fn add_observers(&mut self, add_observers: bool) -> &mut Self; +} + +impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> { + fn add_observers(&mut self, add_observers: bool) -> &mut Self { + if add_observers { + self.override_component_clone_handler::(ComponentCloneHandler::Custom( + component_clone_observed_by, + )) + } else { + self.remove_component_clone_handler_override::() + } + } +} + +fn component_clone_observed_by(world: &mut DeferredWorld, entity_cloner: &EntityCloner) { + let target = entity_cloner.target(); + let source = entity_cloner.source(); + + world.commands().queue(move |world: &mut World| { + let observed_by = world + .get::(source) + .map(|observed_by| observed_by.0.clone()) + .expect("Source entity must have ObservedBy"); + + world + .entity_mut(target) + .insert(ObservedBy(observed_by.clone())); + + for observer in &observed_by { + let mut observer_state = world + .get_mut::(*observer) + .expect("Source observer entity must have ObserverState"); + observer_state.descriptor.entities.push(target); + let event_types = observer_state.descriptor.events.clone(); + let components = observer_state.descriptor.components.clone(); + for event_type in event_types { + let observers = world.observers.get_observers(event_type); + if components.is_empty() { + if let Some(map) = observers.entity_observers.get(&source).cloned() { + observers.entity_observers.insert(target, map); + } + } else { + for component in &components { + let Some(observers) = observers.component_observers.get_mut(component) + else { + continue; + }; + if let Some(map) = observers.entity_map.get(&source).cloned() { + observers.entity_map.insert(target, map); + } + } + } + } + } + }); +} + +#[cfg(test)] +mod tests { + use crate::{ + self as bevy_ecs, + entity::EntityCloneBuilder, + event::Event, + observer::{CloneEntityWithObserversExt, Trigger}, + system::{ResMut, Resource}, + world::World, + }; + + #[derive(Resource, Default)] + struct Num(usize); + + #[derive(Event)] + struct E; + + #[test] + fn clone_entity_with_observer() { + let mut world = World::default(); + world.init_resource::(); + + let e = world + .spawn_empty() + .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .id(); + world.flush(); + + world.trigger_targets(E, e); + + let e_clone = world.spawn_empty().id(); + let mut builder = EntityCloneBuilder::new(&mut world); + builder.add_observers(true); + builder.clone_entity(e, e_clone); + + world.trigger_targets(E, [e, e_clone]); + + assert_eq!(world.resource::().0, 3); + } } diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 674f98f650..cec04b94ea 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -4,6 +4,7 @@ mod entity_observer; mod runner; mod trigger_event; +pub use entity_observer::CloneEntityWithObserversExt; pub use runner::*; pub use trigger_event::*; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 289e37e3b4..a6612d01a3 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -11,7 +11,7 @@ use crate::{ bundle::{Bundle, InsertMode}, change_detection::Mut, component::{Component, ComponentId, ComponentInfo}, - entity::{Entities, Entity}, + entity::{Entities, Entity, EntityCloneBuilder}, event::{Event, SendEvent}, observer::{Observer, TriggerEvent, TriggerTargets}, schedule::ScheduleLabel, @@ -271,6 +271,69 @@ impl<'w, 's> Commands<'w, 's> { } } + /// Clones an entity and allows configuring cloning behavior using [`EntityCloneBuilder`], returning [`EntityCommands`] of the cloned entity. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// + /// #[derive(Component, Clone)] + /// struct ComponentA(u32); + /// #[derive(Component, Clone)] + /// struct ComponentB(u32); + /// + /// fn example_system(mut commands: Commands) { + /// // Create a new entity and retrieve its id. + /// let entity = commands.spawn((ComponentA(10), ComponentB(20))).id(); + /// + /// // Create a clone of the first entity, but without ComponentB + /// let entity_clone = commands.clone_entity_with(entity, |builder| { + /// builder.deny::(); + /// }).id(); + /// } + /// # bevy_ecs::system::assert_is_system(example_system); + pub fn clone_entity_with( + &mut self, + entity: Entity, + f: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + ) -> EntityCommands<'_> { + let cloned_entity = self.spawn_empty().id(); + self.queue(move |world: &mut World| { + let mut builder = EntityCloneBuilder::new(world); + f(&mut builder); + builder.clone_entity(entity, cloned_entity); + }); + EntityCommands { + commands: self.reborrow(), + entity: cloned_entity, + } + } + + /// Clones an entity and returns [`EntityCommands`] of the cloned entity. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// + /// #[derive(Component, Clone)] + /// struct ComponentA(u32); + /// #[derive(Component, Clone)] + /// struct ComponentB(u32); + /// + /// fn example_system(mut commands: Commands) { + /// // Create a new entity and retrieve its id. + /// let entity = commands.spawn((ComponentA(10), ComponentB(20))).id(); + /// + /// // Create a clone of the first entity + /// let entity_clone = commands.clone_entity(entity).id(); + /// } + /// # bevy_ecs::system::assert_is_system(example_system); + pub fn clone_entity(&mut self, entity: Entity) -> EntityCommands<'_> { + self.clone_entity_with(entity, |_| {}) + } + /// Reserves a new empty [`Entity`] to be spawned, and returns its corresponding [`EntityCommands`]. /// /// See [`World::spawn_empty`] for more details. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 6eb715af56..2d51122e24 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -34,8 +34,9 @@ use crate::{ bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode}, change_detection::{MutUntyped, TicksMut}, component::{ - Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks, - Components, RequiredComponents, RequiredComponentsError, Tick, + Component, ComponentCloneHandlers, ComponentDescriptor, ComponentHooks, ComponentId, + ComponentInfo, ComponentTicks, Components, RequiredComponents, RequiredComponentsError, + Tick, }, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation}, event::{Event, EventId, Events, SendBatchIds}, @@ -3338,6 +3339,35 @@ impl World { // SAFETY: We just initialized the bundle so its id should definitely be valid. unsafe { self.bundles.get(id).debug_checked_unwrap() } } + + /// Retrieves a mutable reference to the [`ComponentCloneHandlers`]. Can be used to set and update clone functions for components. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// use bevy_ecs::component::{ComponentId, ComponentCloneHandler}; + /// use bevy_ecs::entity::EntityCloner; + /// use bevy_ecs::world::DeferredWorld; + /// + /// fn custom_clone_handler( + /// _world: &mut DeferredWorld, + /// _entity_cloner: &EntityCloner, + /// ) { + /// // Custom cloning logic for component + /// } + /// + /// #[derive(Component)] + /// struct ComponentA; + /// + /// let mut world = World::new(); + /// + /// let component_id = world.register_component::(); + /// + /// world.get_component_clone_handlers_mut() + /// .set_component_handler(component_id, ComponentCloneHandler::Custom(custom_clone_handler)) + /// ``` + pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers { + self.components.get_component_clone_handlers_mut() + } } impl World { diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs index 18ad943ee3..b811c1da2d 100644 --- a/crates/bevy_hierarchy/src/components/children.rs +++ b/crates/bevy_hierarchy/src/components/children.rs @@ -4,7 +4,7 @@ use bevy_ecs::reflect::{ ReflectVisitEntitiesMut, }; use bevy_ecs::{ - component::Component, + component::{Component, ComponentCloneHandler, StorageType}, entity::{Entity, VisitEntitiesMut}, prelude::FromWorld, world::World, @@ -25,7 +25,7 @@ use smallvec::SmallVec; /// [`Query`]: bevy_ecs::system::Query /// [`Parent`]: crate::components::parent::Parent /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -#[derive(Component, Debug, VisitEntitiesMut)] +#[derive(Debug, VisitEntitiesMut)] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( feature = "reflect", @@ -40,6 +40,14 @@ use smallvec::SmallVec; )] pub struct Children(pub(crate) SmallVec<[Entity; 8]>); +impl Component for Children { + const STORAGE_TYPE: StorageType = StorageType::Table; + + fn get_component_clone_handler() -> ComponentCloneHandler { + ComponentCloneHandler::Ignore + } +} + // TODO: We need to impl either FromWorld or Default so Children can be registered as Reflect. // This is because Reflect deserialize by creating an instance and apply a patch on top. // However Children should only ever be set with a real user-defined entities. Its worth looking diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index b369447734..445857bb18 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -4,7 +4,7 @@ use bevy_ecs::reflect::{ ReflectVisitEntitiesMut, }; use bevy_ecs::{ - component::Component, + component::{Component, ComponentCloneHandler, StorageType}, entity::{Entity, VisitEntities, VisitEntitiesMut}, traversal::Traversal, world::{FromWorld, World}, @@ -24,7 +24,7 @@ use core::ops::Deref; /// [`Query`]: bevy_ecs::system::Query /// [`Children`]: super::children::Children /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -#[derive(Component, Debug, Eq, PartialEq, VisitEntities, VisitEntitiesMut)] +#[derive(Debug, Eq, PartialEq, VisitEntities, VisitEntitiesMut)] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( feature = "reflect", @@ -40,6 +40,14 @@ use core::ops::Deref; )] pub struct Parent(pub(crate) Entity); +impl Component for Parent { + const STORAGE_TYPE: StorageType = StorageType::Table; + + fn get_component_clone_handler() -> ComponentCloneHandler { + ComponentCloneHandler::Ignore + } +} + impl Parent { /// Gets the [`Entity`] ID of the parent. #[inline(always)] diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index 4a9d71ace7..d48e74d864 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -1,8 +1,12 @@ -use crate::components::{Children, Parent}; +use crate::{ + components::{Children, Parent}, + BuildChildren, +}; use bevy_ecs::{ - entity::Entity, + component::ComponentCloneHandler, + entity::{Entity, EntityCloneBuilder, EntityCloner}, system::EntityCommands, - world::{Command, EntityWorldMut, World}, + world::{Command, DeferredWorld, EntityWorldMut, World}, }; use bevy_utils::tracing::debug; @@ -198,6 +202,67 @@ impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { } } +/// Trait that holds functions for cloning entities recursively down the hierarchy +pub trait CloneEntityHierarchyExt { + /// Sets the option to recursively clone entities. + /// When set to true all children will be cloned with the same options as the parent. + fn recursive(&mut self, recursive: bool) -> &mut Self; + /// Sets the option to add cloned entity as a child to the parent entity. + fn as_child(&mut self, as_child: bool) -> &mut Self; +} + +impl CloneEntityHierarchyExt for EntityCloneBuilder<'_> { + fn recursive(&mut self, recursive: bool) -> &mut Self { + if recursive { + self.override_component_clone_handler::(ComponentCloneHandler::Custom( + component_clone_children, + )) + } else { + self.remove_component_clone_handler_override::() + } + } + fn as_child(&mut self, as_child: bool) -> &mut Self { + if as_child { + self.override_component_clone_handler::(ComponentCloneHandler::Custom( + component_clone_parent, + )) + } else { + self.remove_component_clone_handler_override::() + } + } +} + +/// Clone handler for the [`Children`] component. Allows to clone the entity recursively. +fn component_clone_children(world: &mut DeferredWorld, entity_cloner: &EntityCloner) { + let children = world + .get::(entity_cloner.source()) + .expect("Source entity must have Children component") + .iter() + .cloned() + .collect::>(); + let parent = entity_cloner.target(); + for child in children { + let child_clone = world.commands().spawn_empty().id(); + let mut entity_cloner = entity_cloner.with_source_and_target(child, child_clone); + world.commands().queue(move |world: &mut World| { + entity_cloner.clone_entity(world); + world.entity_mut(child_clone).set_parent(parent); + }); + } +} + +/// Clone handler for the [`Parent`] component. Allows to add clone as a child to the parent entity. +fn component_clone_parent(world: &mut DeferredWorld, entity_cloner: &EntityCloner) { + let parent = world + .get::(entity_cloner.source()) + .map(|p| p.0) + .expect("Source entity must have Parent component"); + world + .commands() + .entity(entity_cloner.target()) + .set_parent(parent); +} + #[cfg(test)] mod tests { use bevy_ecs::{ @@ -210,6 +275,7 @@ mod tests { use crate::{ child_builder::{BuildChildren, ChildBuild}, components::Children, + CloneEntityHierarchyExt, }; #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] @@ -342,4 +408,89 @@ mod tests { // The original child should be despawned. assert!(world.get_entity(child).is_err()); } + + #[test] + fn clone_entity_recursive() { + #[derive(Component, PartialEq, Eq, Clone)] + struct Component1 { + field: usize, + } + + let parent_component = Component1 { field: 10 }; + let child1_component = Component1 { field: 20 }; + let child1_1_component = Component1 { field: 30 }; + let child2_component = Component1 { field: 21 }; + let child2_1_component = Component1 { field: 31 }; + + let mut world = World::default(); + + let mut queue = CommandQueue::default(); + let e_clone = { + let mut commands = Commands::new(&mut queue, &world); + let e = commands + .spawn(parent_component.clone()) + .with_children(|children| { + children + .spawn(child1_component.clone()) + .with_children(|children| { + children.spawn(child1_1_component.clone()); + }); + children + .spawn(child2_component.clone()) + .with_children(|children| { + children.spawn(child2_1_component.clone()); + }); + }) + .id(); + let e_clone = commands + .clone_entity_with(e, |builder| { + builder.recursive(true); + }) + .id(); + e_clone + }; + queue.apply(&mut world); + + assert!(world + .get::(e_clone) + .is_some_and(|c| *c == parent_component)); + + let children = world.get::(e_clone).unwrap(); + for (child, (component1, component2)) in children.iter().zip([ + (child1_component, child1_1_component), + (child2_component, child2_1_component), + ]) { + assert!(world + .get::(*child) + .is_some_and(|c| *c == component1)); + for child2 in world.get::(*child).unwrap().iter() { + assert!(world + .get::(*child2) + .is_some_and(|c| *c == component2)); + } + } + } + + #[test] + fn clone_entity_as_child() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let mut commands = Commands::new(&mut queue, &world); + + let child = commands.spawn_empty().id(); + let parent = commands.spawn_empty().add_child(child).id(); + + let child_clone = commands + .clone_entity_with(child, |builder| { + builder.as_child(true); + }) + .id(); + + queue.apply(&mut world); + + assert!(world + .entity(parent) + .get::() + .is_some_and(|c| c.contains(&child_clone))); + } }