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:
parent
3ef99cf82c
commit
20049d4c34
@ -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"
|
||||
|
171
benches/benches/bevy_ecs/entity_cloning.rs
Normal file
171
benches/benches/bevy_ecs/entity_cloning.rs
Normal 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);
|
||||
});
|
||||
}
|
@ -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"
|
||||
|
@ -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, ®istry);
|
||||
// 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>()
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -45,7 +45,7 @@ impl Component for Children {
|
||||
type Mutability = Mutable;
|
||||
|
||||
fn get_component_clone_handler() -> ComponentCloneHandler {
|
||||
ComponentCloneHandler::Ignore
|
||||
ComponentCloneHandler::ignore()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ impl Component for Parent {
|
||||
type Mutability = Mutable;
|
||||
|
||||
fn get_component_clone_handler() -> ComponentCloneHandler {
|
||||
ComponentCloneHandler::Ignore
|
||||
ComponentCloneHandler::ignore()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
Loading…
Reference in New Issue
Block a user