diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 0eaae27ce4b00..44ffa1d52b993 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -1,7 +1,7 @@ use core::hint::black_box; use benches::bench; -use bevy_ecs::bundle::Bundle; +use bevy_ecs::bundle::{Bundle, InsertMode}; use bevy_ecs::component::ComponentCloneBehavior; use bevy_ecs::entity::EntityCloner; use bevy_ecs::hierarchy::ChildOf; @@ -17,41 +17,15 @@ criterion_group!( hierarchy_tall, hierarchy_wide, hierarchy_many, + filter ); #[derive(Component, Reflect, Default, Clone)] -struct C1(Mat4); +struct C(Mat4); -#[derive(Component, Reflect, Default, Clone)] -struct C2(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C3(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C4(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C5(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C6(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C7(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C8(Mat4); - -#[derive(Component, Reflect, Default, Clone)] -struct C9(Mat4); +type ComplexBundle = (C<1>, C<2>, C<3>, C<4>, C<5>, C<6>, C<7>, C<8>, C<9>, C<10>); -#[derive(Component, Reflect, Default, Clone)] -struct C10(Mat4); - -type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); - -/// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to +/// Sets the [`ComponentCloneBehavior`] for all explicit and required components in a bundle `B` to /// use the [`Reflect`] trait instead of [`Clone`]. fn reflection_cloner( world: &mut World, @@ -71,7 +45,7 @@ fn reflection_cloner( // this bundle are saved. let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); - let mut builder = EntityCloner::build(world); + let mut builder = EntityCloner::build_opt_out(world); // Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`. for component in component_ids { @@ -82,16 +56,15 @@ fn reflection_cloner( builder.finish() } -/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a -/// bundle `B`. +/// A helper function that benchmarks running [`EntityCloner::spawn_clone`] with a bundle `B`. /// /// The bundle must implement [`Default`], which is used to create the first entity that gets cloned /// in the benchmark. /// -/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler`] for all -/// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect` +/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneBehavior`] for all +/// components (which is usually [`ComponentCloneBehavior::clone()`]). If `clone_via_reflect` /// is true, it will overwrite the handler for all components in the bundle to be -/// [`ComponentCloneHandler::reflect_handler()`]. +/// [`ComponentCloneBehavior::reflect()`]. fn bench_clone( b: &mut Bencher, clone_via_reflect: bool, @@ -114,8 +87,7 @@ fn bench_clone( }); } -/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a -/// bundle `B`. +/// A helper function that benchmarks running [`EntityCloner::spawn_clone`] with a bundle `B`. /// /// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several /// children. It does so by setting up an entity tree with a given `height` where each entity has a @@ -135,7 +107,7 @@ fn bench_clone_hierarchy( let mut cloner = if clone_via_reflect { reflection_cloner::(&mut world, true) } else { - let mut builder = EntityCloner::build(&mut world); + let mut builder = EntityCloner::build_opt_out(&mut world); builder.linked_cloning(true); builder.finish() }; @@ -169,7 +141,7 @@ fn bench_clone_hierarchy( // Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This // constant represents this as an easy array that can be used in a `for` loop. -const SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)]; +const CLONE_SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)]; /// Benchmarks cloning a single entity with 10 components and no children. fn single(c: &mut Criterion) { @@ -178,7 +150,7 @@ fn single(c: &mut Criterion) { // We're cloning 1 entity. group.throughput(Throughput::Elements(1)); - for (id, clone_via_reflect) in SCENARIOS { + for (id, clone_via_reflect) in CLONE_SCENARIOS { group.bench_function(id, |b| { bench_clone::(b, clone_via_reflect); }); @@ -194,9 +166,9 @@ fn hierarchy_tall(c: &mut Criterion) { // We're cloning both the root entity and its 50 descendents. group.throughput(Throughput::Elements(51)); - for (id, clone_via_reflect) in SCENARIOS { + for (id, clone_via_reflect) in CLONE_SCENARIOS { group.bench_function(id, |b| { - bench_clone_hierarchy::(b, 50, 1, clone_via_reflect); + bench_clone_hierarchy::>(b, 50, 1, clone_via_reflect); }); } @@ -210,9 +182,9 @@ fn hierarchy_wide(c: &mut Criterion) { // We're cloning both the root entity and its 50 direct children. group.throughput(Throughput::Elements(51)); - for (id, clone_via_reflect) in SCENARIOS { + for (id, clone_via_reflect) in CLONE_SCENARIOS { group.bench_function(id, |b| { - bench_clone_hierarchy::(b, 1, 50, clone_via_reflect); + bench_clone_hierarchy::>(b, 1, 50, clone_via_reflect); }); } @@ -228,7 +200,7 @@ fn hierarchy_many(c: &mut Criterion) { // of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :) group.throughput(Throughput::Elements(364)); - for (id, clone_via_reflect) in SCENARIOS { + for (id, clone_via_reflect) in CLONE_SCENARIOS { group.bench_function(id, |b| { bench_clone_hierarchy::(b, 5, 3, clone_via_reflect); }); @@ -236,3 +208,157 @@ fn hierarchy_many(c: &mut Criterion) { group.finish(); } + +/// Filter scenario variant for bot opt-in and opt-out filters +#[derive(Clone, Copy)] +#[expect( + clippy::enum_variant_names, + reason = "'Opt' is not understood as an prefix but `OptOut'/'OptIn' are" +)] +enum FilterScenario { + OptOutNone, + OptOutNoneKeep(bool), + OptOutAll, + OptInNone, + OptInAll, + OptInAllWithoutRequired, + OptInAllKeep(bool), + OptInAllKeepWithoutRequired(bool), +} + +impl From for String { + fn from(value: FilterScenario) -> Self { + match value { + FilterScenario::OptOutNone => "opt_out_none", + FilterScenario::OptOutNoneKeep(true) => "opt_out_none_keep_none", + FilterScenario::OptOutNoneKeep(false) => "opt_out_none_keep_all", + FilterScenario::OptOutAll => "opt_out_all", + FilterScenario::OptInNone => "opt_in_none", + FilterScenario::OptInAll => "opt_in_all", + FilterScenario::OptInAllWithoutRequired => "opt_in_all_without_required", + FilterScenario::OptInAllKeep(true) => "opt_in_all_keep_none", + FilterScenario::OptInAllKeep(false) => "opt_in_all_keep_all", + FilterScenario::OptInAllKeepWithoutRequired(true) => { + "opt_in_all_keep_none_without_required" + } + FilterScenario::OptInAllKeepWithoutRequired(false) => { + "opt_in_all_keep_all_without_required" + } + } + .into() + } +} + +/// Common scenarios for different filter to be benchmarked. +const FILTER_SCENARIOS: [FilterScenario; 11] = [ + FilterScenario::OptOutNone, + FilterScenario::OptOutNoneKeep(true), + FilterScenario::OptOutNoneKeep(false), + FilterScenario::OptOutAll, + FilterScenario::OptInNone, + FilterScenario::OptInAll, + FilterScenario::OptInAllWithoutRequired, + FilterScenario::OptInAllKeep(true), + FilterScenario::OptInAllKeep(false), + FilterScenario::OptInAllKeepWithoutRequired(true), + FilterScenario::OptInAllKeepWithoutRequired(false), +]; + +/// A helper function that benchmarks running [`EntityCloner::clone_entity`] with a bundle `B`. +/// +/// The bundle must implement [`Default`], which is used to create the first entity that gets its components cloned +/// in the benchmark. It may also be used to populate the target entity depending on the scenario. +fn bench_filter(b: &mut Bencher, scenario: FilterScenario) { + let mut world = World::default(); + let mut spawn = |empty| match empty { + false => world.spawn(B::default()).id(), + true => world.spawn_empty().id(), + }; + let source = spawn(false); + let (target, mut cloner); + + match scenario { + FilterScenario::OptOutNone => { + target = spawn(true); + cloner = EntityCloner::default(); + } + FilterScenario::OptOutNoneKeep(is_new) => { + target = spawn(is_new); + let mut builder = EntityCloner::build_opt_out(&mut world); + builder.insert_mode(InsertMode::Keep); + cloner = builder.finish(); + } + FilterScenario::OptOutAll => { + target = spawn(true); + let mut builder = EntityCloner::build_opt_out(&mut world); + builder.deny::(); + cloner = builder.finish(); + } + FilterScenario::OptInNone => { + target = spawn(true); + let builder = EntityCloner::build_opt_in(&mut world); + cloner = builder.finish(); + } + FilterScenario::OptInAll => { + target = spawn(true); + let mut builder = EntityCloner::build_opt_in(&mut world); + builder.allow::(); + cloner = builder.finish(); + } + FilterScenario::OptInAllWithoutRequired => { + target = spawn(true); + let mut builder = EntityCloner::build_opt_in(&mut world); + builder.without_required_components(|builder| { + builder.allow::(); + }); + cloner = builder.finish(); + } + FilterScenario::OptInAllKeep(is_new) => { + target = spawn(is_new); + let mut builder = EntityCloner::build_opt_in(&mut world); + builder.allow_if_new::(); + cloner = builder.finish(); + } + FilterScenario::OptInAllKeepWithoutRequired(is_new) => { + target = spawn(is_new); + let mut builder = EntityCloner::build_opt_in(&mut world); + builder.without_required_components(|builder| { + builder.allow_if_new::(); + }); + cloner = builder.finish(); + } + } + + b.iter(|| { + // clones the given entity into the target + cloner.clone_entity(&mut world, black_box(source), black_box(target)); + world.flush(); + }); +} + +/// Benchmarks filtering of cloning a single entity with 5 unclonable components (each requiring 1 unclonable component) into a target. +fn filter(c: &mut Criterion) { + #[derive(Component, Default)] + #[component(clone_behavior = Ignore)] + struct C; + + #[derive(Component, Default)] + #[component(clone_behavior = Ignore)] + #[require(C::)] + struct R; + + type RequiringBundle = (R<1>, R<2>, R<3>, R<4>, R<5>); + + let mut group = c.benchmark_group(bench!("filter")); + + // We're cloning 1 entity into a target. + group.throughput(Throughput::Elements(1)); + + for scenario in FILTER_SCENARIOS { + group.bench_function(scenario, |b| { + bench_filter::(b, scenario); + }); + } + + group.finish(); +} diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 02d2491b7a6c3..08da93c2619a3 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1,13 +1,14 @@ -use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, vec::Vec}; -use bevy_platform::collections::{HashMap, HashSet}; +use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; +use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; use bevy_ptr::{Ptr, PtrMut}; use bevy_utils::prelude::DebugName; use bumpalo::Bump; -use core::any::TypeId; +use core::{any::TypeId, cell::LazyCell, ops::Range}; +use derive_more::derive::From; use crate::{ archetype::Archetype, - bundle::Bundle, + bundle::{Bundle, BundleId, InsertMode}, component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo}, entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper}, query::DebugCheckedUnwrap, @@ -81,7 +82,7 @@ pub struct ComponentCloneCtx<'a, 'b> { source: Entity, target: Entity, component_info: &'a ComponentInfo, - entity_cloner: &'a mut EntityCloner, + state: &'a mut EntityClonerState, mapper: &'a mut dyn EntityMapper, #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, @@ -105,7 +106,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { bundle_scratch: &'a mut BundleScratch<'b>, entities: &'a Entities, component_info: &'a ComponentInfo, - entity_cloner: &'a mut EntityCloner, + entity_cloner: &'a mut EntityClonerState, mapper: &'a mut dyn EntityMapper, #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, #[cfg(not(feature = "bevy_reflect"))] type_registry: Option<&'a ()>, @@ -120,7 +121,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { entities, mapper, component_info, - entity_cloner, + state: entity_cloner, type_registry, } } @@ -155,7 +156,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) will also be cloned. #[inline] pub fn linked_cloning(&self) -> bool { - self.entity_cloner.linked_cloning + self.state.linked_cloning } /// Returns this context's [`EntityMapper`]. @@ -272,7 +273,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { pub fn queue_entity_clone(&mut self, entity: Entity) { let target = self.entities.reserve_entity(); self.mapper.set_mapped(entity, target); - self.entity_cloner.clone_queue.push_back(entity); + self.state.clone_queue.push_back(entity); } /// Queues a deferred clone operation, which will run with exclusive [`World`] access immediately after calling the clone handler for each component on an entity. @@ -281,13 +282,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { &mut self, deferred: impl FnOnce(&mut World, &mut dyn EntityMapper) + 'static, ) { - self.entity_cloner - .deferred_commands - .push_back(Box::new(deferred)); + self.state.deferred_commands.push_back(Box::new(deferred)); } } -/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build`], which +/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build_opt_out`]/ +/// [`opt_in`](EntityCloner::build_opt_in), which /// returns an [`EntityClonerBuilder`]. /// /// After configuration is complete an entity can be cloned using [`Self::clone_entity`]. @@ -308,7 +308,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// let entity = world.spawn(component.clone()).id(); /// let entity_clone = world.spawn_empty().id(); /// -/// EntityCloner::build(&mut world).clone_entity(entity, entity_clone); +/// EntityCloner::build_opt_out(&mut world).clone_entity(entity, entity_clone); /// /// assert!(world.get::(entity_clone).is_some_and(|c| *c == component)); ///``` @@ -340,32 +340,10 @@ 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. +#[derive(Default)] pub struct EntityCloner { - filter_allows_components: bool, - filter: HashSet, - filter_required: HashSet, - clone_behavior_overrides: HashMap, - move_components: bool, - linked_cloning: bool, - default_clone_fn: ComponentCloneFn, - clone_queue: VecDeque, - deferred_commands: VecDeque>, -} - -impl Default for EntityCloner { - fn default() -> Self { - Self { - filter_allows_components: false, - move_components: false, - linked_cloning: false, - default_clone_fn: ComponentCloneBehavior::global_default_fn(), - filter: Default::default(), - filter_required: Default::default(), - clone_behavior_overrides: Default::default(), - clone_queue: Default::default(), - deferred_commands: Default::default(), - } - } + filter: EntityClonerFilter, + state: EntityClonerState, } /// An expandable scratch space for defining a dynamic bundle. @@ -433,12 +411,33 @@ impl<'a> BundleScratch<'a> { } impl EntityCloner { - /// Returns a new [`EntityClonerBuilder`] using the given `world`. - pub fn build(world: &mut World) -> EntityClonerBuilder { + /// Returns a new [`EntityClonerBuilder`] using the given `world` with the [`OptOut`] configuration. + /// + /// This builder tries to clone every component from the source entity except for components that were + /// explicitly denied, for example by using the [`deny`](EntityClonerBuilder::deny) method. + /// + /// Required components are not considered by denied components and must be explicitly denied as well if desired. + pub fn build_opt_out(world: &mut World) -> EntityClonerBuilder { EntityClonerBuilder { world, - attach_required_components: true, - entity_cloner: EntityCloner::default(), + filter: Default::default(), + state: Default::default(), + } + } + + /// Returns a new [`EntityClonerBuilder`] using the given `world` with the [`OptIn`] configuration. + /// + /// This builder tries to clone every component that was explicitly allowed from the source entity, + /// for example by using the [`allow`](EntityClonerBuilder::allow) method. + /// + /// Components allowed to be cloned through this builder would also allow their required components, + /// which will be cloned from the source entity only if the target entity does not contain them already. + /// To skip adding required components see [`without_required_components`](EntityClonerBuilder::without_required_components). + pub fn build_opt_in(world: &mut World) -> EntityClonerBuilder { + EntityClonerBuilder { + world, + filter: Default::default(), + state: Default::default(), } } @@ -446,12 +445,91 @@ impl EntityCloner { /// This will produce "deep" / recursive clones of relationship trees that have "linked spawn". #[inline] pub fn linked_cloning(&self) -> bool { - self.linked_cloning + self.state.linked_cloning + } + + /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. + /// If this [`EntityCloner`] has [`EntityCloner::linked_cloning`], then it will recursively spawn entities as defined + /// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) + #[track_caller] + pub fn clone_entity(&mut self, world: &mut World, source: Entity, target: Entity) { + let mut map = EntityHashMap::::new(); + map.set_mapped(source, target); + self.clone_entity_mapped(world, source, &mut map); + } + + /// Clones and inserts components from the `source` entity into a newly spawned entity using the stored configuration. + /// If this [`EntityCloner`] has [`EntityCloner::linked_cloning`], then it will recursively spawn entities as defined + /// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) + #[track_caller] + pub fn spawn_clone(&mut self, world: &mut World, source: Entity) -> Entity { + let target = world.spawn_empty().id(); + self.clone_entity(world, source, target); + target + } + + /// Clones the entity into whatever entity `mapper` chooses for it. + #[track_caller] + pub fn clone_entity_mapped( + &mut self, + world: &mut World, + source: Entity, + mapper: &mut dyn EntityMapper, + ) -> Entity { + Self::clone_entity_mapped_internal(&mut self.state, &mut self.filter, world, source, mapper) + } + + #[track_caller] + #[inline] + fn clone_entity_mapped_internal( + state: &mut EntityClonerState, + filter: &mut impl CloneByFilter, + world: &mut World, + source: Entity, + mapper: &mut dyn EntityMapper, + ) -> Entity { + // All relationships on the root should have their hooks run + let target = Self::clone_entity_internal( + state, + filter, + world, + source, + mapper, + RelationshipHookMode::Run, + ); + let child_hook_insert_mode = if state.linked_cloning { + // When spawning "linked relationships", we want to ignore hooks for relationships we are spawning, while + // still registering with original relationship targets that are "not linked" to the current recursive spawn. + RelationshipHookMode::RunIfNotLinked + } else { + // If we are not cloning "linked relationships" recursively, then we want any cloned relationship components to + // register themselves with their original relationship target. + RelationshipHookMode::Run + }; + loop { + let queued = state.clone_queue.pop_front(); + if let Some(queued) = queued { + Self::clone_entity_internal( + state, + filter, + world, + queued, + mapper, + child_hook_insert_mode, + ); + } else { + break; + } + } + target } /// Clones and inserts components from the `source` entity into the entity mapped by `mapper` from `source` using the stored configuration. fn clone_entity_internal( - &mut self, + state: &mut EntityClonerState, + filter: &mut impl CloneByFilter, world: &mut World, source: Entity, mapper: &mut dyn EntityMapper, @@ -464,12 +542,6 @@ impl EntityCloner { { let world = world.as_unsafe_world_cell(); let source_entity = world.get_entity(source).expect("Source entity must exist"); - let target_archetype = (!self.filter_required.is_empty()).then(|| { - world - .get_entity(target) - .expect("Target entity must exist") - .archetype() - }); #[cfg(feature = "bevy_reflect")] // SAFETY: we have unique access to `world`, nothing else accesses the registry at this moment, and we clone @@ -482,21 +554,24 @@ impl EntityCloner { #[cfg(not(feature = "bevy_reflect"))] let app_registry = Option::<()>::None; - let archetype = source_entity.archetype(); - bundle_scratch = BundleScratch::with_capacity(archetype.component_count()); + let source_archetype = source_entity.archetype(); + bundle_scratch = BundleScratch::with_capacity(source_archetype.component_count()); - for component in archetype.components() { - if !self.is_cloning_allowed(&component, target_archetype) { - continue; - } + let target_archetype = LazyCell::new(|| { + world + .get_entity(target) + .expect("Target entity must exist") + .archetype() + }); - let handler = match self.clone_behavior_overrides.get(&component) { - Some(clone_behavior) => clone_behavior.resolve(self.default_clone_fn), + 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 .components() .get_info(component) - .map(|info| info.clone_behavior().resolve(self.default_clone_fn)) - .unwrap_or(self.default_clone_fn), + .map(|info| info.clone_behavior().resolve(state.default_clone_fn)) + .unwrap_or(state.default_clone_fn), }; // SAFETY: This component exists because it is present on the archetype. @@ -525,19 +600,19 @@ impl EntityCloner { &mut bundle_scratch, world.entities(), info, - self, + state, mapper, app_registry.as_ref(), ) }; (handler)(&source_component, &mut ctx); - } + }); } world.flush(); - for deferred in self.deferred_commands.drain(..) { + for deferred in state.deferred_commands.drain(..) { (deferred)(world, mapper); } @@ -545,7 +620,7 @@ impl EntityCloner { panic!("Target entity does not exist"); } - if self.move_components { + if state.move_components { world .entity_mut(source) .remove_by_ids(&bundle_scratch.component_ids); @@ -557,111 +632,64 @@ impl EntityCloner { unsafe { bundle_scratch.write(world, target, relationship_hook_insert_mode) }; target } +} - /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. - /// If this [`EntityCloner`] has [`EntityCloner::linked_cloning`], then it will recursively spawn entities as defined - /// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with - /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) - #[track_caller] - pub fn clone_entity(&mut self, world: &mut World, source: Entity, target: Entity) { - let mut map = EntityHashMap::::new(); - map.set_mapped(source, target); - self.clone_entity_mapped(world, source, &mut map); - } - - /// Clones and inserts components from the `source` entity into a newly spawned entity using the stored configuration. - /// If this [`EntityCloner`] has [`EntityCloner::linked_cloning`], then it will recursively spawn entities as defined - /// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with - /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) - #[track_caller] - pub fn spawn_clone(&mut self, world: &mut World, source: Entity) -> Entity { - let target = world.spawn_empty().id(); - self.clone_entity(world, source, target); - target - } - - /// Clones the entity into whatever entity `mapper` chooses for it. - #[track_caller] - pub fn clone_entity_mapped( - &mut self, - world: &mut World, - source: Entity, - mapper: &mut dyn EntityMapper, - ) -> Entity { - // All relationships on the root should have their hooks run - let target = self.clone_entity_internal(world, source, mapper, RelationshipHookMode::Run); - let child_hook_insert_mode = if self.linked_cloning { - // When spawning "linked relationships", we want to ignore hooks for relationships we are spawning, while - // still registering with original relationship targets that are "not linked" to the current recursive spawn. - RelationshipHookMode::RunIfNotLinked - } else { - // If we are not cloning "linked relationships" recursively, then we want any cloned relationship components to - // register themselves with their original relationship target. - RelationshipHookMode::Run - }; - loop { - let queued = self.clone_queue.pop_front(); - if let Some(queued) = queued { - self.clone_entity_internal(world, queued, mapper, child_hook_insert_mode); - } else { - break; - } - } - target - } +/// Part of the [`EntityCloner`], see there for more information. +struct EntityClonerState { + clone_behavior_overrides: HashMap, + move_components: bool, + linked_cloning: bool, + default_clone_fn: ComponentCloneFn, + clone_queue: VecDeque, + deferred_commands: VecDeque>, +} - fn is_cloning_allowed( - &self, - component: &ComponentId, - target_archetype: Option<&Archetype>, - ) -> bool { - if self.filter_allows_components { - self.filter.contains(component) - || target_archetype.is_some_and(|archetype| { - !archetype.contains(*component) && self.filter_required.contains(component) - }) - } else { - !self.filter.contains(component) && !self.filter_required.contains(component) +impl Default for EntityClonerState { + fn default() -> Self { + Self { + move_components: false, + linked_cloning: false, + default_clone_fn: ComponentCloneBehavior::global_default_fn(), + clone_behavior_overrides: Default::default(), + clone_queue: Default::default(), + deferred_commands: Default::default(), } } } /// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information. -pub struct EntityClonerBuilder<'w> { +pub struct EntityClonerBuilder<'w, Filter> { world: &'w mut World, - entity_cloner: EntityCloner, - attach_required_components: bool, + filter: Filter, + state: EntityClonerState, } -impl<'w> EntityClonerBuilder<'w> { +impl<'w, Filter: CloneByFilter> EntityClonerBuilder<'w, Filter> { /// Internally calls [`EntityCloner::clone_entity`] on the builder's [`World`]. pub fn clone_entity(&mut self, source: Entity, target: Entity) -> &mut Self { - self.entity_cloner.clone_entity(self.world, source, target); + let mut mapper = EntityHashMap::::new(); + mapper.set_mapped(source, target); + EntityCloner::clone_entity_mapped_internal( + &mut self.state, + &mut self.filter, + self.world, + source, + &mut mapper, + ); self } + /// Finishes configuring [`EntityCloner`] returns it. pub fn finish(self) -> EntityCloner { - self.entity_cloner - } - - /// By default, any components allowed/denied through the filter will automatically - /// allow/deny all of their required components. - /// - /// This method allows for a scoped mode where any changes to the filter - /// will not involve required components. - pub fn without_required_components( - &mut self, - builder: impl FnOnce(&mut EntityClonerBuilder), - ) -> &mut Self { - self.attach_required_components = false; - builder(self); - self.attach_required_components = true; - self + EntityCloner { + filter: self.filter.into(), + state: self.state, + } } /// Sets the default clone function to use. pub fn with_default_clone_fn(&mut self, clone_fn: ComponentCloneFn) -> &mut Self { - self.entity_cloner.default_clone_fn = clone_fn; + self.state.default_clone_fn = clone_fn; self } @@ -673,211 +701,630 @@ impl<'w> EntityClonerBuilder<'w> { /// The setting only applies to components that are allowed through the filter /// at the time [`EntityClonerBuilder::clone_entity`] is called. pub fn move_components(&mut self, enable: bool) -> &mut Self { - self.entity_cloner.move_components = enable; + self.state.move_components = enable; self } - /// Adds all components of the bundle to the list of components to clone. + /// Overrides the [`ComponentCloneBehavior`] for a component in this builder. + /// This handler will be used to clone the component instead of the global one defined by the [`EntityCloner`]. /// - /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call - /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. - pub fn allow(&mut self) -> &mut Self { - let bundle = self.world.register_bundle::(); - let ids = bundle.explicit_components().to_owned(); - for id in ids { - self.filter_allow(id); + /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. + pub fn override_clone_behavior( + &mut self, + clone_behavior: ComponentCloneBehavior, + ) -> &mut Self { + if let Some(id) = self.world.components().valid_component_id::() { + self.state + .clone_behavior_overrides + .insert(id, clone_behavior); } self } - /// Extends the list of components to clone. + /// Overrides the [`ComponentCloneBehavior`] for a component with the given `component_id` in this builder. + /// This handler will be used to clone the component instead of the global one defined by the [`EntityCloner`]. /// - /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call - /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. - pub fn allow_by_ids(&mut self, ids: impl IntoIterator) -> &mut Self { - for id in ids { - self.filter_allow(id); + /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. + pub fn override_clone_behavior_with_id( + &mut self, + component_id: ComponentId, + clone_behavior: ComponentCloneBehavior, + ) -> &mut Self { + self.state + .clone_behavior_overrides + .insert(component_id, clone_behavior); + self + } + + /// Removes a previously set override of [`ComponentCloneBehavior`] for a component in this builder. + pub fn remove_clone_behavior_override(&mut self) -> &mut Self { + if let Some(id) = self.world.components().valid_component_id::() { + self.state.clone_behavior_overrides.remove(&id); } self } - /// Extends the list of components to clone using [`TypeId`]s. + /// Removes a previously set override of [`ComponentCloneBehavior`] for a given `component_id` in this builder. + pub fn remove_clone_behavior_override_with_id( + &mut self, + component_id: ComponentId, + ) -> &mut Self { + self.state.clone_behavior_overrides.remove(&component_id); + self + } + + /// When true this cloner will be configured to clone entities referenced in cloned components via [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN). + /// This will produce "deep" / recursive clones of relationship trees that have "linked spawn". + pub fn linked_cloning(&mut self, linked_cloning: bool) -> &mut Self { + self.state.linked_cloning = linked_cloning; + self + } +} + +impl<'w> EntityClonerBuilder<'w, OptOut> { + /// By default, any components denied through the filter will automatically + /// deny all of components they are required by too. /// - /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call - /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. - pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator) -> &mut Self { - for type_id in ids { - if let Some(id) = self.world.components().get_valid_id(type_id) { - self.filter_allow(id); - } - } + /// This method allows for a scoped mode where any changes to the filter + /// will not involve these requiring components. + /// + /// If component `A` is denied in the `builder` closure here and component `B` + /// requires `A`, then `A` will be inserted with the value defined in `B`'s + /// [`Component` derive](https://docs.rs/bevy/latest/bevy/ecs/component/trait.Component.html#required-components). + /// This assumes `A` is missing yet at the target entity. + pub fn without_required_by_components(&mut self, builder: impl FnOnce(&mut Self)) -> &mut Self { + self.filter.attach_required_by_components = false; + builder(self); + self.filter.attach_required_by_components = true; self } - /// Resets the filter to allow all components to be cloned. - pub fn allow_all(&mut self) -> &mut Self { - self.entity_cloner.filter_allows_components = false; - self.entity_cloner.filter.clear(); + /// Sets whether components are always cloned ([`InsertMode::Replace`], the default) or only if it is missing + /// ([`InsertMode::Keep`]) at the target entity. + /// + /// This makes no difference if the target is spawned by the cloner. + pub fn insert_mode(&mut self, insert_mode: InsertMode) -> &mut Self { + self.filter.insert_mode = insert_mode; self } /// Disallows all components of the bundle from being cloned. + /// + /// If component `A` is denied here and component `B` requires `A`, then `A` + /// is denied as well. See [`Self::without_required_by_components`] to alter + /// this behavior. pub fn deny(&mut self) -> &mut Self { - let bundle = self.world.register_bundle::(); - let ids = bundle.explicit_components().to_owned(); - for id in ids { - self.filter_deny(id); + let bundle_id = self.world.register_bundle::().id(); + self.deny_by_bundle_id(bundle_id) + } + + /// Disallows all components of the bundle ID from being cloned. + /// + /// If component `A` is denied here and component `B` requires `A`, then `A` + /// is denied as well. See [`Self::without_required_by_components`] to alter + /// this behavior. + pub fn deny_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self { + if let Some(bundle) = self.world.bundles().get(bundle_id) { + let ids = bundle.explicit_components().iter(); + for &id in ids { + self.filter.filter_deny(id, self.world); + } } self } /// Extends the list of components that shouldn't be cloned. + /// + /// If component `A` is denied here and component `B` requires `A`, then `A` + /// is denied as well. See [`Self::without_required_by_components`] to alter + /// this behavior. pub fn deny_by_ids(&mut self, ids: impl IntoIterator) -> &mut Self { for id in ids { - self.filter_deny(id); + self.filter.filter_deny(id, self.world); } self } /// Extends the list of components that shouldn't be cloned by type ids. + /// + /// If component `A` is denied here and component `B` requires `A`, then `A` + /// is denied as well. See [`Self::without_required_by_components`] to alter + /// this behavior. pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator) -> &mut Self { for type_id in ids { if let Some(id) = self.world.components().get_valid_id(type_id) { - self.filter_deny(id); + self.filter.filter_deny(id, self.world); } } self } +} - /// Sets the filter to deny all components. - pub fn deny_all(&mut self) -> &mut Self { - self.entity_cloner.filter_allows_components = true; - self.entity_cloner.filter.clear(); +impl<'w> EntityClonerBuilder<'w, OptIn> { + /// By default, any components allowed through the filter will automatically + /// allow all of their required components. + /// + /// This method allows for a scoped mode where any changes to the filter + /// will not involve required components. + /// + /// If component `A` is allowed in the `builder` closure here and requires + /// component `B`, then `B` will be inserted with the value defined in `A`'s + /// [`Component` derive](https://docs.rs/bevy/latest/bevy/ecs/component/trait.Component.html#required-components). + /// This assumes `B` is missing yet at the target entity. + pub fn without_required_components(&mut self, builder: impl FnOnce(&mut Self)) -> &mut Self { + self.filter.attach_required_components = false; + builder(self); + self.filter.attach_required_components = true; self } - /// Overrides the [`ComponentCloneBehavior`] for a component in this builder. - /// This handler will be used to clone the component instead of the global one defined by the [`EntityCloner`]. + /// Adds all components of the bundle to the list of components to clone. /// - /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. - pub fn override_clone_behavior( - &mut self, - clone_behavior: ComponentCloneBehavior, - ) -> &mut Self { - if let Some(id) = self.world.components().valid_component_id::() { - self.entity_cloner - .clone_behavior_overrides - .insert(id, clone_behavior); + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow(&mut self) -> &mut Self { + let bundle_id = self.world.register_bundle::().id(); + self.allow_by_bundle_id(bundle_id) + } + + /// Adds all components of the bundle to the list of components to clone if + /// the target does not contain them. + /// + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_if_new(&mut self) -> &mut Self { + let bundle_id = self.world.register_bundle::().id(); + self.allow_by_bundle_id_if_new(bundle_id) + } + + /// Adds all components of the bundle ID to the list of components to clone. + /// + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self { + if let Some(bundle) = self.world.bundles().get(bundle_id) { + let ids = bundle.explicit_components().iter(); + for &id in ids { + self.filter + .filter_allow(id, self.world, InsertMode::Replace); + } } self } - /// Overrides the [`ComponentCloneBehavior`] for a component with the given `component_id` in this builder. - /// This handler will be used to clone the component instead of the global one defined by the [`EntityCloner`]. + /// Adds all components of the bundle ID to the list of components to clone + /// if the target does not contain them. /// - /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. - pub fn override_clone_behavior_with_id( - &mut self, - component_id: ComponentId, - clone_behavior: ComponentCloneBehavior, - ) -> &mut Self { - self.entity_cloner - .clone_behavior_overrides - .insert(component_id, clone_behavior); + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_by_bundle_id_if_new(&mut self, bundle_id: BundleId) -> &mut Self { + if let Some(bundle) = self.world.bundles().get(bundle_id) { + let ids = bundle.explicit_components().iter(); + for &id in ids { + self.filter.filter_allow(id, self.world, InsertMode::Keep); + } + } + self + } + + /// Extends the list of components to clone. + /// + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_by_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + for id in ids { + self.filter + .filter_allow(id, self.world, InsertMode::Replace); + } + self + } + + /// Extends the list of components to clone if the target does not contain them. + /// + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_by_ids_if_new(&mut self, ids: impl IntoIterator) -> &mut Self { + for id in ids { + self.filter.filter_allow(id, self.world, InsertMode::Keep); + } + self + } + + /// Extends the list of components to clone using [`TypeId`]s. + /// + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator) -> &mut Self { + for type_id in ids { + if let Some(id) = self.world.components().get_valid_id(type_id) { + self.filter + .filter_allow(id, self.world, InsertMode::Replace); + } + } self } - /// Removes a previously set override of [`ComponentCloneBehavior`] for a component in this builder. - pub fn remove_clone_behavior_override(&mut self) -> &mut Self { - if let Some(id) = self.world.components().valid_component_id::() { - self.entity_cloner.clone_behavior_overrides.remove(&id); + /// Extends the list of components to clone using [`TypeId`]s if the target + /// does not contain them. + /// + /// If component `A` is allowed here and requires component `B`, then `B` + /// is allowed as well. See [`Self::without_required_components`] + /// to alter this behavior. + pub fn allow_by_type_ids_if_new(&mut self, ids: impl IntoIterator) -> &mut Self { + for type_id in ids { + if let Some(id) = self.world.components().get_valid_id(type_id) { + self.filter.filter_allow(id, self.world, InsertMode::Keep); + } + } + self + } +} + +/// Filters that can selectively clone components depending on its inner configuration are unified with this trait. +#[doc(hidden)] +pub trait CloneByFilter: Into { + /// The filter will call `clone_component` for every [`ComponentId`] that passes it. + fn clone_components<'a>( + &mut self, + source_archetype: &Archetype, + target_archetype: LazyCell<&'a Archetype, impl FnOnce() -> &'a Archetype>, + clone_component: impl FnMut(ComponentId), + ); +} + +/// Part of the [`EntityCloner`], see there for more information. +#[doc(hidden)] +#[derive(From)] +pub enum EntityClonerFilter { + OptOut(OptOut), + OptIn(OptIn), +} + +impl Default for EntityClonerFilter { + fn default() -> Self { + Self::OptOut(Default::default()) + } +} + +impl CloneByFilter for EntityClonerFilter { + #[inline] + fn clone_components<'a>( + &mut self, + source_archetype: &Archetype, + target_archetype: LazyCell<&'a Archetype, impl FnOnce() -> &'a Archetype>, + clone_component: impl FnMut(ComponentId), + ) { + match self { + Self::OptOut(filter) => { + filter.clone_components(source_archetype, target_archetype, clone_component); + } + Self::OptIn(filter) => { + filter.clone_components(source_archetype, target_archetype, clone_component); + } + } + } +} + +/// Generic for [`EntityClonerBuilder`] that makes the cloner try to clone every component from the source entity +/// except for components that were explicitly denied, for example by using the +/// [`deny`](EntityClonerBuilder::deny) method. +/// +/// Required components are not considered by denied components and must be explicitly denied as well if desired. +pub struct OptOut { + /// Contains the components that should not be cloned. + deny: HashSet, + + /// Determines if a component is inserted when it is existing already. + insert_mode: InsertMode, + + /// Is `true` unless during [`EntityClonerBuilder::without_required_by_components`] which will suppress + /// components that require denied components to be denied as well, causing them to be created independent + /// from the value at the source entity if needed. + attach_required_by_components: bool, +} + +impl Default for OptOut { + fn default() -> Self { + Self { + deny: Default::default(), + insert_mode: InsertMode::Replace, + attach_required_by_components: true, + } + } +} + +impl CloneByFilter for OptOut { + #[inline] + fn clone_components<'a>( + &mut self, + source_archetype: &Archetype, + target_archetype: LazyCell<&'a Archetype, impl FnOnce() -> &'a Archetype>, + mut clone_component: impl FnMut(ComponentId), + ) { + match self.insert_mode { + InsertMode::Replace => { + for component in source_archetype.components() { + if !self.deny.contains(&component) { + clone_component(component); + } + } + } + InsertMode::Keep => { + for component in source_archetype.components() { + if !target_archetype.contains(component) && !self.deny.contains(&component) { + clone_component(component); + } + } + } + } + } +} + +impl OptOut { + /// Denies a component through the filter, also deny components that require `id` if + /// [`Self::attach_required_by_components`] is true. + #[inline] + fn filter_deny(&mut self, id: ComponentId, world: &World) { + self.deny.insert(id); + if self.attach_required_by_components { + if let Some(required_by) = world.components().get_required_by(id) { + self.deny.extend(required_by.iter()); + }; } - self } +} - /// Removes a previously set override of [`ComponentCloneBehavior`] for a given `component_id` in this builder. - pub fn remove_clone_behavior_override_with_id( - &mut self, - component_id: ComponentId, - ) -> &mut Self { - self.entity_cloner - .clone_behavior_overrides - .remove(&component_id); - self - } +/// Generic for [`EntityClonerBuilder`] that makes the cloner try to clone every component that was explicitly +/// allowed from the source entity, for example by using the [`allow`](EntityClonerBuilder::allow) method. +/// +/// Required components are also cloned when the target entity does not contain them. +pub struct OptIn { + /// Contains the components explicitly allowed to be cloned. + allow: HashMap, + + /// Lists of required components, [`Explicit`] refers to a range in it. + required_of_allow: Vec, + + /// Contains the components required by those in [`Self::allow`]. + /// Also contains the number of components in [`Self::allow`] each is required by to track + /// when to skip cloning a required component after skipping explicit components that require it. + required: HashMap, + + /// Is `true` unless during [`EntityClonerBuilder::without_required_components`] which will suppress + /// evaluating required components to clone, causing them to be created independent from the value at + /// the source entity if needed. + attach_required_components: bool, +} - /// When true this cloner will be configured to clone entities referenced in cloned components via [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN). - /// This will produce "deep" / recursive clones of relationship trees that have "linked spawn". - pub fn linked_cloning(&mut self, linked_cloning: bool) -> &mut Self { - self.entity_cloner.linked_cloning = linked_cloning; - self +impl Default for OptIn { + fn default() -> Self { + Self { + allow: Default::default(), + required_of_allow: Default::default(), + required: Default::default(), + attach_required_components: true, + } } +} - /// Helper function that allows a component through the filter. - fn filter_allow(&mut self, id: ComponentId) { - if self.entity_cloner.filter_allows_components { - self.entity_cloner.filter.insert(id); - } else { - self.entity_cloner.filter.remove(&id); - } - if self.attach_required_components { - if let Some(info) = self.world.components().get_info(id) { - for required_id in info.required_components().iter_ids() { - if self.entity_cloner.filter_allows_components { - self.entity_cloner.filter_required.insert(required_id); - } else { - self.entity_cloner.filter_required.remove(&required_id); +impl CloneByFilter for OptIn { + #[inline] + fn clone_components<'a>( + &mut self, + source_archetype: &Archetype, + target_archetype: LazyCell<&'a Archetype, impl FnOnce() -> &'a Archetype>, + mut clone_component: impl FnMut(ComponentId), + ) { + // track the amount of components left not being cloned yet to exit this method early + let mut uncloned_components = source_archetype.component_count(); + + // track if any `Required::required_by_reduced` has been reduced so they are reset + let mut reduced_any = false; + + // clone explicit components + for (&component, explicit) in self.allow.iter() { + if uncloned_components == 0 { + // exhausted all source components, reset changed `Required::required_by_reduced` + if reduced_any { + self.required + .iter_mut() + .for_each(|(_, required)| required.reset()); + } + return; + } + + let do_clone = source_archetype.contains(component) + && (explicit.insert_mode == InsertMode::Replace + || !target_archetype.contains(component)); + if do_clone { + clone_component(component); + uncloned_components -= 1; + } else if let Some(range) = explicit.required_range.clone() { + for component in self.required_of_allow[range].iter() { + // may be None if required component was also added as explicit later + if let Some(required) = self.required.get_mut(component) { + required.required_by_reduced -= 1; + reduced_any = true; } } } } - } - /// Helper function that disallows a component through the filter. - fn filter_deny(&mut self, id: ComponentId) { - if self.entity_cloner.filter_allows_components { - self.entity_cloner.filter.remove(&id); - } else { - self.entity_cloner.filter.insert(id); + let mut required_iter = self.required.iter_mut(); + + // clone required components + let required_components = required_iter + .by_ref() + .filter_map(|(&component, required)| { + let do_clone = required.required_by_reduced > 0 // required by a cloned component + && source_archetype.contains(component) // must exist to clone, may miss if removed + && !target_archetype.contains(component); // do not overwrite existing values + + // reset changed `Required::required_by_reduced` as this is done being checked here + required.reset(); + + do_clone.then_some(component) + }) + .take(uncloned_components); + + for required_component in required_components { + clone_component(required_component); } - if self.attach_required_components { - if let Some(info) = self.world.components().get_info(id) { - for required_id in info.required_components().iter_ids() { - if self.entity_cloner.filter_allows_components { - self.entity_cloner.filter_required.remove(&required_id); - } else { - self.entity_cloner.filter_required.insert(required_id); + + // if the `required_components` iterator has not been exhausted yet because the source has no more + // components to clone, iterate the rest to reset changed `Required::required_by_reduced` for the + // next clone + if reduced_any { + required_iter.for_each(|(_, required)| required.reset()); + } + } +} + +impl OptIn { + /// Allows a component through the filter, also allow required components if + /// [`Self::attach_required_components`] is true. + #[inline] + fn filter_allow(&mut self, id: ComponentId, world: &World, mut insert_mode: InsertMode) { + match self.allow.entry(id) { + Entry::Vacant(explicit) => { + // explicit components should not appear in the required map + self.required.remove(&id); + + if !self.attach_required_components { + explicit.insert(Explicit { + insert_mode, + required_range: None, + }); + } else { + self.filter_allow_with_required(id, world, insert_mode); + } + } + Entry::Occupied(mut explicit) => { + let explicit = explicit.get_mut(); + + // set required component range if it was inserted with `None` earlier + if self.attach_required_components && explicit.required_range.is_none() { + if explicit.insert_mode == InsertMode::Replace { + // do not overwrite with Keep if component was allowed as Replace earlier + insert_mode = InsertMode::Replace; } + + self.filter_allow_with_required(id, world, insert_mode); + } else if explicit.insert_mode == InsertMode::Keep { + // potentially overwrite Keep with Replace + explicit.insert_mode = insert_mode; } } - } + }; + } + + // Allow a component through the filter and include required components. + #[inline] + fn filter_allow_with_required( + &mut self, + id: ComponentId, + world: &World, + insert_mode: InsertMode, + ) { + let Some(info) = world.components().get_info(id) else { + return; + }; + + let iter = info + .required_components() + .iter_ids() + .filter(|id| !self.allow.contains_key(id)) + .inspect(|id| { + // set or increase the number of components this `id` is required by + self.required + .entry(*id) + .and_modify(|required| { + required.required_by += 1; + required.required_by_reduced += 1; + }) + .or_insert(Required { + required_by: 1, + required_by_reduced: 1, + }); + }); + + let start = self.required_of_allow.len(); + self.required_of_allow.extend(iter); + let end = self.required_of_allow.len(); + + self.allow.insert( + id, + Explicit { + insert_mode, + required_range: Some(start..end), + }, + ); + } +} + +/// Contains the components explicitly allowed to be cloned. +struct Explicit { + /// If component was added via [`allow`](EntityClonerBuilder::allow) etc, this is `Overwrite`. + /// + /// If component was added via [`allow_if_new`](EntityClonerBuilder::allow_if_new) etc, this is `Keep`. + insert_mode: InsertMode, + + /// Contains the range in [`OptIn::required_of_allow`] for this component containing its + /// required components. + /// + /// Is `None` if [`OptIn::attach_required_components`] was `false` when added. + /// It may be set to `Some` later if the component is later added explicitly again with + /// [`OptIn::attach_required_components`] being `true`. + /// + /// Range is empty if this component has no required components that are not also explicitly allowed. + required_range: Option>, +} + +struct Required { + /// Amount of explicit components this component is required by. + required_by: u32, + + /// As [`Self::required_by`] but is reduced during cloning when an explicit component is not cloned, + /// either because [`Explicit::insert_mode`] is `Keep` or the source entity does not contain it. + /// + /// If this is zero, the required component is not cloned. + /// + /// The counter is reset to `required_by` when the cloning is over in case another entity needs to be + /// cloned by the same [`EntityCloner`]. + required_by_reduced: u32, +} + +impl Required { + // Revert reductions for the next entity to clone with this EntityCloner + #[inline] + fn reset(&mut self) { + self.required_by_reduced = self.required_by; } } #[cfg(test)] mod tests { - use super::ComponentCloneCtx; + use super::*; use crate::{ - component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, - entity::{Entity, EntityCloner, EntityHashMap, SourceComponent}, + component::{ComponentDescriptor, StorageType}, prelude::{ChildOf, Children, Resource}, - reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, world::{FromWorld, World}, }; - use alloc::vec::Vec; use bevy_ptr::OwningPtr; - use bevy_reflect::Reflect; use core::marker::PhantomData; use core::{alloc::Layout, ops::Deref}; #[cfg(feature = "bevy_reflect")] mod reflect { use super::*; - use crate::{ - component::{Component, ComponentCloneBehavior}, - entity::{EntityCloner, SourceComponent}, - reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, - }; + use crate::reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}; use alloc::vec; use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr}; @@ -900,7 +1347,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) + EntityCloner::build_opt_out(&mut world) .override_clone_behavior::(ComponentCloneBehavior::reflect()) .clone_entity(e, e_clone); @@ -985,7 +1432,7 @@ mod tests { .id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) + EntityCloner::build_opt_out(&mut world) .override_clone_behavior_with_id(a_id, ComponentCloneBehavior::reflect()) .override_clone_behavior_with_id(b_id, ComponentCloneBehavior::reflect()) .override_clone_behavior_with_id(c_id, ComponentCloneBehavior::reflect()) @@ -1018,7 +1465,7 @@ mod tests { let mut registry = registry.write(); registry.register::(); registry - .get_mut(core::any::TypeId::of::()) + .get_mut(TypeId::of::()) .unwrap() .insert(>::from_type()); } @@ -1026,7 +1473,7 @@ mod tests { let e = world.spawn(A).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) + EntityCloner::build_opt_out(&mut world) .override_clone_behavior::(ComponentCloneBehavior::Custom(test_handler)) .clone_entity(e, e_clone); } @@ -1055,7 +1502,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world).clone_entity(e, e_clone); + EntityCloner::build_opt_out(&mut world).clone_entity(e, e_clone); assert!(world .get::(e_clone) @@ -1079,7 +1526,7 @@ mod tests { // No AppTypeRegistry let e = world.spawn((A, B(Default::default()))).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) + EntityCloner::build_opt_out(&mut world) .override_clone_behavior::(ComponentCloneBehavior::reflect()) .override_clone_behavior::(ComponentCloneBehavior::reflect()) .clone_entity(e, e_clone); @@ -1093,10 +1540,50 @@ mod tests { let e = world.spawn((A, B(Default::default()))).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world).clone_entity(e, e_clone); + EntityCloner::build_opt_out(&mut world).clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), None); assert_eq!(world.get::(e_clone), None); } + + #[test] + fn clone_with_reflect_from_world() { + #[derive(Component, Reflect, PartialEq, Eq, Debug)] + #[reflect(Component, FromWorld, from_reflect = false)] + struct SomeRef( + #[entities] Entity, + // We add an ignored field here to ensure `reflect_clone` fails and `FromWorld` is used + #[reflect(ignore)] PhantomData<()>, + ); + + #[derive(Resource)] + struct FromWorldCalled(bool); + + impl FromWorld for SomeRef { + fn from_world(world: &mut World) -> Self { + world.insert_resource(FromWorldCalled(true)); + SomeRef(Entity::PLACEHOLDER, Default::default()) + } + } + let mut world = World::new(); + let registry = AppTypeRegistry::default(); + registry.write().register::(); + world.insert_resource(registry); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn(SomeRef(a, Default::default())).id(); + let d = world.spawn_empty().id(); + let mut map = EntityHashMap::::new(); + map.insert(a, b); + map.insert(c, d); + + let cloned = EntityCloner::default().clone_entity_mapped(&mut world, c, &mut map); + assert_eq!( + *world.entity(cloned).get::().unwrap(), + SomeRef(b, Default::default()) + ); + assert!(world.resource::().0); + } } #[test] @@ -1113,7 +1600,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world).clone_entity(e, e_clone); + EntityCloner::build_opt_out(&mut world).clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); } @@ -1135,8 +1622,7 @@ mod tests { let e = world.spawn((component.clone(), B)).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) - .deny_all() + EntityCloner::build_opt_in(&mut world) .allow::() .clone_entity(e, e_clone); @@ -1152,9 +1638,10 @@ mod tests { } #[derive(Component, Clone)] + #[require(C)] struct B; - #[derive(Component, Clone)] + #[derive(Component, Clone, Default)] struct C; let mut world = World::default(); @@ -1164,50 +1651,45 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) - .deny::() + EntityCloner::build_opt_out(&mut world) + .deny::() .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); - assert!(world.get::(e_clone).is_some()); + assert!(world.get::(e_clone).is_none()); } #[test] - fn clone_entity_with_override_allow_filter() { + fn clone_entity_with_deny_filter_without_required_by() { + #[derive(Component, Clone)] + #[require(B { field: 5 })] + struct A; + #[derive(Component, Clone, PartialEq, Eq)] - struct A { + struct B { field: usize, } - #[derive(Component, Clone)] - struct B; - - #[derive(Component, Clone)] - struct C; - let mut world = World::default(); - let component = A { field: 5 }; - - let e = world.spawn((component.clone(), B, C)).id(); + let e = world.spawn((A, B { field: 10 })).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) - .deny_all() - .allow::() - .allow::() - .allow::() - .deny::() + EntityCloner::build_opt_out(&mut world) + .without_required_by_components(|builder| { + builder.deny::(); + }) .clone_entity(e, e_clone); - assert!(world.get::(e_clone).is_some_and(|c| *c == component)); - assert!(world.get::(e_clone).is_none()); - assert!(world.get::(e_clone).is_some()); + assert!(world.get::(e_clone).is_some()); + assert!(world + .get::(e_clone) + .is_some_and(|c| *c == B { field: 5 })); } #[test] - fn clone_entity_with_override_bundle() { + fn clone_entity_with_deny_filter_if_new() { #[derive(Component, Clone, PartialEq, Eq)] struct A { field: usize, @@ -1221,20 +1703,126 @@ mod tests { let mut world = World::default(); - let component = A { field: 5 }; - - let e = world.spawn((component.clone(), B, C)).id(); - let e_clone = world.spawn_empty().id(); + let e = world.spawn((A { field: 5 }, B, C)).id(); + let e_clone = world.spawn(A { field: 8 }).id(); - EntityCloner::build(&mut world) - .deny_all() - .allow::<(A, B, C)>() - .deny::<(B, C)>() + EntityCloner::build_opt_out(&mut world) + .deny::() + .insert_mode(InsertMode::Keep) .clone_entity(e, e_clone); - assert!(world.get::(e_clone).is_some_and(|c| *c == component)); + assert!(world + .get::(e_clone) + .is_some_and(|c| *c == A { field: 8 })); assert!(world.get::(e_clone).is_none()); - assert!(world.get::(e_clone).is_none()); + assert!(world.get::(e_clone).is_some()); + } + + #[test] + fn allow_and_allow_if_new_always_allows() { + #[derive(Component, Clone, PartialEq, Debug)] + struct A(u8); + + let mut world = World::default(); + let e = world.spawn(A(1)).id(); + let e_clone1 = world.spawn(A(2)).id(); + + EntityCloner::build_opt_in(&mut world) + .allow_if_new::() + .allow::() + .clone_entity(e, e_clone1); + + assert_eq!(world.get::(e_clone1), Some(&A(1))); + + let e_clone2 = world.spawn(A(2)).id(); + + EntityCloner::build_opt_in(&mut world) + .allow::() + .allow_if_new::() + .clone_entity(e, e_clone2); + + assert_eq!(world.get::(e_clone2), Some(&A(1))); + } + + #[test] + fn with_and_without_required_components_include_required() { + #[derive(Component, Clone, PartialEq, Debug)] + #[require(B(5))] + struct A; + + #[derive(Component, Clone, PartialEq, Debug)] + struct B(u8); + + let mut world = World::default(); + let e = world.spawn((A, B(10))).id(); + let e_clone1 = world.spawn_empty().id(); + EntityCloner::build_opt_in(&mut world) + .without_required_components(|builder| { + builder.allow::(); + }) + .allow::() + .clone_entity(e, e_clone1); + + assert_eq!(world.get::(e_clone1), Some(&B(10))); + + let e_clone2 = world.spawn_empty().id(); + + EntityCloner::build_opt_in(&mut world) + .allow::() + .without_required_components(|builder| { + builder.allow::(); + }) + .clone_entity(e, e_clone2); + + assert_eq!(world.get::(e_clone2), Some(&B(10))); + } + + #[test] + fn clone_required_becoming_explicit() { + #[derive(Component, Clone, PartialEq, Debug)] + #[require(B(5))] + struct A; + + #[derive(Component, Clone, PartialEq, Debug)] + struct B(u8); + + let mut world = World::default(); + let e = world.spawn((A, B(10))).id(); + let e_clone1 = world.spawn(B(20)).id(); + EntityCloner::build_opt_in(&mut world) + .allow::() + .allow::() + .clone_entity(e, e_clone1); + + assert_eq!(world.get::(e_clone1), Some(&B(10))); + + let e_clone2 = world.spawn(B(20)).id(); + EntityCloner::build_opt_in(&mut world) + .allow::() + .allow::() + .clone_entity(e, e_clone2); + + assert_eq!(world.get::(e_clone2), Some(&B(10))); + } + + #[test] + fn required_not_cloned_because_requiring_missing() { + #[derive(Component, Clone)] + #[require(B)] + struct A; + + #[derive(Component, Clone, Default)] + struct B; + + let mut world = World::default(); + let e = world.spawn(B).id(); + let e_clone1 = world.spawn_empty().id(); + + EntityCloner::build_opt_in(&mut world) + .allow::() + .clone_entity(e, e_clone1); + + assert!(world.get::(e_clone1).is_none()); } #[test] @@ -1255,8 +1843,7 @@ mod tests { let e = world.spawn(A).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) - .deny_all() + EntityCloner::build_opt_in(&mut world) .allow::() .clone_entity(e, e_clone); @@ -1283,8 +1870,7 @@ mod tests { let e = world.spawn((A, C(0))).id(); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) - .deny_all() + EntityCloner::build_opt_in(&mut world) .without_required_components(|builder| { builder.allow::(); }) @@ -1295,6 +1881,60 @@ mod tests { assert_eq!(world.entity(e_clone).get::(), Some(&C(5))); } + #[test] + fn clone_entity_with_missing_required_components() { + #[derive(Component, Clone, PartialEq, Debug)] + #[require(B)] + struct A; + + #[derive(Component, Clone, PartialEq, Debug, Default)] + #[require(C(5))] + struct B; + + #[derive(Component, Clone, PartialEq, Debug)] + struct C(u32); + + let mut world = World::default(); + + let e = world.spawn(A).remove::().id(); + let e_clone = world.spawn_empty().id(); + + EntityCloner::build_opt_in(&mut world) + .allow::() + .clone_entity(e, e_clone); + + assert_eq!(world.entity(e_clone).get::(), Some(&A)); + assert_eq!(world.entity(e_clone).get::(), Some(&B)); + assert_eq!(world.entity(e_clone).get::(), Some(&C(5))); + } + + #[test] + fn skipped_required_components_counter_is_reset_on_early_return() { + #[derive(Component, Clone, PartialEq, Debug, Default)] + #[require(B(5))] + struct A; + + #[derive(Component, Clone, PartialEq, Debug)] + struct B(u32); + + #[derive(Component, Clone, PartialEq, Debug, Default)] + struct C; + + let mut world = World::default(); + + let e1 = world.spawn(C).id(); + let e2 = world.spawn((A, B(0))).id(); + let e_clone = world.spawn_empty().id(); + + let mut builder = EntityCloner::build_opt_in(&mut world); + builder.allow::<(A, C)>(); + let mut cloner = builder.finish(); + cloner.clone_entity(&mut world, e1, e_clone); + cloner.clone_entity(&mut world, e2, e_clone); + + assert_eq!(world.entity(e_clone).get::(), Some(&B(0))); + } + #[test] fn clone_entity_with_dynamic_components() { const COMPONENT_SIZE: usize = 10; @@ -1335,7 +1975,7 @@ mod tests { let entity = entity.id(); let entity_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world).clone_entity(entity, entity_clone); + EntityCloner::build_opt_out(&mut world).clone_entity(entity, entity_clone); let ptr = world.get_by_id(entity, component_id).unwrap(); let clone_ptr = world.get_by_id(entity_clone, component_id).unwrap(); @@ -1357,7 +1997,7 @@ mod tests { let child2 = world.spawn(ChildOf(root)).id(); let clone_root = world.spawn_empty().id(); - EntityCloner::build(&mut world) + EntityCloner::build_opt_out(&mut world) .linked_cloning(true) .clone_entity(root, clone_root); @@ -1382,46 +2022,6 @@ mod tests { ); } - #[test] - fn clone_with_reflect_from_world() { - #[derive(Component, Reflect, PartialEq, Eq, Debug)] - #[reflect(Component, FromWorld, from_reflect = false)] - struct SomeRef( - #[entities] Entity, - // We add an ignored field here to ensure `reflect_clone` fails and `FromWorld` is used - #[reflect(ignore)] PhantomData<()>, - ); - - #[derive(Resource)] - struct FromWorldCalled(bool); - - impl FromWorld for SomeRef { - fn from_world(world: &mut World) -> Self { - world.insert_resource(FromWorldCalled(true)); - SomeRef(Entity::PLACEHOLDER, Default::default()) - } - } - let mut world = World::new(); - let registry = AppTypeRegistry::default(); - registry.write().register::(); - world.insert_resource(registry); - - let a = world.spawn_empty().id(); - let b = world.spawn_empty().id(); - let c = world.spawn(SomeRef(a, Default::default())).id(); - let d = world.spawn_empty().id(); - let mut map = EntityHashMap::::new(); - map.insert(a, b); - map.insert(c, d); - - let cloned = EntityCloner::default().clone_entity_mapped(&mut world, c, &mut map); - assert_eq!( - *world.entity(cloned).get::().unwrap(), - SomeRef(b, Default::default()) - ); - assert!(world.resource::().0); - } - #[test] fn cloning_with_required_components_preserves_existing() { #[derive(Component, Clone, PartialEq, Debug, Default)] @@ -1436,21 +2036,11 @@ mod tests { let e = world.spawn((A, B(0))).id(); let e_clone = world.spawn(B(1)).id(); - EntityCloner::build(&mut world) - .deny_all() + EntityCloner::build_opt_in(&mut world) .allow::() .clone_entity(e, e_clone); assert_eq!(world.entity(e_clone).get::(), Some(&A)); assert_eq!(world.entity(e_clone).get::(), Some(&B(1))); - - let e_clone2 = world.spawn(B(2)).id(); - - EntityCloner::build(&mut world) - .allow_all() - .deny::() - .clone_entity(e, e_clone2); - - assert_eq!(world.entity(e_clone2).get::(), Some(&B(2))); } } diff --git a/crates/bevy_ecs/src/observer/entity_cloning.rs b/crates/bevy_ecs/src/observer/entity_cloning.rs index ee37300e64e01..7c7a4f69e9d0b 100644 --- a/crates/bevy_ecs/src/observer/entity_cloning.rs +++ b/crates/bevy_ecs/src/observer/entity_cloning.rs @@ -2,14 +2,16 @@ use crate::{ component::ComponentCloneBehavior, - entity::{ComponentCloneCtx, EntityClonerBuilder, EntityMapper, SourceComponent}, + entity::{ + CloneByFilter, ComponentCloneCtx, EntityClonerBuilder, EntityMapper, SourceComponent, + }, observer::ObservedBy, world::World, }; use super::Observer; -impl EntityClonerBuilder<'_> { +impl EntityClonerBuilder<'_, Filter> { /// Sets the option to automatically add cloned entities to the observers targeting source entity. pub fn add_observers(&mut self, add_observers: bool) -> &mut Self { if add_observers { @@ -98,7 +100,7 @@ mod tests { world.trigger_targets(E, e); let e_clone = world.spawn_empty().id(); - EntityCloner::build(&mut world) + EntityCloner::build_opt_out(&mut world) .add_observers(true) .clone_entity(e, e_clone); diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 87bd2d858b27c..098493a148633 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -11,7 +11,7 @@ use crate::{ bundle::{Bundle, InsertMode}, change_detection::MaybeLocation, component::{Component, ComponentId, ComponentInfo}, - entity::{Entity, EntityClonerBuilder}, + entity::{Entity, EntityClonerBuilder, OptIn, OptOut}, event::EntityEvent, relationship::RelationshipHookMode, system::IntoObserverSystem, @@ -243,12 +243,36 @@ pub fn trigger(event: impl EntityEvent) -> impl EntityCommand { /// An [`EntityCommand`] that clones parts of an entity onto another entity, /// configured through [`EntityClonerBuilder`]. -pub fn clone_with( +/// +/// This builder tries to clone every component from the source entity except +/// for components that were explicitly denied, for example by using the +/// [`deny`](EntityClonerBuilder::deny) method. +/// +/// Required components are not considered by denied components and must be +/// explicitly denied as well if desired. +pub fn clone_with_opt_out( + target: Entity, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, +) -> impl EntityCommand { + move |mut entity: EntityWorldMut| { + entity.clone_with_opt_out(target, config); + } +} + +/// An [`EntityCommand`] that clones parts of an entity onto another entity, +/// configured through [`EntityClonerBuilder`]. +/// +/// This builder tries to clone every component that was explicitly allowed +/// from the source entity, for example by using the +/// [`allow`](EntityClonerBuilder::allow) method. +/// +/// Required components are also cloned when the target entity does not contain them. +pub fn clone_with_opt_in( target: Entity, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> impl EntityCommand { move |mut entity: EntityWorldMut| { - entity.clone_with(target, config); + entity.clone_with_opt_in(target, config); } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index ee6e01de1346a..0751e267708ad 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -18,7 +18,7 @@ use crate::{ bundle::{Bundle, InsertMode, NoBundleEffect}, change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, - entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError}, + entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError, OptIn, OptOut}, error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, event::{BufferedEvent, EntityEvent, Event}, observer::{Observer, TriggerTargets}, @@ -1978,8 +1978,9 @@ impl<'a> EntityCommands<'a> { /// Clones parts of an entity (components, observers, etc.) onto another entity, /// configured through [`EntityClonerBuilder`]. /// - /// By default, the other entity will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). + /// The other entity will receive all the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) except those that are + /// [denied](EntityClonerBuilder::deny) in the `config`. /// /// # Panics /// @@ -1987,7 +1988,7 @@ impl<'a> EntityCommands<'a> { /// /// # Example /// - /// Configure through [`EntityClonerBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// #[derive(Component, Clone)] @@ -2002,8 +2003,8 @@ impl<'a> EntityCommands<'a> { /// // Create a new entity and keep its EntityCommands. /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// - /// // Clone only ComponentA onto the target. - /// entity.clone_with(target, |builder| { + /// // Clone ComponentA but not ComponentB onto the target. + /// entity.clone_with_opt_out(target, |builder| { /// builder.deny::(); /// }); /// } @@ -2011,12 +2012,57 @@ impl<'a> EntityCommands<'a> { /// ``` /// /// See [`EntityClonerBuilder`] for more options. - pub fn clone_with( + pub fn clone_with_opt_out( &mut self, target: Entity, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { - self.queue(entity_command::clone_with(target, config)) + self.queue(entity_command::clone_with_opt_out(target, config)) + } + + /// Clones parts of an entity (components, observers, etc.) onto another entity, + /// configured through [`EntityClonerBuilder`]. + /// + /// The other entity will receive only the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) and are + /// [allowed](EntityClonerBuilder::allow) in the `config`. + /// + /// # Panics + /// + /// The command will panic when applied if the target entity does not exist. + /// + /// # Example + /// + /// Configure through [`EntityClonerBuilder`] as follows: + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Clone)] + /// struct ComponentA(u32); + /// #[derive(Component, Clone)] + /// struct ComponentB(u32); + /// + /// fn example_system(mut commands: Commands) { + /// // Create an empty entity. + /// let target = commands.spawn_empty().id(); + /// + /// // Create a new entity and keep its EntityCommands. + /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); + /// + /// // Clone ComponentA but not ComponentB onto the target. + /// entity.clone_with_opt_in(target, |builder| { + /// builder.allow::(); + /// }); + /// } + /// # bevy_ecs::system::assert_is_system(example_system); + /// ``` + /// + /// See [`EntityClonerBuilder`] for more options. + pub fn clone_with_opt_in( + &mut self, + target: Entity, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + ) -> &mut Self { + self.queue(entity_command::clone_with_opt_in(target, config)) } /// Spawns a clone of this entity and returns the [`EntityCommands`] of the clone. @@ -2025,7 +2071,8 @@ impl<'a> EntityCommands<'a> { /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// /// To configure cloning behavior (such as only cloning certain components), - /// use [`EntityCommands::clone_and_spawn_with`]. + /// use [`EntityCommands::clone_and_spawn_with_opt_out`]/ + /// [`opt_out`](EntityCommands::clone_and_spawn_with_opt_out). /// /// # Note /// @@ -2045,25 +2092,22 @@ impl<'a> EntityCommands<'a> { /// // Create a new entity and store its EntityCommands. /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// - /// // Create a clone of the first entity. + /// // Create a clone of the entity. /// let mut entity_clone = entity.clone_and_spawn(); /// } /// # bevy_ecs::system::assert_is_system(example_system); pub fn clone_and_spawn(&mut self) -> EntityCommands<'_> { - self.clone_and_spawn_with(|_| {}) + self.clone_and_spawn_with_opt_out(|_| {}) } /// Spawns a clone of this entity and allows configuring cloning behavior /// using [`EntityClonerBuilder`], returning the [`EntityCommands`] of the clone. /// - /// By default, the clone will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). - /// - /// To exclude specific components, use [`EntityClonerBuilder::deny`]. - /// To only include specific components, use [`EntityClonerBuilder::deny_all`] - /// followed by [`EntityClonerBuilder::allow`]. + /// The clone will receive all the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) except those that are + /// [denied](EntityClonerBuilder::deny) in the `config`. /// - /// See the methods on [`EntityClonerBuilder`] for more options. + /// See the methods on [`EntityClonerBuilder`] for more options. /// /// # Note /// @@ -2083,18 +2127,63 @@ impl<'a> EntityCommands<'a> { /// // Create a new entity and store its EntityCommands. /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// - /// // Create a clone of the first entity, but without ComponentB. - /// let mut entity_clone = entity.clone_and_spawn_with(|builder| { + /// // Create a clone of the entity with ComponentA but without ComponentB. + /// let mut entity_clone = entity.clone_and_spawn_with_opt_out(|builder| { /// builder.deny::(); /// }); /// } /// # bevy_ecs::system::assert_is_system(example_system); - pub fn clone_and_spawn_with( + pub fn clone_and_spawn_with_opt_out( + &mut self, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + ) -> EntityCommands<'_> { + let entity_clone = self.commands().spawn_empty().id(); + self.clone_with_opt_out(entity_clone, config); + EntityCommands { + commands: self.commands_mut().reborrow(), + entity: entity_clone, + } + } + + /// Spawns a clone of this entity and allows configuring cloning behavior + /// using [`EntityClonerBuilder`], returning the [`EntityCommands`] of the clone. + /// + /// The clone will receive only the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) and are + /// [allowed](EntityClonerBuilder::allow) in the `config`. + /// + /// See the methods on [`EntityClonerBuilder`] for more options. + /// + /// # Note + /// + /// If the original entity does not exist when this command is applied, + /// the returned entity will have no components. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Clone)] + /// struct ComponentA(u32); + /// #[derive(Component, Clone)] + /// struct ComponentB(u32); + /// + /// fn example_system(mut commands: Commands) { + /// // Create a new entity and store its EntityCommands. + /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); + /// + /// // Create a clone of the entity with ComponentA but without ComponentB. + /// let mut entity_clone = entity.clone_and_spawn_with_opt_in(|builder| { + /// builder.allow::(); + /// }); + /// } + /// # bevy_ecs::system::assert_is_system(example_system); + pub fn clone_and_spawn_with_opt_in( &mut self, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> EntityCommands<'_> { let entity_clone = self.commands().spawn_empty().id(); - self.clone_with(entity_clone, config); + self.clone_with_opt_in(entity_clone, config); EntityCommands { commands: self.commands_mut().reborrow(), entity: entity_clone, diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d29c3db4283ca..9b7f8eb551133 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -11,7 +11,7 @@ use crate::{ }, entity::{ ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, - EntityIdLocation, EntityLocation, + EntityIdLocation, EntityLocation, OptIn, OptOut, }, event::EntityEvent, lifecycle::{DESPAWN, REMOVE, REPLACE}, @@ -2672,10 +2672,12 @@ impl<'w> EntityWorldMut<'w> { /// Clones parts of an entity (components, observers, etc.) onto another entity, /// configured through [`EntityClonerBuilder`]. /// - /// By default, the other entity will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). + /// The other entity will receive all the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) except those that are + /// [denied](EntityClonerBuilder::deny) in the `config`. + /// + /// # Example /// - /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// # #[derive(Component, Clone, PartialEq, Debug)] @@ -2685,27 +2687,76 @@ impl<'w> EntityWorldMut<'w> { /// # let mut world = World::new(); /// # let entity = world.spawn((ComponentA, ComponentB)).id(); /// # let target = world.spawn_empty().id(); - /// world.entity_mut(entity).clone_with(target, |builder| { - /// builder.deny::(); + /// // Clone all components except ComponentA onto the target. + /// world.entity_mut(entity).clone_with_opt_out(target, |builder| { + /// builder.deny::(); + /// }); + /// # assert_eq!(world.get::(target), None); + /// # assert_eq!(world.get::(target), Some(&ComponentB)); + /// ``` + /// + /// See [`EntityClonerBuilder`] for more options. + /// + /// # Panics + /// + /// - If this entity has been despawned while this `EntityWorldMut` is still alive. + /// - If the target entity does not exist. + pub fn clone_with_opt_out( + &mut self, + target: Entity, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + ) -> &mut Self { + self.assert_not_despawned(); + + let mut builder = EntityCloner::build_opt_out(self.world); + config(&mut builder); + builder.clone_entity(self.entity, target); + + self.world.flush(); + self.update_location(); + self + } + + /// Clones parts of an entity (components, observers, etc.) onto another entity, + /// configured through [`EntityClonerBuilder`]. + /// + /// The other entity will receive only the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) and are + /// [allowed](EntityClonerBuilder::allow) in the `config`. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Component, Clone, PartialEq, Debug)] + /// # struct ComponentA; + /// # #[derive(Component, Clone, PartialEq, Debug)] + /// # struct ComponentB; + /// # let mut world = World::new(); + /// # let entity = world.spawn((ComponentA, ComponentB)).id(); + /// # let target = world.spawn_empty().id(); + /// // Clone only ComponentA onto the target. + /// world.entity_mut(entity).clone_with_opt_in(target, |builder| { + /// builder.allow::(); /// }); /// # assert_eq!(world.get::(target), Some(&ComponentA)); /// # assert_eq!(world.get::(target), None); /// ``` /// - /// See [`EntityClonerBuilder`] for more options. + /// See [`EntityClonerBuilder`] for more options. /// /// # Panics /// /// - If this entity has been despawned while this `EntityWorldMut` is still alive. /// - If the target entity does not exist. - pub fn clone_with( + pub fn clone_with_opt_in( &mut self, target: Entity, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloner::build(self.world); + let mut builder = EntityCloner::build_opt_in(self.world); config(&mut builder); builder.clone_entity(self.entity, target); @@ -2720,52 +2771,104 @@ impl<'w> EntityWorldMut<'w> { /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// /// To configure cloning behavior (such as only cloning certain components), - /// use [`EntityWorldMut::clone_and_spawn_with`]. + /// use [`EntityWorldMut::clone_and_spawn_with_opt_out`]/ + /// [`opt_in`](`EntityWorldMut::clone_and_spawn_with_opt_in`). /// /// # Panics /// /// If this entity has been despawned while this `EntityWorldMut` is still alive. pub fn clone_and_spawn(&mut self) -> Entity { - self.clone_and_spawn_with(|_| {}) + self.clone_and_spawn_with_opt_out(|_| {}) } /// Spawns a clone of this entity and allows configuring cloning behavior /// using [`EntityClonerBuilder`], returning the [`Entity`] of the clone. /// - /// By default, the clone will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). + /// The clone will receive all the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) except those that are + /// [denied](EntityClonerBuilder::deny) in the `config`. + /// + /// # Example /// - /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; + /// # let mut world = World::new(); + /// # let entity = world.spawn((ComponentA, ComponentB)).id(); /// # #[derive(Component, Clone, PartialEq, Debug)] /// # struct ComponentA; /// # #[derive(Component, Clone, PartialEq, Debug)] /// # struct ComponentB; + /// // Create a clone of an entity but without ComponentA. + /// let entity_clone = world.entity_mut(entity).clone_and_spawn_with_opt_out(|builder| { + /// builder.deny::(); + /// }); + /// # assert_eq!(world.get::(entity_clone), None); + /// # assert_eq!(world.get::(entity_clone), Some(&ComponentB)); + /// ``` + /// + /// See [`EntityClonerBuilder`] for more options. + /// + /// # Panics + /// + /// If this entity has been despawned while this `EntityWorldMut` is still alive. + pub fn clone_and_spawn_with_opt_out( + &mut self, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + ) -> Entity { + self.assert_not_despawned(); + + let entity_clone = self.world.entities.reserve_entity(); + self.world.flush(); + + let mut builder = EntityCloner::build_opt_out(self.world); + config(&mut builder); + builder.clone_entity(self.entity, entity_clone); + + self.world.flush(); + self.update_location(); + entity_clone + } + + /// Spawns a clone of this entity and allows configuring cloning behavior + /// using [`EntityClonerBuilder`], returning the [`Entity`] of the clone. + /// + /// The clone will receive only the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) and are + /// [allowed](EntityClonerBuilder::allow) in the `config`. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; /// # let mut world = World::new(); /// # let entity = world.spawn((ComponentA, ComponentB)).id(); - /// let entity_clone = world.entity_mut(entity).clone_and_spawn_with(|builder| { - /// builder.deny::(); + /// # #[derive(Component, Clone, PartialEq, Debug)] + /// # struct ComponentA; + /// # #[derive(Component, Clone, PartialEq, Debug)] + /// # struct ComponentB; + /// // Create a clone of an entity but only with ComponentA. + /// let entity_clone = world.entity_mut(entity).clone_and_spawn_with_opt_in(|builder| { + /// builder.allow::(); /// }); /// # assert_eq!(world.get::(entity_clone), Some(&ComponentA)); /// # assert_eq!(world.get::(entity_clone), None); /// ``` /// - /// See [`EntityClonerBuilder`] for more options. + /// See [`EntityClonerBuilder`] for more options. /// /// # Panics /// /// If this entity has been despawned while this `EntityWorldMut` is still alive. - pub fn clone_and_spawn_with( + pub fn clone_and_spawn_with_opt_in( &mut self, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> Entity { self.assert_not_despawned(); let entity_clone = self.world.entities.reserve_entity(); self.world.flush(); - let mut builder = EntityCloner::build(self.world); + let mut builder = EntityCloner::build_opt_in(self.world); config(&mut builder); builder.clone_entity(self.entity, entity_clone); @@ -2786,8 +2889,7 @@ impl<'w> EntityWorldMut<'w> { pub fn clone_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); - EntityCloner::build(self.world) - .deny_all() + EntityCloner::build_opt_in(self.world) .allow::() .clone_entity(self.entity, target); @@ -2809,8 +2911,7 @@ impl<'w> EntityWorldMut<'w> { pub fn move_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); - EntityCloner::build(self.world) - .deny_all() + EntityCloner::build_opt_in(self.world) .allow::() .move_components(true) .clone_entity(self.entity, target); @@ -5998,12 +6099,12 @@ mod tests { #[test] fn entity_world_mut_clone_with_move_and_require() { #[derive(Component, Clone, PartialEq, Debug)] - #[require(B)] + #[require(B(3))] struct A; #[derive(Component, Clone, PartialEq, Debug, Default)] #[require(C(3))] - struct B; + struct B(u32); #[derive(Component, Clone, PartialEq, Debug, Default)] #[require(D)] @@ -6013,22 +6114,25 @@ mod tests { struct D; let mut world = World::new(); - let entity_a = world.spawn(A).id(); + let entity_a = world.spawn((A, B(5))).id(); let entity_b = world.spawn_empty().id(); - world.entity_mut(entity_a).clone_with(entity_b, |builder| { - builder - .move_components(true) - .without_required_components(|builder| { - builder.deny::(); - }); - }); + world + .entity_mut(entity_a) + .clone_with_opt_in(entity_b, |builder| { + builder + .move_components(true) + .allow::() + .without_required_components(|builder| { + builder.allow::(); + }); + }); - assert_eq!(world.entity(entity_a).get::(), Some(&A)); - assert_eq!(world.entity(entity_b).get::(), None); + assert_eq!(world.entity(entity_a).get::(), None); + assert_eq!(world.entity(entity_b).get::(), Some(&A)); - assert_eq!(world.entity(entity_a).get::(), None); - assert_eq!(world.entity(entity_b).get::(), Some(&B)); + assert_eq!(world.entity(entity_a).get::(), Some(&B(5))); + assert_eq!(world.entity(entity_b).get::(), Some(&B(3))); assert_eq!(world.entity(entity_a).get::(), None); assert_eq!(world.entity(entity_b).get::(), Some(&C(3))); diff --git a/release-content/migration-guides/entity_cloner_builder_split.md b/release-content/migration-guides/entity_cloner_builder_split.md new file mode 100644 index 0000000000000..fa03b2ee367d9 --- /dev/null +++ b/release-content/migration-guides/entity_cloner_builder_split.md @@ -0,0 +1,73 @@ +--- +title: EntityClonerBuilder Split +pull_requests: [19649] +--- + +`EntityClonerBuilder` is now generic and has different methods depending on the generic. + +To get the wanted one, `EntityCloner::build` got split too: + +- `EntityCloner::build_opt_out` to get `EntityClonerBuilder` +- `EntityCloner::build_opt_in` to get `EntityClonerBuilder` + +The first is used to clone all components possible and optionally _opting out_ of some. +The second is used to only clone components as specified by _opting in_ for them. + +```rs +// 0.16 +let mut builder = EntityCloner.build(&mut world); +builder.allow_all().deny::(); +builder.clone_entity(source_entity, target_entity); + +let mut builder = EntityCloner.build(&mut world); +builder.deny_all().allow::(); +builder.clone_entity(source_entity, target_entity); + +// 0.17 +let mut builder = EntityCloner.build_opt_out(&mut world); +builder.deny::(); +builder.clone_entity(source_entity, target_entity); + +let mut builder = EntityCloner.build_opt_in(&mut world); +builder.allow::(); +builder.clone_entity(source_entity, target_entity); +``` + +Still, using `EntityClonerBuilder::finish` will return a non-generic `EntityCloner`. +This change is done because the behavior of the two is too different to share the same struct and same methods and mixing them caused bugs. + +The methods of the two builder types are different to 0.16 and to each other now: + +## Opt-Out variant + +- Still offers variants of the `deny` methods which now also includes one with a `BundleId` argument. +- No longer offers `allow` methods, you need to be exact with denying components. +- Offers now the `insert_mode` method to configure if components are cloned if they already exist at the target. +- Required components of denied components are no longer considered. Denying `A`, which requires `B`, does not imply `B` alone would not be useful at the target. So if you do not want to clone `B` too, you need to deny it explicitly. This also means there is no `without_required_components` method anymore as that would be redundant. +- It is now the other way around: Denying `A`, which is required _by_ `C`, will now also deny `C`. This can be bypassed with the new `without_required_by_components` method. + +## Opt-In variant + +- Still offers variants of the `allow` methods which now also includes one with a `BundleId` argument. +- No longer offers `deny` methods, you need to be exact with allowing components. +- Offers now `allow_if_new` method variants that only clone this component if the target does not contain it. If it does, required components of it will also not be cloned, except those that are also required by one that is actually cloned. +- Still offers the `without_required_components` method. + +## Common methods + +All other methods `EntityClonerBuilder` had in 0.16 are still available for both variants: + +- `with_default_clone_fn` +- `move_components` +- `clone_behavior` variants +- `linked_cloning` + +## Other affected APIs + +| 0.16 | 0.17 | +| - | - | +| `EntityWorldMut::clone_with` | `EntityWorldMut::clone_with_opt_out` `EntityWorldMut::clone_with_opt_in` | +| `EntityWorldMut::clone_and_spawn_with` | `EntityWorldMut::clone_and_spawn_with_opt_out` `EntityWorldMut::clone_and_spawn_with_opt_in` | +| `EntityCommands::clone_with` | `EntityCommands::clone_with_opt_out` `EntityCommands::clone_with_opt_in` | +| `EntityCommands::clone_and_spawn_with` | `EntityCommands::clone_and_spawn_with_opt_out` `EntityCommands::clone_and_spawn_with_opt_in` | +| `entity_command::clone_with` | `entity_command::clone_with_opt_out` `entity_command::clone_with_opt_in` |