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 })
|
||||
.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 {
|
||||
|
@ -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(
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user