Faster entity cloning (#16717)

# Objective

#16132 introduced entity cloning functionality, and while it works and
is useful, it can be made faster. This is the promised follow-up to
improve performance.

## Solution

**PREFACE**: This is my first time writing `unsafe` in rust and I have
only vague idea about what I'm doing. I would encourage reviewers to
scrutinize `unsafe` parts in particular.

The solution is to clone component data to an intermediate buffer and
use `EntityWorldMut::insert_by_ids` to insert components without
additional archetype moves.

To facilitate this, `EntityCloner::clone_entity` now reads all
components of the source entity and provides clone handlers with the
ability to read component data straight from component storage using
`read_source_component` and write to an intermediate buffer using
`write_target_component`. `ComponentId` is used to check that requested
type corresponds to the type available on source entity.

Reflect-based handler is a little trickier to pull of: we only have
`&dyn Reflect` and no direct access to the underlying data.
`ReflectFromPtr` can be used to get `&dyn Reflect` from concrete
component data, but to write it we need to create a clone of the
underlying data using `Reflect`. For this reason only components that
have `ReflectDefault` or `ReflectFromReflect` or `ReflectFromWorld` can
be cloned, all other components will be skipped. The good news is that
this is actually only a temporary limitation: once #13432 lands we will
be able to clone component without requiring one of these `type data`s.

This PR also introduces `entity_cloning` benchmark to better compare
changes between the PR and main, you can see the results in the
**showcase** section.

## Testing

- All previous tests passing
- Added test for fast reflect clone path (temporary, will be removed
after reflection-based cloning lands)
- Ran miri

## Showcase
Here's a table demonstrating the improvement:

| **benchmark** | **main, avg** | **PR, avg** | **change, avg** |
| ----------------------- | ------------- | ----------- |
--------------- |
| many components reflect | 18.505 µs | 2.1351 µs | -89.095% |
| hierarchy wide reflect* | 22.778 ms | 4.1875 ms | -81.616% |
| hierarchy tall reflect* | 107.24 µs | 26.322 µs | -77.141% |
| hierarchy many reflect | 78.533 ms | 9.7415 ms | -87.596% |
| many components clone | 1.3633 µs | 758.17 ns | -45.937% |
| hierarchy wide clone* | 2.7716 ms | 3.3411 ms | +20.546% |
| hierarchy tall clone* | 17.646 µs | 20.190 µs | +17.379% |
| hierarchy many clone | 5.8779 ms | 4.2650 ms | -27.439% |

*: these benchmarks have entities with only 1 component

## Considerations
Once #10154 is resolved a large part of the functionality in this PR
will probably become obsolete. It might still be a little bit faster
than using command batching, but the complexity might not be worth it.

## Migration Guide
- `&EntityCloner` in component clone handlers is changed to `&mut
ComponentCloneCtx` to better separate data.
- Changed `EntityCloneHandler` from enum to struct and added convenience
functions to add default clone and reflect handler more easily.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
This commit is contained in:
eugineerd 2024-12-18 23:03:39 +03:00 committed by GitHub
parent 3ef99cf82c
commit 20049d4c34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 935 additions and 191 deletions

View File

@ -60,6 +60,11 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[[bench]]
name = "entity_cloning"
path = "benches/bevy_ecs/entity_cloning.rs"
harness = false
[[bench]]
name = "ecs"
path = "benches/bevy_ecs/main.rs"

View File

@ -0,0 +1,171 @@
use bevy_ecs::bundle::Bundle;
use bevy_ecs::reflect::AppTypeRegistry;
use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World};
use bevy_hierarchy::{BuildChildren, CloneEntityHierarchyExt};
use bevy_math::Mat4;
use bevy_reflect::{GetTypeRegistration, Reflect};
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
criterion_group!(benches, reflect_benches, clone_benches);
criterion_main!(benches);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C1(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C2(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C3(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C4(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C5(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C6(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C7(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C8(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C9(Mat4);
#[derive(Component, Reflect, Default, Clone)]
#[reflect(Component)]
struct C10(Mat4);
type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10);
fn hierarchy<C: Bundle + Default + GetTypeRegistration>(
b: &mut Bencher,
width: usize,
height: usize,
clone_via_reflect: bool,
) {
let mut world = World::default();
let registry = AppTypeRegistry::default();
{
let mut r = registry.write();
r.register::<C>();
}
world.insert_resource(registry);
world.register_bundle::<C>();
if clone_via_reflect {
let mut components = Vec::new();
C::get_component_ids(world.components(), &mut |id| components.push(id.unwrap()));
for component in components {
world
.get_component_clone_handlers_mut()
.set_component_handler(
component,
bevy_ecs::component::ComponentCloneHandler::reflect_handler(),
);
}
}
let id = world.spawn(black_box(C::default())).id();
let mut hierarchy_level = vec![id];
for _ in 0..height {
let current_hierarchy_level = hierarchy_level.clone();
hierarchy_level.clear();
for parent_id in current_hierarchy_level {
for _ in 0..width {
let child_id = world
.spawn(black_box(C::default()))
.set_parent(parent_id)
.id();
hierarchy_level.push(child_id)
}
}
}
world.flush();
b.iter(move || {
world.commands().entity(id).clone_and_spawn_with(|builder| {
builder.recursive(true);
});
world.flush();
});
}
fn simple<C: Bundle + Default + GetTypeRegistration>(b: &mut Bencher, clone_via_reflect: bool) {
let mut world = World::default();
let registry = AppTypeRegistry::default();
{
let mut r = registry.write();
r.register::<C>();
}
world.insert_resource(registry);
world.register_bundle::<C>();
if clone_via_reflect {
let mut components = Vec::new();
C::get_component_ids(world.components(), &mut |id| components.push(id.unwrap()));
for component in components {
world
.get_component_clone_handlers_mut()
.set_component_handler(
component,
bevy_ecs::component::ComponentCloneHandler::reflect_handler(),
);
}
}
let id = world.spawn(black_box(C::default())).id();
b.iter(move || {
world.commands().entity(id).clone_and_spawn();
world.flush();
});
}
fn reflect_benches(c: &mut Criterion) {
c.bench_function("many components reflect", |b| {
simple::<ComplexBundle>(b, true);
});
c.bench_function("hierarchy wide reflect", |b| {
hierarchy::<C1>(b, 10, 4, true);
});
c.bench_function("hierarchy tall reflect", |b| {
hierarchy::<C1>(b, 1, 50, true);
});
c.bench_function("hierarchy many reflect", |b| {
hierarchy::<ComplexBundle>(b, 5, 5, true);
});
}
fn clone_benches(c: &mut Criterion) {
c.bench_function("many components clone", |b| {
simple::<ComplexBundle>(b, false);
});
c.bench_function("hierarchy wide clone", |b| {
hierarchy::<C1>(b, 10, 4, false);
});
c.bench_function("hierarchy tall clone", |b| {
hierarchy::<C1>(b, 1, 50, false);
});
c.bench_function("hierarchy many clone", |b| {
hierarchy::<ComplexBundle>(b, 5, 5, false);
});
}

View File

@ -133,6 +133,7 @@ spin = { version = "0.9.8", default-features = false, features = [
] }
tracing = { version = "0.1", default-features = false, optional = true }
log = { version = "0.4", default-features = false }
bumpalo = "3"
[dev-dependencies]
rand = "0.8"

View File

@ -5,12 +5,14 @@ use crate::{
archetype::ArchetypeFlags,
bundle::BundleInfo,
change_detection::MAX_CHANGE_AGE,
entity::{Entity, EntityCloner},
entity::{ComponentCloneCtx, Entity},
query::DebugCheckedUnwrap,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
system::{Local, Resource, SystemParam},
world::{DeferredWorld, FromWorld, World},
};
#[cfg(feature = "bevy_reflect")]
use alloc::boxed::Box;
use alloc::{borrow::Cow, format, vec::Vec};
pub use bevy_ecs_macros::Component;
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
@ -419,7 +421,7 @@ pub trait Component: Send + Sync + 'static {
///
/// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority.
fn get_component_clone_handler() -> ComponentCloneHandler {
ComponentCloneHandler::default()
ComponentCloneHandler::default_handler()
}
}
@ -981,18 +983,45 @@ impl ComponentDescriptor {
}
/// Function type that can be used to clone an entity.
pub type ComponentCloneFn = fn(&mut DeferredWorld, &EntityCloner);
pub type ComponentCloneFn = fn(&mut DeferredWorld, &mut ComponentCloneCtx);
/// An enum instructing how to clone a component.
#[derive(Debug, Default)]
pub enum ComponentCloneHandler {
#[default]
/// A struct instructing which clone handler to use when cloning a component.
#[derive(Debug)]
pub struct ComponentCloneHandler(Option<ComponentCloneFn>);
impl ComponentCloneHandler {
/// Use the global default function to clone the component with this handler.
Default,
pub fn default_handler() -> Self {
Self(None)
}
/// Do not clone the component. When a command to clone an entity is issued, component with this handler will be skipped.
Ignore,
pub fn ignore() -> Self {
Self(Some(component_clone_ignore))
}
/// Set clone handler based on `Clone` trait.
///
/// If set as a handler for a component that is not the same as the one used to create this handler, it will panic.
pub fn clone_handler<C: Component + Clone>() -> Self {
Self(Some(component_clone_via_clone::<C>))
}
/// Set clone handler based on `Reflect` trait.
#[cfg(feature = "bevy_reflect")]
pub fn reflect_handler() -> Self {
Self(Some(component_clone_via_reflect))
}
/// Set a custom handler for the component.
Custom(ComponentCloneFn),
pub fn custom_handler(handler: ComponentCloneFn) -> Self {
Self(Some(handler))
}
/// Get [`ComponentCloneFn`] representing this handler or `None` if set to default handler.
pub fn get_handler(&self) -> Option<ComponentCloneFn> {
self.0
}
}
/// A registry of component clone handlers. Allows to set global default and per-component clone function for all components in the world.
@ -1003,7 +1032,7 @@ pub struct ComponentCloneHandlers {
}
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
/// Sets the default handler for this registry. All components with [`default`](ComponentCloneHandler::default_handler) 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.
@ -1023,11 +1052,7 @@ impl ComponentCloneHandlers {
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),
};
self.handlers[id.0] = handler.0;
}
/// Checks if the specified component is registered. If not, the component will use the default global handler.
@ -2146,62 +2171,96 @@ pub fn enforce_no_required_components_recursion(
///
/// See [`ComponentCloneHandlers`] for more details.
pub fn component_clone_via_clone<C: Clone + Component>(
world: &mut DeferredWorld,
entity_cloner: &EntityCloner,
_world: &mut DeferredWorld,
ctx: &mut ComponentCloneCtx,
) {
let component = world
.entity(entity_cloner.source())
.get::<C>()
.expect("Component must exists on source entity")
.clone();
world
.commands()
.entity(entity_cloner.target())
.insert(component);
if let Some(component) = ctx.read_source_component::<C>() {
ctx.write_target_component(component.clone());
}
}
/// 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.
/// To clone a component using this handler, the following must be true:
/// - World has [`AppTypeRegistry`](crate::reflect::AppTypeRegistry)
/// - Component has [`TypeId`]
/// - Component is registered
/// - Component has [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr) registered
/// - Component has one of the following registered: [`ReflectFromReflect`](bevy_reflect::ReflectFromReflect),
/// [`ReflectDefault`](bevy_reflect::std_traits::ReflectDefault), [`ReflectFromWorld`](crate::reflect::ReflectFromWorld)
///
/// If any of the conditions is not satisfied, the component will be skipped.
///
/// See [`EntityCloneBuilder`](crate::entity::EntityCloneBuilder) for 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::<crate::reflect::AppTypeRegistry, ()>(|world, registry| {
let registry = registry.read();
pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
let Some(registry) = ctx.type_registry() else {
return;
};
let Some(source_component_reflect) = ctx.read_source_component_reflect() else {
return;
};
let component_info = ctx.component_info();
// checked in read_source_component_reflect
let type_id = component_info.type_id().unwrap();
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::<crate::reflect::ReflectComponent>(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, &registry);
// Try to clone using ReflectFromReflect
if let Some(reflect_from_reflect) =
registry.get_type_data::<bevy_reflect::ReflectFromReflect>(type_id)
{
if let Some(component) =
reflect_from_reflect.from_reflect(source_component_reflect.as_partial_reflect())
{
drop(registry);
ctx.write_target_component_reflect(component);
return;
}
}
// Else, try to clone using ReflectDefault
if let Some(reflect_default) =
registry.get_type_data::<bevy_reflect::std_traits::ReflectDefault>(type_id)
{
let mut component = reflect_default.default();
component.apply(source_component_reflect.as_partial_reflect());
drop(registry);
ctx.write_target_component_reflect(component);
return;
}
// Otherwise, try to clone using ReflectFromWorld
if let Some(reflect_from_world) =
registry.get_type_data::<crate::reflect::ReflectFromWorld>(type_id)
{
let reflect_from_world = reflect_from_world.clone();
let source_component_cloned = source_component_reflect.clone_value();
let component_layout = component_info.layout();
let target = ctx.target();
let component_id = ctx.component_id();
world.commands().queue(move |world: &mut World| {
let mut component = reflect_from_world.from_world(world);
assert_eq!(type_id, (*component).type_id());
component.apply(source_component_cloned.as_partial_reflect());
// SAFETY:
// - component_id is from the same world as target entity
// - component is a valid value represented by component_id
unsafe {
let raw_component_ptr =
core::ptr::NonNull::new_unchecked(Box::into_raw(component).cast::<u8>());
world
.entity_mut(target)
.insert_by_id(component_id, OwningPtr::new(raw_component_ptr));
alloc::alloc::dealloc(raw_component_ptr.as_ptr(), component_layout);
}
});
});
}
}
/// Noop implementation of component clone handler function.
///
/// See [`ComponentCloneHandlers`] for more details.
pub fn component_clone_ignore(_world: &mut DeferredWorld, _entity_cloner: &EntityCloner) {}
/// See [`EntityCloneBuilder`](crate::entity::EntityCloneBuilder) for details.
pub fn component_clone_ignore(_world: &mut DeferredWorld, _ctx: &mut ComponentCloneCtx) {}
/// Wrapper for components clone specialization using autoderef.
#[doc(hidden)]
@ -2220,7 +2279,7 @@ pub trait ComponentCloneBase {
}
impl<C: Component> ComponentCloneBase for ComponentCloneSpecializationWrapper<C> {
fn get_component_clone_handler(&self) -> ComponentCloneHandler {
ComponentCloneHandler::default()
ComponentCloneHandler::default_handler()
}
}
@ -2231,6 +2290,6 @@ pub trait ComponentCloneViaClone {
}
impl<C: Clone + Component> ComponentCloneViaClone for &ComponentCloneSpecializationWrapper<C> {
fn get_component_clone_handler(&self) -> ComponentCloneHandler {
ComponentCloneHandler::Custom(component_clone_via_clone::<C>)
ComponentCloneHandler::clone_handler::<C>()
}
}

View File

@ -1,8 +1,13 @@
use alloc::{borrow::ToOwned, vec::Vec};
use core::any::TypeId;
use bevy_ptr::{Ptr, PtrMut};
use bumpalo::Bump;
use core::{any::TypeId, ptr::NonNull};
use bevy_utils::{HashMap, HashSet};
#[cfg(feature = "bevy_reflect")]
use alloc::boxed::Box;
#[cfg(feature = "portable-atomic")]
use portable_atomic_util::Arc;
@ -11,16 +16,261 @@ use alloc::sync::Arc;
use crate::{
bundle::Bundle,
component::{component_clone_ignore, Component, ComponentCloneHandler, ComponentId},
component::{Component, ComponentCloneHandler, ComponentId, ComponentInfo, Components},
entity::Entity,
query::DebugCheckedUnwrap,
world::World,
};
/// A helper struct to clone an entity. Used internally by [`EntityCloneBuilder::clone_entity`] and custom clone handlers.
/// Context for component clone handlers.
///
/// Provides fast access to useful resources like [`AppTypeRegistry`](crate::reflect::AppTypeRegistry)
/// and allows component clone handler to get information about component being cloned.
pub struct ComponentCloneCtx<'a, 'b> {
component_id: ComponentId,
source_component_ptr: Ptr<'a>,
target_component_written: bool,
target_components_ptrs: &'a mut Vec<PtrMut<'b>>,
target_components_buffer: &'b Bump,
components: &'a Components,
component_info: &'a ComponentInfo,
entity_cloner: &'a EntityCloner,
#[cfg(feature = "bevy_reflect")]
type_registry: Option<&'a crate::reflect::AppTypeRegistry>,
#[cfg(not(feature = "bevy_reflect"))]
#[expect(dead_code)]
type_registry: Option<()>,
}
impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// Create a new instance of `ComponentCloneCtx` that can be passed to component clone handlers.
///
/// # Safety
/// Caller must ensure that:
/// - `components` and `component_id` are from the same world.
/// - `source_component_ptr` points to a valid component of type represented by `component_id`.
unsafe fn new(
component_id: ComponentId,
source_component_ptr: Ptr<'a>,
target_components_ptrs: &'a mut Vec<PtrMut<'b>>,
target_components_buffer: &'b Bump,
components: &'a Components,
entity_cloner: &'a EntityCloner,
#[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>,
#[cfg(not(feature = "bevy_reflect"))] type_registry: Option<()>,
) -> Self {
Self {
component_id,
source_component_ptr,
target_components_ptrs,
target_component_written: false,
target_components_buffer,
components,
component_info: components.get_info_unchecked(component_id),
entity_cloner,
type_registry,
}
}
/// Returns true if [`write_target_component`](`Self::write_target_component`) was called before.
pub fn target_component_written(&self) -> bool {
self.target_component_written
}
/// Returns the current source entity.
pub fn source(&self) -> Entity {
self.entity_cloner.source
}
/// Returns the current target entity.
pub fn target(&self) -> Entity {
self.entity_cloner.target
}
/// Returns the [`ComponentId`] of the component being cloned.
pub fn component_id(&self) -> ComponentId {
self.component_id
}
/// Returns the [`ComponentInfo`] of the component being cloned.
pub fn component_info(&self) -> &ComponentInfo {
self.component_info
}
/// Returns a reference to the component on the source entity.
///
/// Will return `None` if `ComponentId` of requested component does not match `ComponentId` of source component
pub fn read_source_component<T: Component>(&self) -> Option<&T> {
if self
.component_info
.type_id()
.is_some_and(|id| id == TypeId::of::<T>())
{
// SAFETY:
// - Components and ComponentId are from the same world
// - source_component_ptr holds valid data of the type referenced by ComponentId
unsafe { Some(self.source_component_ptr.deref::<T>()) }
} else {
None
}
}
/// Returns a reference to the component on the source entity as [`&dyn Reflect`](bevy_reflect::Reflect).
///
/// Will return `None` if:
/// - World does not have [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`).
/// - Component does not implement [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr).
/// - Component is not registered.
/// - Component does not have [`TypeId`]
/// - Registered [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr)'s [`TypeId`] does not match component's [`TypeId`]
#[cfg(feature = "bevy_reflect")]
pub fn read_source_component_reflect(&self) -> Option<&dyn bevy_reflect::Reflect> {
let registry = self.type_registry?.read();
let type_id = self.component_info.type_id()?;
let reflect_from_ptr = registry.get_type_data::<bevy_reflect::ReflectFromPtr>(type_id)?;
if reflect_from_ptr.type_id() != type_id {
return None;
}
// SAFETY: `source_component_ptr` stores data represented by `component_id`, which we used to get `ReflectFromPtr`.
unsafe { Some(reflect_from_ptr.as_reflect(self.source_component_ptr)) }
}
/// Writes component data to target entity.
///
/// # Panics
/// This will panic if:
/// - Component has already been written once.
/// - Component being written is not registered in the world.
/// - `ComponentId` of component being written does not match expected `ComponentId`.
pub fn write_target_component<T: Component>(&mut self, component: T) {
let short_name = disqualified::ShortName::of::<T>();
if self.target_component_written {
panic!("Trying to write component '{short_name}' multiple times")
}
if !self
.component_info
.type_id()
.is_some_and(|id| id == TypeId::of::<T>())
{
panic!("TypeId of component '{short_name}' does not match source component TypeId")
};
let component_ref = self.target_components_buffer.alloc(component);
self.target_components_ptrs
.push(PtrMut::from(component_ref));
self.target_component_written = true;
}
/// Writes component data to target entity by providing a pointer to source component data and a pointer to uninitialized target component data.
///
/// This method allows caller to provide a function (`clone_fn`) to clone component using untyped pointers.
/// First argument to `clone_fn` points to source component data ([`Ptr`]), second argument points to uninitialized buffer ([`NonNull`]) allocated with layout
/// described by [`ComponentInfo`] stored in this [`ComponentCloneCtx`]. If cloning is successful and uninitialized buffer contains a valid clone of
/// source component, `clone_fn` should return `true`, otherwise it should return `false`.
///
/// # Safety
/// Caller must ensure that if `clone_fn` is called and returns `true`, the second argument ([`NonNull`] pointer) points to a valid component data
/// described by [`ComponentInfo`] stored in this [`ComponentCloneCtx`].
/// # Panics
/// This will panic if component has already been written once.
pub unsafe fn write_target_component_ptr(
&mut self,
clone_fn: impl FnOnce(Ptr, NonNull<u8>) -> bool,
) {
if self.target_component_written {
panic!("Trying to write component multiple times")
}
let layout = self.component_info.layout();
let target_component_data_ptr = self.target_components_buffer.alloc_layout(layout);
if clone_fn(self.source_component_ptr, target_component_data_ptr) {
self.target_components_ptrs
.push(PtrMut::new(target_component_data_ptr));
self.target_component_written = true;
}
}
/// Writes component data to target entity.
///
/// # Panics
/// This will panic if:
/// - World does not have [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`).
/// - Component does not implement [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr).
/// - Source component does not have [`TypeId`].
/// - Passed component's [`TypeId`] does not match source component [`TypeId`].
/// - Component has already been written once.
#[cfg(feature = "bevy_reflect")]
pub fn write_target_component_reflect(&mut self, component: Box<dyn bevy_reflect::Reflect>) {
if self.target_component_written {
panic!("Trying to write component multiple times")
}
let source_type_id = self
.component_info
.type_id()
.expect("Source component must have TypeId");
let component_type_id = component.type_id();
if source_type_id != component_type_id {
panic!("Passed component TypeId does not match source component TypeId")
}
let component_layout = self.component_info.layout();
let component_data_ptr = Box::into_raw(component).cast::<u8>();
let target_component_data_ptr =
self.target_components_buffer.alloc_layout(component_layout);
// SAFETY:
// - target_component_data_ptr and component_data have the same data type.
// - component_data_ptr has layout of component_layout
unsafe {
core::ptr::copy_nonoverlapping(
component_data_ptr,
target_component_data_ptr.as_ptr(),
component_layout.size(),
);
self.target_components_ptrs
.push(PtrMut::new(target_component_data_ptr));
alloc::alloc::dealloc(component_data_ptr, component_layout);
}
self.target_component_written = true;
}
/// Return a reference to this context's `EntityCloner` instance.
///
/// This can be used to issue clone commands using the same cloning configuration:
/// ```
/// # use bevy_ecs::world::{DeferredWorld, World};
/// # use bevy_ecs::entity::ComponentCloneCtx;
/// fn clone_handler(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
/// let another_target = world.commands().spawn_empty().id();
/// let mut entity_cloner = ctx
/// .entity_cloner()
/// .with_source_and_target(ctx.source(), another_target);
/// world.commands().queue(move |world: &mut World| {
/// entity_cloner.clone_entity(world);
/// });
/// }
/// ```
pub fn entity_cloner(&self) -> &EntityCloner {
self.entity_cloner
}
/// Returns instance of [`Components`].
pub fn components(&self) -> &Components {
self.components
}
/// Returns [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`) if it exists in the world.
///
/// NOTE: Prefer this method instead of manually reading the resource from the world.
#[cfg(feature = "bevy_reflect")]
pub fn type_registry(&self) -> Option<&crate::reflect::AppTypeRegistry> {
self.type_registry
}
}
/// A helper struct to clone an entity. Used internally by [`EntityCloneBuilder::clone_entity`].
pub struct EntityCloner {
source: Entity,
target: Entity,
component_id: Option<ComponentId>,
filter_allows_components: bool,
filter: Arc<HashSet<ComponentId>>,
clone_handlers_overrides: Arc<HashMap<ComponentId, ComponentCloneHandler>>,
@ -30,32 +280,96 @@ pub struct EntityCloner {
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");
// SAFETY:
// - `source_entity` is read-only.
// - `type_registry` is read-only.
// - `components` is read-only.
// - `deferred_world` disallows structural ecs changes, which means all read-only resources above a not affected.
let (type_registry, source_entity, components, mut deferred_world) = unsafe {
let world = world.as_unsafe_world_cell();
let source_entity = world
.get_entity(self.source)
.expect("Source entity must exist");
#[cfg(feature = "bevy_reflect")]
let app_registry = world.get_resource::<crate::reflect::AppTypeRegistry>();
#[cfg(not(feature = "bevy_reflect"))]
let app_registry = Option::<()>::None;
(
app_registry,
source_entity,
world.components(),
world.into_deferred(),
)
};
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)),
);
let component_data = Bump::new();
let mut component_ids: Vec<ComponentId> = Vec::with_capacity(archetype.component_count());
let mut component_data_ptrs: Vec<PtrMut> = Vec::with_capacity(archetype.component_count());
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,
for component in archetype.components() {
if !self.is_cloning_allowed(&component) {
continue;
}
let global_handlers = components.get_component_clone_handlers();
let handler = match self.clone_handlers_overrides.get(&component) {
Some(handler) => handler
.get_handler()
.unwrap_or_else(|| global_handlers.get_default_handler()),
None => global_handlers.get_handler(component),
};
self.component_id = Some(*component);
(handler)(&mut world.into(), self);
// SAFETY:
// - There are no other mutable references to source entity.
// - `component` is from `source_entity`'s archetype
let source_component_ptr =
unsafe { source_entity.get_by_id(component).debug_checked_unwrap() };
// SAFETY:
// - `components` and `component` are from the same world
// - `source_component_ptr` is valid and points to the same type as represented by `component`
let mut ctx = unsafe {
ComponentCloneCtx::new(
component,
source_component_ptr,
&mut component_data_ptrs,
&component_data,
components,
self,
type_registry,
)
};
(handler)(&mut deferred_world, &mut ctx);
if ctx.target_component_written {
component_ids.push(component);
}
}
world.flush();
if !world.entities.contains(self.target) {
panic!("Target entity does not exist");
}
debug_assert_eq!(component_data_ptrs.len(), component_ids.len());
// SAFETY:
// - All `component_ids` are from the same world as `target` entity
// - All `component_data_ptrs` are valid types represented by `component_ids`
unsafe {
world.entity_mut(self.target).insert_by_ids(
&component_ids,
component_data_ptrs.into_iter().map(|ptr| ptr.promote()),
);
}
if self.move_components {
world.entity_mut(self.source).remove_by_ids(&components);
world.entity_mut(self.source).remove_by_ids(&component_ids);
}
}
@ -64,22 +378,6 @@ impl EntityCloner {
|| (!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 {
@ -123,7 +421,7 @@ impl EntityCloner {
/// 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
/// To use `Clone`-based handler ([`ComponentCloneHandler::clone_handler`]) 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):
@ -137,7 +435,7 @@ impl EntityCloner {
/// const STORAGE_TYPE: StorageType = StorageType::Table;
/// type Mutability = Mutable;
/// fn get_component_clone_handler() -> ComponentCloneHandler {
/// ComponentCloneHandler::Custom(component_clone_via_clone::<Self>)
/// ComponentCloneHandler::clone_handler::<Self>()
/// }
/// }
/// ```
@ -187,15 +485,12 @@ impl<'w> EntityCloneBuilder<'w> {
EntityCloner {
source,
target,
component_id: None,
filter_allows_components,
filter: Arc::new(filter),
clone_handlers_overrides: Arc::new(clone_handlers_overrides),
move_components,
}
.clone_entity(world);
world.flush_commands();
}
/// By default, any components allowed/denied through the filter will automatically
@ -369,34 +664,219 @@ impl<'w> EntityCloneBuilder<'w> {
#[cfg(test)]
mod tests {
use crate::{self as bevy_ecs, component::Component, entity::EntityCloneBuilder, world::World};
use super::ComponentCloneCtx;
use crate::{
self as bevy_ecs,
component::{Component, ComponentCloneHandler, ComponentDescriptor, StorageType},
entity::EntityCloneBuilder,
world::{DeferredWorld, World},
};
use bevy_ecs_macros::require;
use bevy_ptr::OwningPtr;
use core::alloc::Layout;
#[cfg(feature = "bevy_reflect")]
#[test]
fn clone_entity_using_reflect() {
use crate::reflect::{AppTypeRegistry, ReflectComponent};
use bevy_reflect::Reflect;
mod reflect {
use super::*;
use crate::reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld};
use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr};
#[derive(Component, Reflect, Clone, PartialEq, Eq)]
#[reflect(Component)]
struct A {
field: usize,
#[test]
fn clone_entity_using_reflect() {
#[derive(Component, Reflect, Clone, PartialEq, Eq)]
#[reflect(Component)]
struct A {
field: usize,
}
let mut world = World::default();
world.init_resource::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
registry.write().register::<A>();
world.register_component::<A>();
let id = world.component_id::<A>().unwrap();
world
.get_component_clone_handlers_mut()
.set_component_handler(id, ComponentCloneHandler::reflect_handler());
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::<A>(e_clone).is_some_and(|c| *c == component));
}
let mut world = World::default();
world.init_resource::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
registry.write().register::<A>();
// TODO: remove this when https://github.com/bevyengine/bevy/pull/13432 lands
#[test]
fn clone_entity_using_reflect_all_paths() {
// `ReflectDefault`-based fast path
#[derive(Component, Reflect, PartialEq, Eq, Default, Debug)]
#[reflect(Default)]
#[reflect(from_reflect = false)]
struct A {
field: usize,
field2: Vec<usize>,
}
let component = A { field: 5 };
// `ReflectFromReflect`-based fast path
#[derive(Component, Reflect, PartialEq, Eq, Default, Debug)]
struct B {
field: usize,
field2: Vec<usize>,
}
let e = world.spawn(component.clone()).id();
let e_clone = world.spawn_empty().id();
// `ReflectFromWorld`-based fast path
#[derive(Component, Reflect, PartialEq, Eq, Default, Debug)]
#[reflect(FromWorld)]
#[reflect(from_reflect = false)]
struct C {
field: usize,
field2: Vec<usize>,
}
EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone);
let mut world = World::default();
world.init_resource::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
registry.write().register::<(A, B, C)>();
assert!(world.get::<A>(e_clone).is_some_and(|c| *c == component));
let a_id = world.register_component::<A>();
let b_id = world.register_component::<B>();
let c_id = world.register_component::<C>();
let handlers = world.get_component_clone_handlers_mut();
handlers.set_component_handler(a_id, ComponentCloneHandler::reflect_handler());
handlers.set_component_handler(b_id, ComponentCloneHandler::reflect_handler());
handlers.set_component_handler(c_id, ComponentCloneHandler::reflect_handler());
let component_a = A {
field: 5,
field2: vec![1, 2, 3, 4, 5],
};
let component_b = B {
field: 6,
field2: vec![1, 2, 3, 4, 5],
};
let component_c = C {
field: 7,
field2: vec![1, 2, 3, 4, 5],
};
let e = world.spawn((component_a, component_b, component_c)).id();
let e_clone = world.spawn_empty().id();
EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone);
assert_eq!(world.get::<A>(e_clone), Some(world.get::<A>(e).unwrap()));
assert_eq!(world.get::<B>(e_clone), Some(world.get::<B>(e).unwrap()));
assert_eq!(world.get::<C>(e_clone), Some(world.get::<C>(e).unwrap()));
}
#[test]
fn read_source_component_reflect_should_return_none_on_invalid_reflect_from_ptr() {
#[derive(Component, Reflect)]
struct A;
#[derive(Component, Reflect)]
struct B;
fn test_handler(_world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
assert!(ctx.read_source_component_reflect().is_none());
}
let mut world = World::default();
world.init_resource::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
{
let mut registry = registry.write();
registry.register::<A>();
registry
.get_mut(core::any::TypeId::of::<A>())
.unwrap()
.insert(<ReflectFromPtr as FromType<B>>::from_type());
}
let a_id = world.register_component::<A>();
let handlers = world.get_component_clone_handlers_mut();
handlers
.set_component_handler(a_id, ComponentCloneHandler::custom_handler(test_handler));
let e = world.spawn(A).id();
let e_clone = world.spawn_empty().id();
EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone);
}
#[test]
fn clone_entity_specialization() {
#[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::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
registry.write().register::<A>();
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::<A>(e_clone)
.is_some_and(|comp| *comp == A { field: 10 }));
}
#[test]
fn clone_entity_using_reflect_should_skip_without_panic() {
// Not reflected
#[derive(Component, PartialEq, Eq, Default, Debug)]
struct A;
// No valid type data
#[derive(Component, Reflect, PartialEq, Eq, Default, Debug)]
#[reflect(Component)]
#[reflect(from_reflect = false)]
struct B;
let mut world = World::default();
let a_id = world.register_component::<A>();
let b_id = world.register_component::<B>();
let handlers = world.get_component_clone_handlers_mut();
handlers.set_component_handler(a_id, ComponentCloneHandler::reflect_handler());
handlers.set_component_handler(b_id, ComponentCloneHandler::reflect_handler());
// No AppTypeRegistry
let e = world.spawn((A, B)).id();
let e_clone = world.spawn_empty().id();
EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone);
assert_eq!(world.get::<A>(e_clone), None);
assert_eq!(world.get::<B>(e_clone), None);
// With AppTypeRegistry
world.init_resource::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
registry.write().register::<B>();
let e = world.spawn((A, B)).id();
let e_clone = world.spawn_empty().id();
EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone);
assert_eq!(world.get::<A>(e_clone), None);
assert_eq!(world.get::<B>(e_clone), None);
}
}
#[test]
@ -418,41 +898,6 @@ mod tests {
assert!(world.get::<A>(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::<AppTypeRegistry>();
let registry = world.get_resource::<AppTypeRegistry>().unwrap();
registry.write().register::<A>();
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::<A>(e_clone)
.is_some_and(|comp| *comp == A { field: 10 }));
}
#[test]
fn clone_entity_with_allow_filter() {
#[derive(Component, Clone, PartialEq, Eq)]
@ -601,4 +1046,70 @@ mod tests {
assert_eq!(world.entity(e_clone).get::<B>(), Some(&B));
assert_eq!(world.entity(e_clone).get::<C>(), Some(&C(5)));
}
#[test]
fn clone_entity_with_dynamic_components() {
const COMPONENT_SIZE: usize = 10;
fn test_handler(_world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
// SAFETY: this handler is only going to be used with a component represented by [u8; COMPONENT_SIZE]
unsafe {
ctx.write_target_component_ptr(move |source_ptr, target_ptr| {
core::ptr::copy_nonoverlapping(
source_ptr.as_ptr(),
target_ptr.as_ptr(),
COMPONENT_SIZE,
);
true
});
}
}
let mut world = World::default();
let layout = Layout::array::<u8>(COMPONENT_SIZE).unwrap();
// SAFETY:
// - No drop command is required
// - The component will store [u8; COMPONENT_SIZE], which is Send + Sync
let descriptor = unsafe {
ComponentDescriptor::new_with_layout(
"DynamicComp",
StorageType::Table,
layout,
None,
true,
)
};
let component_id = world.register_component_with_descriptor(descriptor);
let handlers = world.get_component_clone_handlers_mut();
handlers.set_component_handler(
component_id,
ComponentCloneHandler::custom_handler(test_handler),
);
let mut entity = world.spawn_empty();
let data = [5u8; COMPONENT_SIZE];
// SAFETY:
// - ptr points to data represented by component_id ([u8; COMPONENT_SIZE])
// - component_id is from the same world as entity
OwningPtr::make(data, |ptr| unsafe {
entity.insert_by_id(component_id, ptr);
});
let entity = entity.id();
let entity_clone = world.spawn_empty().id();
let builder = EntityCloneBuilder::new(&mut world);
builder.clone_entity(entity, entity_clone);
let ptr = world.get_by_id(entity, component_id).unwrap();
let clone_ptr = world.get_by_id(entity_clone, component_id).unwrap();
// SAFETY: ptr and clone_ptr store component represented by [u8; COMPONENT_SIZE]
unsafe {
assert_eq!(
core::slice::from_raw_parts(ptr.as_ptr(), COMPONENT_SIZE),
core::slice::from_raw_parts(clone_ptr.as_ptr(), COMPONENT_SIZE),
);
}
}
}

View File

@ -1,6 +1,6 @@
use crate::{
component::{Component, ComponentCloneHandler, ComponentHooks, Mutable, StorageType},
entity::{Entity, EntityCloneBuilder, EntityCloner},
entity::{ComponentCloneCtx, Entity, EntityCloneBuilder},
observer::ObserverState,
world::{DeferredWorld, World},
};
@ -44,7 +44,7 @@ impl Component for ObservedBy {
}
fn get_component_clone_handler() -> ComponentCloneHandler {
ComponentCloneHandler::Ignore
ComponentCloneHandler::ignore()
}
}
@ -57,18 +57,18 @@ pub trait CloneEntityWithObserversExt {
impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> {
fn add_observers(&mut self, add_observers: bool) -> &mut Self {
if add_observers {
self.override_component_clone_handler::<ObservedBy>(ComponentCloneHandler::Custom(
component_clone_observed_by,
))
self.override_component_clone_handler::<ObservedBy>(
ComponentCloneHandler::custom_handler(component_clone_observed_by),
)
} else {
self.remove_component_clone_handler_override::<ObservedBy>()
}
}
}
fn component_clone_observed_by(world: &mut DeferredWorld, entity_cloner: &EntityCloner) {
let target = entity_cloner.target();
let source = entity_cloner.source();
fn component_clone_observed_by(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
let target = ctx.target();
let source = ctx.source();
world.commands().queue(move |world: &mut World| {
let observed_by = world

View File

@ -3155,12 +3155,12 @@ impl World {
/// ```
/// # use bevy_ecs::prelude::*;
/// use bevy_ecs::component::{ComponentId, ComponentCloneHandler};
/// use bevy_ecs::entity::EntityCloner;
/// use bevy_ecs::entity::ComponentCloneCtx;
/// use bevy_ecs::world::DeferredWorld;
///
/// fn custom_clone_handler(
/// _world: &mut DeferredWorld,
/// _entity_cloner: &EntityCloner,
/// _ctx: &mut ComponentCloneCtx,
/// ) {
/// // Custom cloning logic for component
/// }
@ -3173,7 +3173,7 @@ impl World {
/// let component_id = world.register_component::<ComponentA>();
///
/// world.get_component_clone_handlers_mut()
/// .set_component_handler(component_id, ComponentCloneHandler::Custom(custom_clone_handler))
/// .set_component_handler(component_id, ComponentCloneHandler::custom_handler(custom_clone_handler))
/// ```
pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers {
self.components.get_component_clone_handlers_mut()

View File

@ -45,7 +45,7 @@ impl Component for Children {
type Mutability = Mutable;
fn get_component_clone_handler() -> ComponentCloneHandler {
ComponentCloneHandler::Ignore
ComponentCloneHandler::ignore()
}
}

View File

@ -45,7 +45,7 @@ impl Component for Parent {
type Mutability = Mutable;
fn get_component_clone_handler() -> ComponentCloneHandler {
ComponentCloneHandler::Ignore
ComponentCloneHandler::ignore()
}
}

View File

@ -4,7 +4,7 @@ use crate::{
};
use bevy_ecs::{
component::ComponentCloneHandler,
entity::{Entity, EntityCloneBuilder, EntityCloner},
entity::{ComponentCloneCtx, Entity, EntityCloneBuilder},
system::EntityCommands,
world::{Command, DeferredWorld, EntityWorldMut, World},
};
@ -214,16 +214,16 @@ pub trait CloneEntityHierarchyExt {
impl CloneEntityHierarchyExt for EntityCloneBuilder<'_> {
fn recursive(&mut self, recursive: bool) -> &mut Self {
if recursive {
self.override_component_clone_handler::<Children>(ComponentCloneHandler::Custom(
component_clone_children,
))
self.override_component_clone_handler::<Children>(
ComponentCloneHandler::custom_handler(component_clone_children),
)
} else {
self.remove_component_clone_handler_override::<Children>()
}
}
fn as_child(&mut self, as_child: bool) -> &mut Self {
if as_child {
self.override_component_clone_handler::<Parent>(ComponentCloneHandler::Custom(
self.override_component_clone_handler::<Parent>(ComponentCloneHandler::custom_handler(
component_clone_parent,
))
} else {
@ -233,34 +233,31 @@ impl CloneEntityHierarchyExt for EntityCloneBuilder<'_> {
}
/// 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::<Children>(entity_cloner.source())
fn component_clone_children(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
let children = ctx
.read_source_component::<Children>()
.expect("Source entity must have Children component")
.iter()
.cloned()
.collect::<Vec<_>>();
let parent = entity_cloner.target();
.iter();
let parent = ctx.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);
let mut clone_entity = ctx
.entity_cloner()
.with_source_and_target(*child, child_clone);
world.commands().queue(move |world: &mut World| {
entity_cloner.clone_entity(world);
clone_entity.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::<Parent>(entity_cloner.source())
fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) {
let parent = ctx
.read_source_component::<Parent>()
.map(|p| p.0)
.expect("Source entity must have Parent component");
world
.commands()
.entity(entity_cloner.target())
.set_parent(parent);
world.commands().entity(ctx.target()).set_parent(parent);
}
#[cfg(test)]