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:
parent
554bc7b0d4
commit
3caca428c0
@ -293,8 +293,14 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
.then_some(quote! { #bevy_ecs_path::component::Immutable })
|
.then_some(quote! { #bevy_ecs_path::component::Immutable })
|
||||||
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
|
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
|
||||||
|
|
||||||
let clone_behavior = if relationship_target.is_some() {
|
let clone_behavior = if relationship_target.is_some() || relationship.is_some() {
|
||||||
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
|
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 {
|
} else if let Some(behavior) = attrs.clone_behavior {
|
||||||
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
|
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,6 +22,7 @@ pub(crate) struct BundleRemover<'w> {
|
|||||||
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
|
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
|
||||||
old_archetype: NonNull<Archetype>,
|
old_archetype: NonNull<Archetype>,
|
||||||
new_archetype: NonNull<Archetype>,
|
new_archetype: NonNull<Archetype>,
|
||||||
|
pub(crate) relationship_hook_mode: RelationshipHookMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> BundleRemover<'w> {
|
impl<'w> BundleRemover<'w> {
|
||||||
@ -97,6 +98,7 @@ impl<'w> BundleRemover<'w> {
|
|||||||
old_archetype: old_archetype.into(),
|
old_archetype: old_archetype.into(),
|
||||||
old_and_new_table: tables,
|
old_and_new_table: tables,
|
||||||
world: world.as_unsafe_world_cell(),
|
world: world.as_unsafe_world_cell(),
|
||||||
|
relationship_hook_mode: RelationshipHookMode::Run,
|
||||||
};
|
};
|
||||||
if is_new_created {
|
if is_new_created {
|
||||||
remover
|
remover
|
||||||
@ -160,7 +162,7 @@ impl<'w> BundleRemover<'w> {
|
|||||||
entity,
|
entity,
|
||||||
bundle_components_in_archetype(),
|
bundle_components_in_archetype(),
|
||||||
caller,
|
caller,
|
||||||
RelationshipHookMode::Run,
|
self.relationship_hook_mode,
|
||||||
);
|
);
|
||||||
if self.old_archetype.as_ref().has_remove_observer() {
|
if self.old_archetype.as_ref().has_remove_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
|
@ -3,16 +3,16 @@ use core::marker::PhantomData;
|
|||||||
use crate::component::Component;
|
use crate::component::Component;
|
||||||
use crate::entity::{ComponentCloneCtx, SourceComponent};
|
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);
|
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)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum ComponentCloneBehavior {
|
pub enum ComponentCloneBehavior {
|
||||||
/// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`])
|
/// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`])
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
/// Do not clone this component.
|
/// Do not clone/move this component.
|
||||||
Ignore,
|
Ignore,
|
||||||
/// Uses a custom [`ComponentCloneFn`].
|
/// Uses a custom [`ComponentCloneFn`].
|
||||||
Custom(ComponentCloneFn),
|
Custom(ComponentCloneFn),
|
||||||
|
@ -8,7 +8,8 @@ use derive_more::derive::From;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::Archetype,
|
archetype::Archetype,
|
||||||
bundle::{Bundle, BundleId, InsertMode},
|
bundle::{Bundle, BundleId, BundleRemover, InsertMode},
|
||||||
|
change_detection::MaybeLocation,
|
||||||
component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo},
|
component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo},
|
||||||
entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper},
|
entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper},
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
@ -76,6 +77,7 @@ impl<'a> SourceComponent<'a> {
|
|||||||
pub struct ComponentCloneCtx<'a, 'b> {
|
pub struct ComponentCloneCtx<'a, 'b> {
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
target_component_written: bool,
|
target_component_written: bool,
|
||||||
|
target_component_moved: bool,
|
||||||
bundle_scratch: &'a mut BundleScratch<'b>,
|
bundle_scratch: &'a mut BundleScratch<'b>,
|
||||||
bundle_scratch_allocator: &'b Bump,
|
bundle_scratch_allocator: &'b Bump,
|
||||||
entities: &'a Entities,
|
entities: &'a Entities,
|
||||||
@ -117,6 +119,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
target,
|
target,
|
||||||
bundle_scratch,
|
bundle_scratch,
|
||||||
target_component_written: false,
|
target_component_written: false,
|
||||||
|
target_component_moved: false,
|
||||||
bundle_scratch_allocator,
|
bundle_scratch_allocator,
|
||||||
entities,
|
entities,
|
||||||
mapper,
|
mapper,
|
||||||
@ -131,6 +134,11 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
self.target_component_written
|
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.
|
/// Returns the current source entity.
|
||||||
pub fn source(&self) -> Entity {
|
pub fn source(&self) -> Entity {
|
||||||
self.source
|
self.source
|
||||||
@ -284,6 +292,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
) {
|
) {
|
||||||
self.state.deferred_commands.push_back(Box::new(deferred));
|
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`]/
|
/// 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`]
|
/// 2. component-defined handler using [`Component::clone_behavior`]
|
||||||
/// 3. default handler override using [`EntityClonerBuilder::with_default_clone_fn`].
|
/// 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.
|
/// 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)]
|
#[derive(Default)]
|
||||||
pub struct EntityCloner {
|
pub struct EntityCloner {
|
||||||
filter: EntityClonerFilter,
|
filter: EntityClonerFilter,
|
||||||
@ -391,6 +418,7 @@ impl<'a> BundleScratch<'a> {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// All [`ComponentId`] values in this instance must come from `world`.
|
/// All [`ComponentId`] values in this instance must come from `world`.
|
||||||
|
#[track_caller]
|
||||||
pub(crate) unsafe fn write(
|
pub(crate) unsafe fn write(
|
||||||
self,
|
self,
|
||||||
world: &mut World,
|
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.
|
/// 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(
|
fn clone_entity_internal(
|
||||||
state: &mut EntityClonerState,
|
state: &mut EntityClonerState,
|
||||||
filter: &mut impl CloneByFilter,
|
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`.
|
// PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`.
|
||||||
let bundle_scratch_allocator = Bump::new();
|
let bundle_scratch_allocator = Bump::new();
|
||||||
let mut bundle_scratch: BundleScratch;
|
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 world = world.as_unsafe_world_cell();
|
||||||
let source_entity = world.get_entity(source).expect("Source entity must exist");
|
let source_entity = world.get_entity(source).expect("Source entity must exist");
|
||||||
@ -564,14 +595,26 @@ impl EntityCloner {
|
|||||||
.archetype()
|
.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| {
|
filter.clone_components(source_archetype, target_archetype, |component| {
|
||||||
let handler = match state.clone_behavior_overrides.get(&component) {
|
let handler = match state.clone_behavior_overrides.get(&component).or_else(|| {
|
||||||
Some(clone_behavior) => clone_behavior.resolve(state.default_clone_fn),
|
world
|
||||||
None => world
|
|
||||||
.components()
|
.components()
|
||||||
.get_info(component)
|
.get_info(component)
|
||||||
.map(|info| info.clone_behavior().resolve(state.default_clone_fn))
|
.map(ComponentInfo::clone_behavior)
|
||||||
.unwrap_or(state.default_clone_fn),
|
}) {
|
||||||
|
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.
|
// SAFETY: This component exists because it is present on the archetype.
|
||||||
@ -607,6 +650,19 @@ impl EntityCloner {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(handler)(&source_component, &mut ctx);
|
(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 {
|
if state.move_components {
|
||||||
world
|
let mut source_entity = world.entity_mut(source);
|
||||||
.entity_mut(source)
|
|
||||||
.remove_by_ids(&bundle_scratch.component_ids);
|
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:
|
// SAFETY:
|
||||||
@ -688,6 +802,8 @@ impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the default clone function to use.
|
/// 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 {
|
pub fn with_default_clone_fn(&mut self, clone_fn: ComponentCloneFn) -> &mut Self {
|
||||||
self.state.default_clone_fn = clone_fn;
|
self.state.default_clone_fn = clone_fn;
|
||||||
self
|
self
|
||||||
@ -700,6 +816,8 @@ impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> {
|
|||||||
///
|
///
|
||||||
/// The setting only applies to components that are allowed through the filter
|
/// The setting only applies to components that are allowed through the filter
|
||||||
/// at the time [`EntityClonerBuilder::clone_entity`] is called.
|
/// 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 {
|
pub fn move_components(&mut self, enable: bool) -> &mut Self {
|
||||||
self.state.move_components = enable;
|
self.state.move_components = enable;
|
||||||
self
|
self
|
||||||
@ -1333,8 +1451,9 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentDescriptor, StorageType},
|
component::{ComponentDescriptor, StorageType},
|
||||||
|
lifecycle::HookContext,
|
||||||
prelude::{ChildOf, Children, Resource},
|
prelude::{ChildOf, Children, Resource},
|
||||||
world::{FromWorld, World},
|
world::{DeferredWorld, FromWorld, World},
|
||||||
};
|
};
|
||||||
use bevy_ptr::OwningPtr;
|
use bevy_ptr::OwningPtr;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
@ -2030,10 +2149,21 @@ mod tests {
|
|||||||
|
|
||||||
assert!(root_children.iter().all(|e| *e != child1 && *e != child2));
|
assert!(root_children.iter().all(|e| *e != child1 && *e != child2));
|
||||||
assert_eq!(root_children.len(), 2);
|
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();
|
let child1_children = world.entity(root_children[0]).get::<Children>().unwrap();
|
||||||
assert_eq!(child1_children.len(), 1);
|
assert_eq!(child1_children.len(), 1);
|
||||||
assert_ne!(child1_children[0], grandchild);
|
assert_ne!(child1_children[0], grandchild);
|
||||||
assert!(world.entity(root_children[1]).get::<Children>().is_none());
|
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!(
|
assert_eq!(
|
||||||
world.entity(root).get::<Children>().unwrap().deref(),
|
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::<A>(), Some(&A));
|
||||||
assert_eq!(world.entity(e_clone).get::<B>(), Some(&B(1)));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ mod related_methods;
|
|||||||
mod relationship_query;
|
mod relationship_query;
|
||||||
mod relationship_source_collection;
|
mod relationship_source_collection;
|
||||||
|
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use alloc::format;
|
use alloc::format;
|
||||||
|
|
||||||
use bevy_utils::prelude::DebugName;
|
use bevy_utils::prelude::DebugName;
|
||||||
@ -12,8 +14,8 @@ pub use relationship_query::*;
|
|||||||
pub use relationship_source_collection::*;
|
pub use relationship_source_collection::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, Mutable},
|
component::{Component, ComponentCloneBehavior, Mutable},
|
||||||
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
entity::{ComponentCloneCtx, Entity},
|
||||||
error::CommandWithEntity,
|
error::CommandWithEntity,
|
||||||
lifecycle::HookContext,
|
lifecycle::HookContext,
|
||||||
world::{DeferredWorld, EntityWorldMut},
|
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.
|
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||||
// note: think of this as "on_drop"
|
// 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 (entities, mut commands) = world.entities_and_commands();
|
||||||
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
||||||
for source_entity in relationship_target.iter() {
|
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
|
/// The "clone behavior" for [`RelationshipTarget`]. The [`RelationshipTarget`] will be populated with the proper components
|
||||||
/// [`RelationshipTarget`] instance with space reserved for the number of targets in the
|
|
||||||
/// original instance. The [`RelationshipTarget`] will then be populated with the proper components
|
|
||||||
/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities
|
/// 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!
|
/// 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
|
/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured
|
||||||
/// to spawn recursively.
|
/// to spawn recursively.
|
||||||
pub fn clone_relationship_target<T: RelationshipTarget>(
|
pub fn clone_relationship_target<T: RelationshipTarget>(
|
||||||
source: &SourceComponent,
|
component: &T,
|
||||||
|
cloned: &mut T,
|
||||||
context: &mut ComponentCloneCtx,
|
context: &mut ComponentCloneCtx,
|
||||||
) {
|
) {
|
||||||
if let Some(component) = source.read::<T>() {
|
if context.linked_cloning() && T::LINKED_SPAWN {
|
||||||
let mut cloned = T::with_capacity(component.len());
|
let collection = cloned.collection_mut_risky();
|
||||||
if context.linked_cloning() && T::LINKED_SPAWN {
|
for entity in component.iter() {
|
||||||
let collection = cloned.collection_mut_risky();
|
collection.add(entity);
|
||||||
for entity in component.iter() {
|
context.queue_entity_clone(entity);
|
||||||
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,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::prelude::{ChildOf, Children};
|
use crate::prelude::{ChildOf, Children};
|
||||||
|
@ -310,8 +310,20 @@ pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An [`EntityCommand`] that clones the specified components of an entity
|
/// An [`EntityCommand`] moves the specified components of this entity into another entity.
|
||||||
/// and inserts them into another entity, then removes them from the original 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 {
|
pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand {
|
||||||
move |mut entity: EntityWorldMut| {
|
move |mut entity: EntityWorldMut| {
|
||||||
entity.move_components::<B>(target);
|
entity.move_components::<B>(target);
|
||||||
|
@ -2234,15 +2234,20 @@ impl<'a> EntityCommands<'a> {
|
|||||||
self.queue(entity_command::clone_components::<B>(target))
|
self.queue(entity_command::clone_components::<B>(target))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clones the specified components of this entity and inserts them into another entity,
|
/// Moves the specified components of this entity into another entity.
|
||||||
/// then removes the components from this entity.
|
|
||||||
///
|
///
|
||||||
/// Components can only be cloned if they implement
|
/// Components with [`Ignore`] clone behavior will not be moved, while components that
|
||||||
/// [`Clone`] or [`Reflect`](bevy_reflect::Reflect).
|
/// 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
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// The command will panic when applied if the target entity does not exist.
|
/// 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 {
|
pub fn move_components<B: Bundle>(&mut self, target: Entity) -> &mut Self {
|
||||||
self.queue(entity_command::move_components::<B>(target))
|
self.queue(entity_command::move_components::<B>(target))
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ use crate::{
|
|||||||
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData},
|
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData},
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
|
storage::{SparseSets, Table},
|
||||||
system::IntoObserverSystem,
|
system::IntoObserverSystem,
|
||||||
world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World},
|
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.
|
/// entity has been despawned while this `EntityWorldMut` is still alive.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self {
|
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 location = self.location();
|
||||||
let components = &mut self.world.components;
|
let components = &mut self.world.components;
|
||||||
|
|
||||||
@ -2279,16 +2301,9 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
}) else {
|
}) else {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
remover.relationship_hook_mode = relationship_hook_mode;
|
||||||
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
let new_location = unsafe {
|
let new_location = unsafe { remover.remove(self.entity, location, caller, pre_remove) }.0;
|
||||||
remover.remove(
|
|
||||||
self.entity,
|
|
||||||
location,
|
|
||||||
MaybeLocation::caller(),
|
|
||||||
BundleRemover::empty_pre_remove,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.0;
|
|
||||||
|
|
||||||
self.location = Some(new_location);
|
self.location = Some(new_location);
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
|
Loading…
Reference in New Issue
Block a user