allow EntityCloner to move components without Clone or Reflect (#20065)

# Objective
Fix #18079 

## Solution
- `EntityCloner` can now move components that don't have `Clone` or
`Reflect` implementation.
- Components with `ComponentCloneBehavior::Ignore` will not be moved.
- Components with `ComponentCloneBehavior::Custom` will be cloned using
their defined `ComponentCloneFn` and then removed from the source entity
to respect their `queue_deferred` logic.
- Relationships still need to be `Clone` or `Reflect` to be movable.
- Custom relationship data is now correctly preserved when cloned or
moved using `EntityCloner`.

## Testing
- Added new tests for moving components

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
eugineerd 2025-07-15 01:33:01 +03:00 committed by GitHub
parent 554bc7b0d4
commit 3caca428c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 756 additions and 47 deletions

View File

@ -293,8 +293,14 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
.then_some(quote! { #bevy_ecs_path::component::Immutable })
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
let clone_behavior = if relationship_target.is_some() {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
let clone_behavior = if relationship_target.is_some() || relationship.is_some() {
quote!(
use #bevy_ecs_path::relationship::{
RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,
RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy
};
(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
)
} else if let Some(behavior) = attrs.clone_behavior {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
} else {

View File

@ -22,6 +22,7 @@ pub(crate) struct BundleRemover<'w> {
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
old_archetype: NonNull<Archetype>,
new_archetype: NonNull<Archetype>,
pub(crate) relationship_hook_mode: RelationshipHookMode,
}
impl<'w> BundleRemover<'w> {
@ -97,6 +98,7 @@ impl<'w> BundleRemover<'w> {
old_archetype: old_archetype.into(),
old_and_new_table: tables,
world: world.as_unsafe_world_cell(),
relationship_hook_mode: RelationshipHookMode::Run,
};
if is_new_created {
remover
@ -160,7 +162,7 @@ impl<'w> BundleRemover<'w> {
entity,
bundle_components_in_archetype(),
caller,
RelationshipHookMode::Run,
self.relationship_hook_mode,
);
if self.old_archetype.as_ref().has_remove_observer() {
deferred_world.trigger_observers(

View File

@ -3,16 +3,16 @@ use core::marker::PhantomData;
use crate::component::Component;
use crate::entity::{ComponentCloneCtx, SourceComponent};
/// Function type that can be used to clone an entity.
/// Function type that can be used to clone a component of an entity.
pub type ComponentCloneFn = fn(&SourceComponent, &mut ComponentCloneCtx);
/// The clone behavior to use when cloning a [`Component`].
/// The clone behavior to use when cloning or moving a [`Component`].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum ComponentCloneBehavior {
/// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`])
#[default]
Default,
/// Do not clone this component.
/// Do not clone/move this component.
Ignore,
/// Uses a custom [`ComponentCloneFn`].
Custom(ComponentCloneFn),

View File

@ -8,7 +8,8 @@ use derive_more::derive::From;
use crate::{
archetype::Archetype,
bundle::{Bundle, BundleId, InsertMode},
bundle::{Bundle, BundleId, BundleRemover, InsertMode},
change_detection::MaybeLocation,
component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo},
entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper},
query::DebugCheckedUnwrap,
@ -76,6 +77,7 @@ impl<'a> SourceComponent<'a> {
pub struct ComponentCloneCtx<'a, 'b> {
component_id: ComponentId,
target_component_written: bool,
target_component_moved: bool,
bundle_scratch: &'a mut BundleScratch<'b>,
bundle_scratch_allocator: &'b Bump,
entities: &'a Entities,
@ -117,6 +119,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
target,
bundle_scratch,
target_component_written: false,
target_component_moved: false,
bundle_scratch_allocator,
entities,
mapper,
@ -131,6 +134,11 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
self.target_component_written
}
/// Returns `true` if used in moving context
pub fn moving(&self) -> bool {
self.state.move_components
}
/// Returns the current source entity.
pub fn source(&self) -> Entity {
self.source
@ -284,6 +292,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
) {
self.state.deferred_commands.push_back(Box::new(deferred));
}
/// Marks component as moved and it's `drop` won't run.
fn move_component(&mut self) {
self.target_component_moved = true;
self.target_component_written = true;
}
}
/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build_opt_out`]/
@ -340,6 +354,19 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// 2. component-defined handler using [`Component::clone_behavior`]
/// 3. default handler override using [`EntityClonerBuilder::with_default_clone_fn`].
/// 4. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not.
///
/// # Moving components
/// [`EntityCloner`] can be configured to move components instead of cloning them by using [`EntityClonerBuilder::move_components`].
/// In this mode components will be moved - removed from source entity and added to the target entity.
///
/// Components with [`ComponentCloneBehavior::Ignore`] clone behavior will not be moved, while components that
/// have a [`ComponentCloneBehavior::Custom`] clone behavior will be cloned using it and then removed from the source entity.
/// All other components will be bitwise copied from the source entity onto the target entity and then removed without dropping.
///
/// Choosing to move components instead of cloning makes [`EntityClonerBuilder::with_default_clone_fn`] ineffective since it's replaced by
/// move handler for components that have [`ComponentCloneBehavior::Default`] clone behavior.
///
/// Note that moving components still triggers `on_remove` hooks/observers on source entity and `on_insert`/`on_add` hooks/observers on the target entity.
#[derive(Default)]
pub struct EntityCloner {
filter: EntityClonerFilter,
@ -391,6 +418,7 @@ impl<'a> BundleScratch<'a> {
///
/// # Safety
/// All [`ComponentId`] values in this instance must come from `world`.
#[track_caller]
pub(crate) unsafe fn write(
self,
world: &mut World,
@ -527,6 +555,7 @@ impl EntityCloner {
}
/// Clones and inserts components from the `source` entity into the entity mapped by `mapper` from `source` using the stored configuration.
#[track_caller]
fn clone_entity_internal(
state: &mut EntityClonerState,
filter: &mut impl CloneByFilter,
@ -539,6 +568,8 @@ impl EntityCloner {
// PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`.
let bundle_scratch_allocator = Bump::new();
let mut bundle_scratch: BundleScratch;
let mut moved_components: Vec<ComponentId> = Vec::new();
let mut deferred_cloned_component_ids: Vec<ComponentId> = Vec::new();
{
let world = world.as_unsafe_world_cell();
let source_entity = world.get_entity(source).expect("Source entity must exist");
@ -564,14 +595,26 @@ impl EntityCloner {
.archetype()
});
if state.move_components {
moved_components.reserve(source_archetype.component_count());
// Replace default handler with special handler which would track if component was moved instead of cloned.
// This is later used to determine whether we need to run component's drop function when removing it from the source entity or not.
state.default_clone_fn = |_, ctx| ctx.move_component();
}
filter.clone_components(source_archetype, target_archetype, |component| {
let handler = match state.clone_behavior_overrides.get(&component) {
Some(clone_behavior) => clone_behavior.resolve(state.default_clone_fn),
None => world
let handler = match state.clone_behavior_overrides.get(&component).or_else(|| {
world
.components()
.get_info(component)
.map(|info| info.clone_behavior().resolve(state.default_clone_fn))
.unwrap_or(state.default_clone_fn),
.map(ComponentInfo::clone_behavior)
}) {
Some(behavior) => match behavior {
ComponentCloneBehavior::Default => state.default_clone_fn,
ComponentCloneBehavior::Ignore => return,
ComponentCloneBehavior::Custom(custom) => *custom,
},
None => state.default_clone_fn,
};
// SAFETY: This component exists because it is present on the archetype.
@ -607,6 +650,19 @@ impl EntityCloner {
};
(handler)(&source_component, &mut ctx);
if ctx.state.move_components {
if ctx.target_component_moved {
moved_components.push(component);
}
// Component wasn't written by the clone handler, so assume it's going to be
// cloned/processed using deferred_commands instead.
// This means that it's ComponentId won't be present in BundleScratch's component_ids,
// but it should still be removed when move_components is true.
else if !ctx.target_component_written() {
deferred_cloned_component_ids.push(component);
}
}
});
}
@ -621,9 +677,67 @@ impl EntityCloner {
}
if state.move_components {
world
.entity_mut(source)
.remove_by_ids(&bundle_scratch.component_ids);
let mut source_entity = world.entity_mut(source);
let cloned_components = if deferred_cloned_component_ids.is_empty() {
&bundle_scratch.component_ids
} else {
// Remove all cloned components with drop by concatenating both vectors
deferred_cloned_component_ids.extend(&bundle_scratch.component_ids);
&deferred_cloned_component_ids
};
source_entity.remove_by_ids_with_caller(
cloned_components,
MaybeLocation::caller(),
RelationshipHookMode::RunIfNotLinked,
BundleRemover::empty_pre_remove,
);
let table_row = source_entity.location().table_row;
// Copy moved components and then forget them without calling drop
source_entity.remove_by_ids_with_caller(
&moved_components,
MaybeLocation::caller(),
RelationshipHookMode::RunIfNotLinked,
|sparse_sets, mut table, components, bundle| {
for &component_id in bundle {
let Some(component_ptr) = sparse_sets
.get(component_id)
.and_then(|component| component.get(source))
.or_else(|| {
// SAFETY: table_row is within this table because we just got it from entity's current location
table.as_mut().and_then(|table| unsafe {
table.get_component(component_id, table_row)
})
})
else {
// Component was removed by some other component's clone side effect before we got to it.
continue;
};
// SAFETY: component_id is valid because remove_by_ids_with_caller checked it before calling this closure
let info = unsafe { components.get_info_unchecked(component_id) };
let layout = info.layout();
let target_ptr = bundle_scratch_allocator.alloc_layout(layout);
// SAFETY:
// - component_ptr points to data with component layout
// - target_ptr was just allocated with component layout
// - component_ptr and target_ptr don't overlap
// - component_ptr matches component_id
unsafe {
core::ptr::copy_nonoverlapping(
component_ptr.as_ptr(),
target_ptr.as_ptr(),
layout.size(),
);
bundle_scratch.push_ptr(component_id, PtrMut::new(target_ptr));
}
}
(/* should drop? */ false, ())
},
);
}
// SAFETY:
@ -688,6 +802,8 @@ impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> {
}
/// Sets the default clone function to use.
///
/// Will be overridden if [`EntityClonerBuilder::move_components`] is enabled.
pub fn with_default_clone_fn(&mut self, clone_fn: ComponentCloneFn) -> &mut Self {
self.state.default_clone_fn = clone_fn;
self
@ -700,6 +816,8 @@ impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> {
///
/// The setting only applies to components that are allowed through the filter
/// at the time [`EntityClonerBuilder::clone_entity`] is called.
///
/// Enabling this overrides any custom function set with [`EntityClonerBuilder::with_default_clone_fn`].
pub fn move_components(&mut self, enable: bool) -> &mut Self {
self.state.move_components = enable;
self
@ -1333,8 +1451,9 @@ mod tests {
use super::*;
use crate::{
component::{ComponentDescriptor, StorageType},
lifecycle::HookContext,
prelude::{ChildOf, Children, Resource},
world::{FromWorld, World},
world::{DeferredWorld, FromWorld, World},
};
use bevy_ptr::OwningPtr;
use core::marker::PhantomData;
@ -2030,10 +2149,21 @@ mod tests {
assert!(root_children.iter().all(|e| *e != child1 && *e != child2));
assert_eq!(root_children.len(), 2);
assert_eq!(
(
world.get::<ChildOf>(root_children[0]),
world.get::<ChildOf>(root_children[1])
),
(Some(&ChildOf(clone_root)), Some(&ChildOf(clone_root)))
);
let child1_children = world.entity(root_children[0]).get::<Children>().unwrap();
assert_eq!(child1_children.len(), 1);
assert_ne!(child1_children[0], grandchild);
assert!(world.entity(root_children[1]).get::<Children>().is_none());
assert_eq!(
world.get::<ChildOf>(child1_children[0]),
Some(&ChildOf(root_children[0]))
);
assert_eq!(
world.entity(root).get::<Children>().unwrap().deref(),
@ -2062,4 +2192,403 @@ mod tests {
assert_eq!(world.entity(e_clone).get::<A>(), Some(&A));
assert_eq!(world.entity(e_clone).get::<B>(), Some(&B(1)));
}
#[test]
fn move_without_clone() {
#[derive(Component, PartialEq, Debug)]
#[component(storage = "SparseSet")]
struct A;
#[derive(Component, PartialEq, Debug)]
struct B(Vec<u8>);
let mut world = World::default();
let e = world.spawn((A, B(alloc::vec![1, 2, 3]))).id();
let e_clone = world.spawn_empty().id();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true);
let mut cloner = builder.finish();
cloner.clone_entity(&mut world, e, e_clone);
assert_eq!(world.get::<A>(e), None);
assert_eq!(world.get::<B>(e), None);
assert_eq!(world.get::<A>(e_clone), Some(&A));
assert_eq!(world.get::<B>(e_clone), Some(&B(alloc::vec![1, 2, 3])));
}
#[test]
fn move_with_remove_hook() {
#[derive(Component, PartialEq, Debug)]
#[component(on_remove=remove_hook)]
struct B(Option<Vec<u8>>);
fn remove_hook(mut world: DeferredWorld, ctx: HookContext) {
world.get_mut::<B>(ctx.entity).unwrap().0.take();
}
let mut world = World::default();
let e = world.spawn(B(Some(alloc::vec![1, 2, 3]))).id();
let e_clone = world.spawn_empty().id();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true);
let mut cloner = builder.finish();
cloner.clone_entity(&mut world, e, e_clone);
assert_eq!(world.get::<B>(e), None);
assert_eq!(world.get::<B>(e_clone), Some(&B(None)));
}
#[test]
fn move_with_deferred() {
#[derive(Component, PartialEq, Debug)]
#[component(clone_behavior=Custom(custom))]
struct A(u32);
#[derive(Component, PartialEq, Debug)]
struct B(u32);
fn custom(_src: &SourceComponent, ctx: &mut ComponentCloneCtx) {
// Clone using deferred
let source = ctx.source();
ctx.queue_deferred(move |world, mapper| {
let target = mapper.get_mapped(source);
world.entity_mut(target).insert(A(10));
});
}
let mut world = World::default();
let e = world.spawn((A(0), B(1))).id();
let e_clone = world.spawn_empty().id();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true);
let mut cloner = builder.finish();
cloner.clone_entity(&mut world, e, e_clone);
assert_eq!(world.get::<A>(e), None);
assert_eq!(world.get::<A>(e_clone), Some(&A(10)));
assert_eq!(world.get::<B>(e), None);
assert_eq!(world.get::<B>(e_clone), Some(&B(1)));
}
#[test]
fn move_relationship() {
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[relationship(relationship_target=Target)]
struct Source(Entity);
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[relationship_target(relationship=Source)]
struct Target(Vec<Entity>);
#[derive(Component, PartialEq, Debug)]
struct A(u32);
let mut world = World::default();
let e_target = world.spawn(A(1)).id();
let e_source = world.spawn((A(2), Source(e_target))).id();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true);
let mut cloner = builder.finish();
let e_source_moved = world.spawn_empty().id();
cloner.clone_entity(&mut world, e_source, e_source_moved);
assert_eq!(world.get::<A>(e_source), None);
assert_eq!(world.get::<A>(e_source_moved), Some(&A(2)));
assert_eq!(world.get::<Source>(e_source), None);
assert_eq!(world.get::<Source>(e_source_moved), Some(&Source(e_target)));
assert_eq!(
world.get::<Target>(e_target),
Some(&Target(alloc::vec![e_source_moved]))
);
let e_target_moved = world.spawn_empty().id();
cloner.clone_entity(&mut world, e_target, e_target_moved);
assert_eq!(world.get::<A>(e_target), None);
assert_eq!(world.get::<A>(e_target_moved), Some(&A(1)));
assert_eq!(world.get::<Target>(e_target), None);
assert_eq!(
world.get::<Target>(e_target_moved),
Some(&Target(alloc::vec![e_source_moved]))
);
assert_eq!(
world.get::<Source>(e_source_moved),
Some(&Source(e_target_moved))
);
}
#[test]
fn move_hierarchy() {
#[derive(Component, PartialEq, Debug)]
struct A(u32);
let mut world = World::default();
let e_parent = world.spawn(A(1)).id();
let e_child1 = world.spawn((A(2), ChildOf(e_parent))).id();
let e_child2 = world.spawn((A(3), ChildOf(e_parent))).id();
let e_child1_1 = world.spawn((A(4), ChildOf(e_child1))).id();
let e_parent_clone = world.spawn_empty().id();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true).linked_cloning(true);
let mut cloner = builder.finish();
cloner.clone_entity(&mut world, e_parent, e_parent_clone);
assert_eq!(world.get::<A>(e_parent), None);
assert_eq!(world.get::<A>(e_child1), None);
assert_eq!(world.get::<A>(e_child2), None);
assert_eq!(world.get::<A>(e_child1_1), None);
let mut children = world.get::<Children>(e_parent_clone).unwrap().iter();
let e_child1_clone = *children.next().unwrap();
let e_child2_clone = *children.next().unwrap();
let mut children = world.get::<Children>(e_child1_clone).unwrap().iter();
let e_child1_1_clone = *children.next().unwrap();
assert_eq!(world.get::<A>(e_parent_clone), Some(&A(1)));
assert_eq!(world.get::<A>(e_child1_clone), Some(&A(2)));
assert_eq!(
world.get::<ChildOf>(e_child1_clone),
Some(&ChildOf(e_parent_clone))
);
assert_eq!(world.get::<A>(e_child2_clone), Some(&A(3)));
assert_eq!(
world.get::<ChildOf>(e_child2_clone),
Some(&ChildOf(e_parent_clone))
);
assert_eq!(world.get::<A>(e_child1_1_clone), Some(&A(4)));
assert_eq!(
world.get::<ChildOf>(e_child1_1_clone),
Some(&ChildOf(e_child1_clone))
);
}
// Original: E1 Target{target: [E2], data: [4,5,6]}
// | E2 Source{target: E1, data: [1,2,3]}
//
// Cloned: E3 Target{target: [], data: [4,5,6]}
#[test]
fn clone_relationship_with_data() {
#[derive(Component, Clone)]
#[relationship(relationship_target=Target)]
struct Source {
#[relationship]
target: Entity,
data: Vec<u8>,
}
#[derive(Component, Clone)]
#[relationship_target(relationship=Source)]
struct Target {
#[relationship]
target: Vec<Entity>,
data: Vec<u8>,
}
let mut world = World::default();
let e_target = world.spawn_empty().id();
let e_source = world
.spawn(Source {
target: e_target,
data: alloc::vec![1, 2, 3],
})
.id();
world.get_mut::<Target>(e_target).unwrap().data = alloc::vec![4, 5, 6];
let builder = EntityCloner::build_opt_out(&mut world);
let mut cloner = builder.finish();
let e_target_clone = world.spawn_empty().id();
cloner.clone_entity(&mut world, e_target, e_target_clone);
let target = world.get::<Target>(e_target).unwrap();
let cloned_target = world.get::<Target>(e_target_clone).unwrap();
assert_eq!(cloned_target.data, target.data);
assert_eq!(target.target, alloc::vec![e_source]);
assert_eq!(cloned_target.target.len(), 0);
let source = world.get::<Source>(e_source).unwrap();
assert_eq!(source.data, alloc::vec![1, 2, 3]);
}
// Original: E1 Target{target: [E2], data: [4,5,6]}
// | E2 Source{target: E1, data: [1,2,3]}
//
// Cloned: E3 Target{target: [E4], data: [4,5,6]}
// | E4 Source{target: E3, data: [1,2,3]}
#[test]
fn clone_linked_relationship_with_data() {
#[derive(Component, Clone)]
#[relationship(relationship_target=Target)]
struct Source {
#[relationship]
target: Entity,
data: Vec<u8>,
}
#[derive(Component, Clone)]
#[relationship_target(relationship=Source, linked_spawn)]
struct Target {
#[relationship]
target: Vec<Entity>,
data: Vec<u8>,
}
let mut world = World::default();
let e_target = world.spawn_empty().id();
let e_source = world
.spawn(Source {
target: e_target,
data: alloc::vec![1, 2, 3],
})
.id();
world.get_mut::<Target>(e_target).unwrap().data = alloc::vec![4, 5, 6];
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.linked_cloning(true);
let mut cloner = builder.finish();
let e_target_clone = world.spawn_empty().id();
cloner.clone_entity(&mut world, e_target, e_target_clone);
let target = world.get::<Target>(e_target).unwrap();
let cloned_target = world.get::<Target>(e_target_clone).unwrap();
assert_eq!(cloned_target.data, target.data);
assert_eq!(target.target, alloc::vec![e_source]);
assert_eq!(cloned_target.target.len(), 1);
let source = world.get::<Source>(e_source).unwrap();
let cloned_source = world.get::<Source>(cloned_target.target[0]).unwrap();
assert_eq!(cloned_source.data, source.data);
assert_eq!(source.target, e_target);
assert_eq!(cloned_source.target, e_target_clone);
}
// Original: E1
// E2
//
// Moved: E3 Target{target: [], data: [4,5,6]}
#[test]
fn move_relationship_with_data() {
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[relationship(relationship_target=Target)]
struct Source {
#[relationship]
target: Entity,
data: Vec<u8>,
}
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[relationship_target(relationship=Source)]
struct Target {
#[relationship]
target: Vec<Entity>,
data: Vec<u8>,
}
let source_data = alloc::vec![1, 2, 3];
let target_data = alloc::vec![4, 5, 6];
let mut world = World::default();
let e_target = world.spawn_empty().id();
let e_source = world
.spawn(Source {
target: e_target,
data: source_data.clone(),
})
.id();
world.get_mut::<Target>(e_target).unwrap().data = target_data.clone();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true);
let mut cloner = builder.finish();
let e_target_moved = world.spawn_empty().id();
cloner.clone_entity(&mut world, e_target, e_target_moved);
assert_eq!(world.get::<Target>(e_target), None);
assert_eq!(
world.get::<Source>(e_source),
Some(&Source {
data: source_data,
target: e_target_moved,
})
);
assert_eq!(
world.get::<Target>(e_target_moved),
Some(&Target {
target: alloc::vec![e_source],
data: target_data
})
);
}
// Original: E1
// E2
//
// Moved: E3 Target{target: [E4], data: [4,5,6]}
// | E4 Source{target: E3, data: [1,2,3]}
#[test]
fn move_linked_relationship_with_data() {
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[relationship(relationship_target=Target)]
struct Source {
#[relationship]
target: Entity,
data: Vec<u8>,
}
#[derive(Component, Clone, PartialEq, Eq, Debug)]
#[relationship_target(relationship=Source, linked_spawn)]
struct Target {
#[relationship]
target: Vec<Entity>,
data: Vec<u8>,
}
let source_data = alloc::vec![1, 2, 3];
let target_data = alloc::vec![4, 5, 6];
let mut world = World::default();
let e_target = world.spawn_empty().id();
let e_source = world
.spawn(Source {
target: e_target,
data: source_data.clone(),
})
.id();
world.get_mut::<Target>(e_target).unwrap().data = target_data.clone();
let mut builder = EntityCloner::build_opt_out(&mut world);
builder.move_components(true).linked_cloning(true);
let mut cloner = builder.finish();
let e_target_moved = world.spawn_empty().id();
cloner.clone_entity(&mut world, e_target, e_target_moved);
assert_eq!(world.get::<Target>(e_target), None);
assert_eq!(world.get::<Source>(e_source), None);
let moved_target = world.get::<Target>(e_target_moved).unwrap();
assert_eq!(moved_target.data, target_data);
assert_eq!(moved_target.target.len(), 1);
let moved_source = world.get::<Source>(moved_target.target[0]).unwrap();
assert_eq!(moved_source.data, source_data);
assert_eq!(moved_source.target, e_target_moved);
}
}

View File

@ -4,6 +4,8 @@ mod related_methods;
mod relationship_query;
mod relationship_source_collection;
use core::marker::PhantomData;
use alloc::format;
use bevy_utils::prelude::DebugName;
@ -12,8 +14,8 @@ pub use relationship_query::*;
pub use relationship_source_collection::*;
use crate::{
component::{Component, Mutable},
entity::{ComponentCloneCtx, Entity, SourceComponent},
component::{Component, ComponentCloneBehavior, Mutable},
entity::{ComponentCloneCtx, Entity},
error::CommandWithEntity,
lifecycle::HookContext,
world::{DeferredWorld, EntityWorldMut},
@ -240,7 +242,19 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
// note: think of this as "on_drop"
fn on_replace(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
fn on_replace(
mut world: DeferredWorld,
HookContext {
entity,
relationship_hook_mode,
..
}: HookContext,
) {
match relationship_hook_mode {
RelationshipHookMode::Run => {}
// For RelationshipTarget we don't want to run this hook even if it isn't linked, but for Relationship we do.
RelationshipHookMode::Skip | RelationshipHookMode::RunIfNotLinked => return,
}
let (entities, mut commands) = world.entities_and_commands();
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
for source_entity in relationship_target.iter() {
@ -287,28 +301,38 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
}
}
/// The "clone behavior" for [`RelationshipTarget`]. This actually creates an empty
/// [`RelationshipTarget`] instance with space reserved for the number of targets in the
/// original instance. The [`RelationshipTarget`] will then be populated with the proper components
/// The "clone behavior" for [`RelationshipTarget`]. The [`RelationshipTarget`] will be populated with the proper components
/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities
/// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that!
///
/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured
/// to spawn recursively.
pub fn clone_relationship_target<T: RelationshipTarget>(
source: &SourceComponent,
component: &T,
cloned: &mut T,
context: &mut ComponentCloneCtx,
) {
if let Some(component) = source.read::<T>() {
let mut cloned = T::with_capacity(component.len());
if context.linked_cloning() && T::LINKED_SPAWN {
let collection = cloned.collection_mut_risky();
for entity in component.iter() {
collection.add(entity);
context.queue_entity_clone(entity);
}
if context.linked_cloning() && T::LINKED_SPAWN {
let collection = cloned.collection_mut_risky();
for entity in component.iter() {
collection.add(entity);
context.queue_entity_clone(entity);
}
} else if context.moving() {
let target = context.target();
let collection = cloned.collection_mut_risky();
for entity in component.iter() {
collection.add(entity);
context.queue_deferred(move |world, _mapper| {
// We don't want relationships hooks to run because we are manually constructing the collection here
_ = DeferredWorld::from(world)
.modify_component_with_relationship_hook_mode::<T::Relationship, ()>(
entity,
RelationshipHookMode::Skip,
|r| r.set_risky(target),
);
});
}
context.write_target_component(cloned);
}
}
@ -323,6 +347,122 @@ pub enum RelationshipHookMode {
Skip,
}
/// Wrapper for components clone specialization using autoderef.
#[doc(hidden)]
pub struct RelationshipCloneBehaviorSpecialization<T>(PhantomData<T>);
impl<T> Default for RelationshipCloneBehaviorSpecialization<T> {
fn default() -> Self {
Self(PhantomData)
}
}
/// Base trait for relationship clone specialization using autoderef.
#[doc(hidden)]
pub trait RelationshipCloneBehaviorBase {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C> RelationshipCloneBehaviorBase for RelationshipCloneBehaviorSpecialization<C> {
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
// Relationships currently must have `Clone`/`Reflect`-based handler for cloning/moving logic to properly work.
ComponentCloneBehavior::Ignore
}
}
/// Specialized trait for relationship clone specialization using autoderef.
#[doc(hidden)]
pub trait RelationshipCloneBehaviorViaReflect {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
#[cfg(feature = "bevy_reflect")]
impl<C: Relationship + bevy_reflect::Reflect> RelationshipCloneBehaviorViaReflect
for &RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::reflect()
}
}
/// Specialized trait for relationship clone specialization using autoderef.
#[doc(hidden)]
pub trait RelationshipCloneBehaviorViaClone {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C: Relationship + Clone> RelationshipCloneBehaviorViaClone
for &&RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::clone::<C>()
}
}
/// Specialized trait for relationship target clone specialization using autoderef.
#[doc(hidden)]
pub trait RelationshipTargetCloneBehaviorViaReflect {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
#[cfg(feature = "bevy_reflect")]
impl<C: RelationshipTarget + bevy_reflect::Reflect + bevy_reflect::TypePath>
RelationshipTargetCloneBehaviorViaReflect for &&&RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Custom(|source, context| {
if let Some(component) = source.read::<C>()
&& let Ok(mut cloned) = component.reflect_clone_and_take::<C>()
{
cloned.collection_mut_risky().clear();
clone_relationship_target(component, &mut cloned, context);
context.write_target_component(cloned);
}
})
}
}
/// Specialized trait for relationship target clone specialization using autoderef.
#[doc(hidden)]
pub trait RelationshipTargetCloneBehaviorViaClone {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C: RelationshipTarget + Clone> RelationshipTargetCloneBehaviorViaClone
for &&&&RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Custom(|source, context| {
if let Some(component) = source.read::<C>() {
let mut cloned = component.clone();
cloned.collection_mut_risky().clear();
clone_relationship_target(component, &mut cloned, context);
context.write_target_component(cloned);
}
})
}
}
/// We know there's no additional data on Children, so this handler is an optimization to avoid cloning the entire Collection.
#[doc(hidden)]
pub trait RelationshipTargetCloneBehaviorHierarchy {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl RelationshipTargetCloneBehaviorHierarchy
for &&&&&RelationshipCloneBehaviorSpecialization<crate::hierarchy::Children>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Custom(|source, context| {
if let Some(component) = source.read::<crate::hierarchy::Children>() {
let mut cloned = crate::hierarchy::Children::with_capacity(component.len());
clone_relationship_target(component, &mut cloned, context);
context.write_target_component(cloned);
}
})
}
}
#[cfg(test)]
mod tests {
use crate::prelude::{ChildOf, Children};

View File

@ -310,8 +310,20 @@ pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand {
}
}
/// An [`EntityCommand`] that clones the specified components of an entity
/// and inserts them into another entity, then removes them from the original entity.
/// An [`EntityCommand`] moves the specified components of this entity into another entity.
///
/// Components with [`Ignore`] clone behavior will not be moved, while components that
/// have a [`Custom`] clone behavior will be cloned using it and then removed from the source entity.
/// All other components will be moved without any other special handling.
///
/// Note that this will trigger `on_remove` hooks/observers on this entity and `on_insert`/`on_add` hooks/observers on the target entity.
///
/// # Panics
///
/// The command will panic when applied if the target entity does not exist.
///
/// [`Ignore`]: crate::component::ComponentCloneBehavior::Ignore
/// [`Custom`]: crate::component::ComponentCloneBehavior::Custom
pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.move_components::<B>(target);

View File

@ -2234,15 +2234,20 @@ impl<'a> EntityCommands<'a> {
self.queue(entity_command::clone_components::<B>(target))
}
/// Clones the specified components of this entity and inserts them into another entity,
/// then removes the components from this entity.
/// Moves the specified components of this entity into another entity.
///
/// Components can only be cloned if they implement
/// [`Clone`] or [`Reflect`](bevy_reflect::Reflect).
/// Components with [`Ignore`] clone behavior will not be moved, while components that
/// have a [`Custom`] clone behavior will be cloned using it and then removed from the source entity.
/// All other components will be moved without any other special handling.
///
/// Note that this will trigger `on_remove` hooks/observers on this entity and `on_insert`/`on_add` hooks/observers on the target entity.
///
/// # Panics
///
/// The command will panic when applied if the target entity does not exist.
///
/// [`Ignore`]: crate::component::ComponentCloneBehavior::Ignore
/// [`Custom`]: crate::component::ComponentCloneBehavior::Custom
pub fn move_components<B: Bundle>(&mut self, target: Entity) -> &mut Self {
self.queue(entity_command::move_components::<B>(target))
}

View File

@ -19,6 +19,7 @@ use crate::{
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData},
relationship::RelationshipHookMode,
resource::Resource,
storage::{SparseSets, Table},
system::IntoObserverSystem,
world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World},
};
@ -2264,6 +2265,27 @@ impl<'w> EntityWorldMut<'w> {
/// entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller]
pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self {
self.remove_by_ids_with_caller(
component_ids,
MaybeLocation::caller(),
RelationshipHookMode::Run,
BundleRemover::empty_pre_remove,
)
}
#[inline]
pub(crate) fn remove_by_ids_with_caller<T: 'static>(
&mut self,
component_ids: &[ComponentId],
caller: MaybeLocation,
relationship_hook_mode: RelationshipHookMode,
pre_remove: impl FnOnce(
&mut SparseSets,
Option<&mut Table>,
&Components,
&[ComponentId],
) -> (bool, T),
) -> &mut Self {
let location = self.location();
let components = &mut self.world.components;
@ -2279,16 +2301,9 @@ impl<'w> EntityWorldMut<'w> {
}) else {
return self;
};
remover.relationship_hook_mode = relationship_hook_mode;
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
let new_location = unsafe {
remover.remove(
self.entity,
location,
MaybeLocation::caller(),
BundleRemover::empty_pre_remove,
)
}
.0;
let new_location = unsafe { remover.remove(self.entity, location, caller, pre_remove) }.0;
self.location = Some(new_location);
self.world.flush();