Split EntityClonerBuilder
in OptOut
and OptIn
variants (#19649)
# Objective Further tests after #19326 showed that configuring `EntityCloner` with required components is bug prone and the current design has several weaknesses in it's API: - Mixing `EntityClonerBuilder::allow` and `EntityClonerBuilder::deny` requires extra care how to support that which has an impact on surrounding code that has to keep edge cases in mind. This is especially true for attempts to fix the following issues. There is no use-case known (to me) why someone would mix those. - A builder with `EntityClonerBuilder::allow_all` configuration tries to support required components like `EntityClonerBuilder::deny_all` does, but the meaning of that is conflicting with how you'd expect things to work: - If all components should be cloned except component `A`, do you also want to exclude required components of `A` too? Or are these also valid without `A` at the target entity? - If `EntityClonerBuilder::allow_all` should ignore required components and not add them to be filtered away, which purpose has `EntityClonerBuilder::without_required_components` for this cloner? - Other bugs found with the linked PR are: - Denying `A` also denies required components of `A` even when `A` does not exist at the source entity - Allowing `A` also allows required components of `A` even when `A` does not exist at the source entity - Adding `allow_if_new` filters to the cloner faces the same issues and require a common solution to dealing with source-archetype sensitive cloning Alternative to #19632 and #19635. # Solution `EntityClonerBuilder` is made generic and split into `EntityClonerBuilder<OptOut>` and `EntityClonerBuilder<OptIn>` For an overview of the changes, see the migration guide. It is generally a good idea to start a review of that. ## Algorithm The generic of `EntityClonerBuilder` contains the filter data that is needed to build and clone the entity components. As the filter needs to be borrowed mutably for the duration of the clone, the borrow checker forced me to separate the filter value and all other fields in `EntityCloner`. The latter are now in the `EntityClonerConfig` struct. This caused many changed LOC, sorry. To make reviewing easier: 1. Check the migration guide 2. Many methods of `EntityCloner` now just call identitcal `EntityClonerConfig` methods with a mutable borrow of the filter 3. Check `EntityClonerConfig::clone_entity_internal` which changed a bit regarding the filter usage that is now trait powered (`CloneByFilter`) to support `OptOut`, `OptIn` and `EntityClonerFilter` (an enum combining the first two) 4. Check `OptOut` type that no longer tracks required components but has a `insert_mode` field 5. Check `OptIn` type that has the most logic changes # Testing I added a bunch of tests that cover the new logic parts and the fixed issues. Benchmarks are in a comment a bit below which shows ~4% to 9% regressions, but it varied wildly for me. For example at one run the reflection-based clonings were on-par with main while the other are not, and redoing that swapped the situation for both. It would be really cool if I could get some hints how to get better benchmark results or if you could run them on your machine too. Just be aware this is not a Performance PR but a Bugfix PR, even if I smuggled in some more functionalities. So doing changes to `EntityClonerBuilder` is kind of required here which might make us bite the bullet. --------- Co-authored-by: eugineerd <70062110+eugineerd@users.noreply.github.com>
This commit is contained in:
parent
a7fdd6fc6f
commit
546711b807
@ -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<const N: usize>(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C2(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 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);
|
||||
|
||||
#[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<B: Bundle + GetTypeRegistration>(
|
||||
world: &mut World,
|
||||
@ -71,7 +45,7 @@ fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
||||
// this bundle are saved.
|
||||
let component_ids: Vec<_> = world.register_bundle::<B>().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<B: Bundle + GetTypeRegistration>(
|
||||
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: Bundle + Default + GetTypeRegistration>(
|
||||
b: &mut Bencher,
|
||||
clone_via_reflect: bool,
|
||||
@ -114,8 +87,7 @@ fn bench_clone<B: Bundle + Default + GetTypeRegistration>(
|
||||
});
|
||||
}
|
||||
|
||||
/// 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<B: Bundle + Default + GetTypeRegistration>(
|
||||
let mut cloner = if clone_via_reflect {
|
||||
reflection_cloner::<B>(&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<B: Bundle + Default + GetTypeRegistration>(
|
||||
|
||||
// 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::<ComplexBundle>(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::<C1>(b, 50, 1, clone_via_reflect);
|
||||
bench_clone_hierarchy::<C<1>>(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::<C1>(b, 1, 50, clone_via_reflect);
|
||||
bench_clone_hierarchy::<C<1>>(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::<ComplexBundle>(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<FilterScenario> 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: Bundle + Default>(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::<B>();
|
||||
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::<B>();
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInAllWithoutRequired => {
|
||||
target = spawn(true);
|
||||
let mut builder = EntityCloner::build_opt_in(&mut world);
|
||||
builder.without_required_components(|builder| {
|
||||
builder.allow::<B>();
|
||||
});
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInAllKeep(is_new) => {
|
||||
target = spawn(is_new);
|
||||
let mut builder = EntityCloner::build_opt_in(&mut world);
|
||||
builder.allow_if_new::<B>();
|
||||
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::<B>();
|
||||
});
|
||||
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<const N: usize>;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[component(clone_behavior = Ignore)]
|
||||
#[require(C::<N>)]
|
||||
struct R<const N: usize>;
|
||||
|
||||
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::<RequiringBundle>(b, scenario);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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<Filter: CloneByFilter> 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);
|
||||
|
||||
|
@ -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<OptOut>::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,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptOut>) + Send + Sync + 'static,
|
||||
) -> impl EntityCommand {
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.clone_with(target, config);
|
||||
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<OptIn>::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<OptIn>) + Send + Sync + 'static,
|
||||
) -> impl EntityCommand {
|
||||
move |mut entity: EntityWorldMut| {
|
||||
entity.clone_with_opt_in(target, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<OptOut>`] 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::<ComponentB>();
|
||||
/// });
|
||||
/// }
|
||||
@ -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<OptOut>) + 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<OptIn>`] 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::<ComponentA>();
|
||||
/// });
|
||||
/// }
|
||||
/// # 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<OptIn>) + 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).
|
||||
/// 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`.
|
||||
///
|
||||
/// To exclude specific components, use [`EntityClonerBuilder::deny`].
|
||||
/// To only include specific components, use [`EntityClonerBuilder::deny_all`]
|
||||
/// followed by [`EntityClonerBuilder::allow`].
|
||||
///
|
||||
/// See the methods on [`EntityClonerBuilder`] for more options.
|
||||
/// See the methods on [`EntityClonerBuilder<OptOut>`] 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::<ComponentB>();
|
||||
/// });
|
||||
/// }
|
||||
/// # 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,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptOut>) + Send + Sync + 'static,
|
||||
) -> EntityCommands<'_> {
|
||||
let entity_clone = self.commands().spawn_empty().id();
|
||||
self.clone_with(entity_clone, config);
|
||||
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<OptIn>`] 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::<ComponentA>();
|
||||
/// });
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(example_system);
|
||||
pub fn clone_and_spawn_with_opt_in(
|
||||
&mut self,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptIn>) + Send + Sync + 'static,
|
||||
) -> EntityCommands<'_> {
|
||||
let entity_clone = self.commands().spawn_empty().id();
|
||||
self.clone_with_opt_in(entity_clone, config);
|
||||
EntityCommands {
|
||||
commands: self.commands_mut().reborrow(),
|
||||
entity: entity_clone,
|
||||
|
@ -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::<ComponentB>();
|
||||
/// // Clone all components except ComponentA onto the target.
|
||||
/// world.entity_mut(entity).clone_with_opt_out(target, |builder| {
|
||||
/// builder.deny::<ComponentA>();
|
||||
/// });
|
||||
/// # assert_eq!(world.get::<ComponentA>(target), Some(&ComponentA));
|
||||
/// # assert_eq!(world.get::<ComponentB>(target), None);
|
||||
/// # assert_eq!(world.get::<ComponentA>(target), None);
|
||||
/// # assert_eq!(world.get::<ComponentB>(target), Some(&ComponentB));
|
||||
/// ```
|
||||
///
|
||||
/// See [`EntityClonerBuilder`] for more options.
|
||||
/// See [`EntityClonerBuilder<OptOut>`] 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_out(
|
||||
&mut self,
|
||||
target: Entity,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptOut>) + Send + Sync + 'static,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
|
||||
let mut builder = EntityCloner::build(self.world);
|
||||
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::<ComponentA>();
|
||||
/// });
|
||||
/// # assert_eq!(world.get::<ComponentA>(target), Some(&ComponentA));
|
||||
/// # assert_eq!(world.get::<ComponentB>(target), None);
|
||||
/// ```
|
||||
///
|
||||
/// See [`EntityClonerBuilder<OptIn>`] 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_in(
|
||||
&mut self,
|
||||
target: Entity,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptIn>) + Send + Sync + 'static,
|
||||
) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
|
||||
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;
|
||||
/// # 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::<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::<ComponentA>();
|
||||
/// });
|
||||
/// # assert_eq!(world.get::<ComponentA>(entity_clone), Some(&ComponentA));
|
||||
/// # assert_eq!(world.get::<ComponentB>(entity_clone), None);
|
||||
/// # assert_eq!(world.get::<ComponentA>(entity_clone), None);
|
||||
/// # assert_eq!(world.get::<ComponentB>(entity_clone), Some(&ComponentB));
|
||||
/// ```
|
||||
///
|
||||
/// See [`EntityClonerBuilder`] for more options.
|
||||
/// See [`EntityClonerBuilder<OptOut>`] 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_out(
|
||||
&mut self,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptOut>) + 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_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();
|
||||
/// # #[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::<ComponentA>();
|
||||
/// });
|
||||
/// # assert_eq!(world.get::<ComponentA>(entity_clone), Some(&ComponentA));
|
||||
/// # assert_eq!(world.get::<ComponentB>(entity_clone), None);
|
||||
/// ```
|
||||
///
|
||||
/// See [`EntityClonerBuilder<OptIn>`] for more options.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If this entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
pub fn clone_and_spawn_with_opt_in(
|
||||
&mut self,
|
||||
config: impl FnOnce(&mut EntityClonerBuilder<OptIn>) + 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_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<B: Bundle>(&mut self, target: Entity) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
|
||||
EntityCloner::build(self.world)
|
||||
.deny_all()
|
||||
EntityCloner::build_opt_in(self.world)
|
||||
.allow::<B>()
|
||||
.clone_entity(self.entity, target);
|
||||
|
||||
@ -2809,8 +2911,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
pub fn move_components<B: Bundle>(&mut self, target: Entity) -> &mut Self {
|
||||
self.assert_not_despawned();
|
||||
|
||||
EntityCloner::build(self.world)
|
||||
.deny_all()
|
||||
EntityCloner::build_opt_in(self.world)
|
||||
.allow::<B>()
|
||||
.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::<A>();
|
||||
});
|
||||
});
|
||||
world
|
||||
.entity_mut(entity_a)
|
||||
.clone_with_opt_in(entity_b, |builder| {
|
||||
builder
|
||||
.move_components(true)
|
||||
.allow::<C>()
|
||||
.without_required_components(|builder| {
|
||||
builder.allow::<A>();
|
||||
});
|
||||
});
|
||||
|
||||
assert_eq!(world.entity(entity_a).get::<A>(), Some(&A));
|
||||
assert_eq!(world.entity(entity_b).get::<A>(), None);
|
||||
assert_eq!(world.entity(entity_a).get::<A>(), None);
|
||||
assert_eq!(world.entity(entity_b).get::<A>(), Some(&A));
|
||||
|
||||
assert_eq!(world.entity(entity_a).get::<B>(), None);
|
||||
assert_eq!(world.entity(entity_b).get::<B>(), Some(&B));
|
||||
assert_eq!(world.entity(entity_a).get::<B>(), Some(&B(5)));
|
||||
assert_eq!(world.entity(entity_b).get::<B>(), Some(&B(3)));
|
||||
|
||||
assert_eq!(world.entity(entity_a).get::<C>(), None);
|
||||
assert_eq!(world.entity(entity_b).get::<C>(), Some(&C(3)));
|
||||
|
@ -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<OptOut>`
|
||||
- `EntityCloner::build_opt_in` to get `EntityClonerBuilder<OptIn>`
|
||||
|
||||
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::<ComponentThatShouldNotBeCloned>();
|
||||
builder.clone_entity(source_entity, target_entity);
|
||||
|
||||
let mut builder = EntityCloner.build(&mut world);
|
||||
builder.deny_all().allow::<ComponentThatShouldBeCloned>();
|
||||
builder.clone_entity(source_entity, target_entity);
|
||||
|
||||
// 0.17
|
||||
let mut builder = EntityCloner.build_opt_out(&mut world);
|
||||
builder.deny::<ComponentThatShouldNotBeCloned>();
|
||||
builder.clone_entity(source_entity, target_entity);
|
||||
|
||||
let mut builder = EntityCloner.build_opt_in(&mut world);
|
||||
builder.allow::<ComponentThatShouldBeCloned>();
|
||||
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` |
|
Loading…
Reference in New Issue
Block a user