From 3c8fae23904e70e5e4c275ab89401b9de6ecb7d7 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 6 Feb 2025 14:13:41 -0800 Subject: [PATCH] Improved Entity Mapping and Cloning (#17687) Fixes #17535 Bevy's approach to handling "entity mapping" during spawning and cloning needs some work. The addition of [Relations](https://github.com/bevyengine/bevy/pull/17398) both [introduced a new "duplicate entities" bug when spawning scenes in the scene system](#17535) and made the weaknesses of the current mapping system exceedingly clear: 1. Entity mapping requires _a ton_ of boilerplate (implement or derive VisitEntities and VisitEntitesMut, then register / reflect MapEntities). Knowing the incantation is challenging and if you forget to do it in part or in whole, spawning subtly breaks. 2. Entity mapping a spawned component in scenes incurs unnecessary overhead: look up ReflectMapEntities, create a _brand new temporary instance_ of the component using FromReflect, map the entities in that instance, and then apply that on top of the actual component using reflection. We can do much better. Additionally, while our new [Entity cloning system](https://github.com/bevyengine/bevy/pull/16132) is already pretty great, it has some areas we can make better: * It doesn't expose semantic info about the clone (ex: ignore or "clone empty"), meaning we can't key off of that in places where it would be useful, such as scene spawning. Rather than duplicating this info across contexts, I think it makes more sense to add that info to the clone system, especially given that we'd like to use cloning code in some of our spawning scenarios. * EntityCloner is currently built in a way that prioritizes a single entity clone * EntityCloner's recursive cloning is built to be done "inside out" in a parallel context (queue commands that each have a clone of EntityCloner). By making EntityCloner the orchestrator of the clone we can remove internal arcs, improve the clarity of the code, make EntityCloner mutable again, and simplify the builder code. * EntityCloner does not currently take into account entity mapping. This is necessary to do true "bullet proof" cloning, would allow us to unify the per-component scene spawning and cloning UX, and ultimately would allow us to use EntityCloner in place of raw reflection for scenes like `Scene(World)` (which would give us a nice performance boost: fewer archetype moves, less reflection overhead). ## Solution ### Improved Entity Mapping First, components now have first-class "entity visiting and mapping" behavior: ```rust #[derive(Component, Reflect)] #[reflect(Component)] struct Inventory { size: usize, #[entities] items: Vec, } ``` Any field with the `#[entities]` annotation will be viewable and mappable when cloning and spawning scenes. Compare that to what was required before! ```rust #[derive(Component, Reflect, VisitEntities, VisitEntitiesMut)] #[reflect(Component, MapEntities)] struct Inventory { #[visit_entities(ignore)] size: usize, items: Vec, } ``` Additionally, for relationships `#[entities]` is implied, meaning this "just works" in scenes and cloning: ```rust #[derive(Component, Reflect)] #[relationship(relationship_target = Children)] #[reflect(Component)] struct ChildOf(pub Entity); ``` Note that Component _does not_ implement `VisitEntities` directly. Instead, it has `Component::visit_entities` and `Component::visit_entities_mut` methods. This is for a few reasons: 1. We cannot implement `VisitEntities for C: Component` because that would conflict with our impl of VisitEntities for anything that implements `IntoIterator`. Preserving that impl is more important from a UX perspective. 2. We should not implement `Component: VisitEntities` VisitEntities in the Component derive, as that would increase the burden of manual Component trait implementors. 3. Making VisitEntitiesMut directly callable for components would make it easy to invalidate invariants defined by a component author. By putting it in the `Component` impl, we can make it harder to call naturally / unavailable to autocomplete using `fn visit_entities_mut(this: &mut Self, ...)`. `ReflectComponent::apply_or_insert` is now `ReflectComponent::apply_or_insert_mapped`. By moving mapping inside this impl, we remove the need to go through the reflection system to do entity mapping, meaning we no longer need to create a clone of the target component, map the entities in that component, and patch those values on top. This will make spawning mapped entities _much_ faster (The default `Component::visit_entities_mut` impl is an inlined empty function, so it will incur no overhead for unmapped entities). ### The Bug Fix To solve #17535, spawning code now skips entities with the new `ComponentCloneBehavior::Ignore` and `ComponentCloneBehavior::RelationshipTarget` variants (note RelationshipTarget is a temporary "workaround" variant that allows scenes to skip these components. This is a temporary workaround that can be removed as these cases should _really_ be using EntityCloner logic, which should be done in a followup PR. When that is done, `ComponentCloneBehavior::RelationshipTarget` can be merged into the normal `ComponentCloneBehavior::Custom`). ### Improved Cloning * `Option` has been replaced by `ComponentCloneBehavior`, which encodes additional intent and context (ex: `Default`, `Ignore`, `Custom`, `RelationshipTarget` (this last one is temporary)). * Global per-world entity cloning configuration has been removed. This felt overly complicated, increased our API surface, and felt too generic. Each clone context can have different requirements (ex: what a user wants in a specific system, what a scene spawner wants, etc). I'd prefer to see how far context-specific EntityCloners get us first. * EntityCloner's internals have been reworked to remove Arcs and make it mutable. * EntityCloner is now directly stored on EntityClonerBuilder, simplifying the code somewhat * EntityCloner's "bundle scratch" pattern has been moved into the new BundleScratch type, improving its usability and making it usable in other contexts (such as future cross-world cloning code). Currently this is still private, but with some higher level safe APIs it could be used externally for making dynamic bundles * EntityCloner's recursive cloning behavior has been "externalized". It is now responsible for orchestrating recursive clones, meaning it no longer needs to be sharable/clone-able across threads / read-only. * EntityCloner now does entity mapping during clones, like scenes do. This gives behavior parity and also makes it more generically useful. * `RelatonshipTarget::RECURSIVE_SPAWN` is now `RelationshipTarget::LINKED_SPAWN`, and this field is used when cloning relationship targets to determine if cloning should happen recursively. The new `LINKED_SPAWN` term was picked to make it more generically applicable across spawning and cloning scenarios. ## Next Steps * I think we should adapt EntityCloner to support cross world cloning. I think this PR helps set the stage for that by making the internals slightly more generalized. We could have a CrossWorldEntityCloner that reuses a lot of this infrastructure. * Once we support cross world cloning, we should use EntityCloner to spawn `Scene(World)` scenes. This would yield significant performance benefits (no archetype moves, less reflection overhead). --------- Co-authored-by: eugineerd <70062110+eugineerd@users.noreply.github.com> Co-authored-by: Alice Cecile --- benches/benches/bevy_ecs/entity_cloning.rs | 53 +- crates/bevy_animation/src/lib.rs | 13 +- crates/bevy_ecs/macros/src/component.rs | 182 +++- crates/bevy_ecs/macros/src/lib.rs | 5 +- crates/bevy_ecs/src/component.rs | 250 +++-- crates/bevy_ecs/src/entity/clone_entities.rs | 881 +++++++++++------- crates/bevy_ecs/src/entity/map_entities.rs | 85 +- crates/bevy_ecs/src/hierarchy.rs | 29 +- crates/bevy_ecs/src/lib.rs | 98 +- .../bevy_ecs/src/observer/entity_observer.rs | 47 +- crates/bevy_ecs/src/observer/mod.rs | 2 +- crates/bevy_ecs/src/reflect/bundle.rs | 39 +- crates/bevy_ecs/src/reflect/component.rs | 51 +- crates/bevy_ecs/src/relationship/mod.rs | 38 +- .../relationship_source_collection.rs | 6 +- .../src/system/commands/entity_command.rs | 6 +- crates/bevy_ecs/src/system/commands/mod.rs | 25 +- crates/bevy_ecs/src/world/entity_ref.rs | 54 +- crates/bevy_ecs/src/world/mod.rs | 37 +- crates/bevy_mesh/src/skinning.rs | 20 +- crates/bevy_scene/src/dynamic_scene.rs | 43 +- crates/bevy_scene/src/lib.rs | 1 - crates/bevy_scene/src/scene.rs | 29 +- crates/bevy_scene/src/serde.rs | 10 +- examples/ecs/dynamic.rs | 5 +- examples/ecs/immutable_components.rs | 5 +- examples/stress_tests/many_components.rs | 3 +- 27 files changed, 1240 insertions(+), 777 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 92687cf678..7c474cc4f8 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -2,7 +2,8 @@ use core::hint::black_box; use benches::bench; use bevy_ecs::bundle::Bundle; -use bevy_ecs::component::ComponentCloneHandler; +use bevy_ecs::component::ComponentCloneBehavior; +use bevy_ecs::entity::EntityCloner; use bevy_ecs::hierarchy::ChildOf; use bevy_ecs::reflect::AppTypeRegistry; use bevy_ecs::{component::Component, world::World}; @@ -52,7 +53,10 @@ 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 /// use the [`Reflect`] trait instead of [`Clone`]. -fn set_reflect_clone_handler(world: &mut World) { +fn reflection_cloner( + world: &mut World, + recursive: bool, +) -> EntityCloner { // Get mutable access to the type registry, creating it if it does not exist yet. let registry = world.get_resource_or_init::(); @@ -67,12 +71,15 @@ fn set_reflect_clone_handler(world: &mut World) // this bundle are saved. let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); - let clone_handlers = world.get_component_clone_handlers_mut(); + let mut builder = EntityCloner::build(world); // Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`. for component in component_ids { - clone_handlers.set_component_handler(component, ComponentCloneHandler::reflect_handler()); + builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect()); } + builder.recursive(recursive); + + builder.finish() } /// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a @@ -91,18 +98,18 @@ fn bench_clone( ) { let mut world = World::default(); - if clone_via_reflect { - set_reflect_clone_handler::(&mut world); - } + let mut cloner = if clone_via_reflect { + reflection_cloner::(&mut world, false) + } else { + EntityCloner::default() + }; // Spawn the first entity, which will be cloned in the benchmark routine. let id = world.spawn(B::default()).id(); b.iter(|| { - // Queue the command to clone the entity. - world.commands().entity(black_box(id)).clone_and_spawn(); - - // Run the command. + // clones the given entity + cloner.spawn_clone(&mut world, black_box(id)); world.flush(); }); } @@ -125,9 +132,15 @@ fn bench_clone_hierarchy( ) { let mut world = World::default(); - if clone_via_reflect { - set_reflect_clone_handler::(&mut world); - } + let mut cloner = if clone_via_reflect { + reflection_cloner::(&mut world, true) + } else { + let mut builder = EntityCloner::build(&mut world); + builder.recursive(true); + builder.finish() + }; + + // Make the clone command recursive, so children are cloned as well. // Spawn the first entity, which will be cloned in the benchmark routine. let id = world.spawn(B::default()).id(); @@ -148,18 +161,8 @@ fn bench_clone_hierarchy( } } - // Flush all `set_parent()` commands. - world.flush(); - b.iter(|| { - world - .commands() - .entity(black_box(id)) - .clone_and_spawn_with(|builder| { - // Make the clone command recursive, so children are cloned as well. - builder.recursive(true); - }); - + cloner.spawn_clone(&mut world, black_box(id)); world.flush(); }); } diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index aeaa0d0ab4..8663ea3f3f 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -33,12 +33,7 @@ use crate::{ use bevy_app::{Animation, App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, AssetEvents, Assets}; -use bevy_ecs::{ - entity::{VisitEntities, VisitEntitiesMut}, - prelude::*, - reflect::{ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut}, - world::EntityMutExcept, -}; +use bevy_ecs::{prelude::*, world::EntityMutExcept}; use bevy_math::FloatOrd; use bevy_platform_support::{collections::HashMap, hash::NoOpHash}; use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; @@ -207,16 +202,16 @@ impl Hash for AnimationTargetId { /// Note that each entity can only be animated by one animation player at a /// time. However, you can change [`AnimationTarget`]'s `player` property at /// runtime to change which player is responsible for animating the entity. -#[derive(Clone, Copy, Component, Reflect, VisitEntities, VisitEntitiesMut)] -#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] +#[derive(Clone, Copy, Component, Reflect)] +#[reflect(Component)] pub struct AnimationTarget { /// The ID of this animation target. /// /// Typically, this is derived from the path. - #[visit_entities(ignore)] pub id: AnimationTargetId, /// The entity containing the [`AnimationPlayer`]. + #[entities] pub player: Entity, } diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 652f0a9d35..76732c0b89 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -1,6 +1,6 @@ use proc_macro::{TokenStream, TokenTree}; use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote, ToTokens}; use std::collections::HashSet; use syn::{ parenthesized, @@ -9,8 +9,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Comma, Paren}, - Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, LitStr, Path, Result, - Token, Visibility, + Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, Index, LitStr, Member, + Path, Result, Token, Visibility, }; pub fn derive_event(input: TokenStream) -> TokenStream { @@ -51,6 +51,8 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { }) } +const ENTITIES_ATTR: &str = "entities"; + pub fn derive_component(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); @@ -69,6 +71,8 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Err(err) => err.into_compile_error().into(), }; + let visit_entities = visit_entities(&ast.data, &bevy_ecs_path, relationship.is_some()); + let storage = storage_path(&bevy_ecs_path, attrs.storage); let on_add_path = attrs.on_add.map(|path| path.to_token_stream()); @@ -117,12 +121,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let on_despawn_path = if attrs .relationship_target - .is_some_and(|target| target.despawn_descendants) + .is_some_and(|target| target.linked_spawn) { if attrs.on_despawn.is_some() { return syn::Error::new( ast.span(), - "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the despawn_descendants attribute", + "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute", ) .into_compile_error() .into(); @@ -202,12 +206,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .then_some(quote! { #bevy_ecs_path::component::Immutable }) .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); - let clone_handler = if relationship_target.is_some() { - quote!(#bevy_ecs_path::component::ComponentCloneHandler::ignore()) + let clone_behavior = if relationship_target.is_some() { + quote!(#bevy_ecs_path::component::ComponentCloneBehavior::RelationshipTarget(#bevy_ecs_path::relationship::clone_relationship_target::)) } else { quote!( - use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; - (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()).get_component_clone_handler() + use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone}; + (&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::::default()).default_clone_behavior() ) }; @@ -238,9 +242,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #on_remove #on_despawn - fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { - #clone_handler + fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior { + #clone_behavior } + + #visit_entities } #relationship @@ -249,6 +255,144 @@ pub fn derive_component(input: TokenStream) -> TokenStream { }) } +fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> TokenStream2 { + match data { + Data::Struct(DataStruct { ref fields, .. }) => { + let mut visited_fields = Vec::new(); + let mut visited_indices = Vec::new(); + match fields { + Fields::Named(fields) => { + for field in &fields.named { + if field + .attrs + .iter() + .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) + { + if let Some(ident) = field.ident.clone() { + visited_fields.push(ident); + } + } + } + } + Fields::Unnamed(fields) => { + for (index, field) in fields.unnamed.iter().enumerate() { + if index == 0 && is_relationship { + visited_indices.push(Index::from(0)); + } else if field + .attrs + .iter() + .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) + { + visited_indices.push(Index::from(index)); + } + } + } + Fields::Unit => {} + } + + if visited_fields.is_empty() && visited_indices.is_empty() { + TokenStream2::new() + } else { + let visit = visited_fields + .iter() + .map(|field| quote!(this.#field.visit_entities(&mut func);)) + .chain( + visited_indices + .iter() + .map(|index| quote!(this.#index.visit_entities(&mut func);)), + ); + let visit_mut = visited_fields + .iter() + .map(|field| quote!(this.#field.visit_entities_mut(&mut func);)) + .chain( + visited_indices + .iter() + .map(|index| quote!(this.#index.visit_entities_mut(&mut func);)), + ); + quote!( + fn visit_entities(this: &Self, mut func: impl FnMut(Entity)) { + use #bevy_ecs_path::entity::VisitEntities; + #(#visit)* + } + + fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut Entity)) { + use #bevy_ecs_path::entity::VisitEntitiesMut; + #(#visit_mut)* + } + ) + } + } + Data::Enum(data_enum) => { + let mut has_visited_fields = false; + let mut visit_variants = Vec::with_capacity(data_enum.variants.len()); + let mut visit_variants_mut = Vec::with_capacity(data_enum.variants.len()); + for variant in &data_enum.variants { + let mut variant_fields = Vec::new(); + let mut variant_fields_mut = Vec::new(); + + let mut visit_variant_fields = Vec::new(); + let mut visit_variant_fields_mut = Vec::new(); + + for (index, field) in variant.fields.iter().enumerate() { + if field + .attrs + .iter() + .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) + { + has_visited_fields = true; + let field_member = ident_or_index(field.ident.as_ref(), index); + let field_ident = format_ident!("field_{}", field_member); + + variant_fields.push(quote!(#field_member: ref #field_ident)); + variant_fields_mut.push(quote!(#field_member: ref mut #field_ident)); + + visit_variant_fields.push(quote!(#field_ident.visit_entities(&mut func);)); + visit_variant_fields_mut + .push(quote!(#field_ident.visit_entities_mut(&mut func);)); + } + } + + let ident = &variant.ident; + visit_variants.push(quote!(Self::#ident {#(#variant_fields,)* ..} => { + #(#visit_variant_fields)* + })); + visit_variants_mut.push(quote!(Self::#ident {#(#variant_fields_mut,)* ..} => { + #(#visit_variant_fields_mut)* + })); + } + if has_visited_fields { + quote!( + fn visit_entities(this: &Self, mut func: impl FnMut(Entity)) { + use #bevy_ecs_path::entity::VisitEntities; + match this { + #(#visit_variants,)* + _ => {} + } + } + + fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut Entity)) { + use #bevy_ecs_path::entity::VisitEntitiesMut; + match this { + #(#visit_variants_mut,)* + _ => {} + } + } + ) + } else { + TokenStream2::new() + } + } + Data::Union(_) => TokenStream2::new(), + } +} + +pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member { + ident.map_or_else( + || Member::Unnamed(index.into()), + |ident| Member::Named(ident.clone()), + ) +} + pub fn document_required_components(attr: TokenStream, item: TokenStream) -> TokenStream { let paths = parse_macro_input!(attr with Punctuated::::parse_terminated) .iter() @@ -326,7 +470,7 @@ struct Relationship { struct RelationshipTarget { relationship: Ident, - despawn_descendants: bool, + linked_spawn: bool, } // values for `storage` attribute @@ -468,18 +612,18 @@ impl Parse for Relationship { impl Parse for RelationshipTarget { fn parse(input: syn::parse::ParseStream) -> Result { let mut relationship_ident = None; - let mut despawn_descendants_exists = false; + let mut linked_spawn_exists = false; syn::custom_keyword!(relationship); - syn::custom_keyword!(despawn_descendants); + syn::custom_keyword!(linked_spawn); let mut done = false; loop { if input.peek(relationship) { input.parse::()?; input.parse::()?; relationship_ident = Some(input.parse::()?); - } else if input.peek(despawn_descendants) { - input.parse::()?; - despawn_descendants_exists = true; + } else if input.peek(linked_spawn) { + input.parse::()?; + linked_spawn_exists = true; } else { done = true; } @@ -494,7 +638,7 @@ impl Parse for RelationshipTarget { let relationship = relationship_ident.ok_or_else(|| syn::Error::new(input.span(), "RelationshipTarget derive must specify a relationship via #[relationship_target(relationship = X)"))?; Ok(RelationshipTarget { relationship, - despawn_descendants: despawn_descendants_exists, + linked_spawn: linked_spawn_exists, }) } } @@ -586,8 +730,10 @@ fn derive_relationship_target( let relationship = &relationship_target.relationship; let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let linked_spawn = relationship_target.linked_spawn; Ok(Some(quote! { impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause { + const LINKED_SPAWN: bool = #linked_spawn; type Relationship = #relationship; type Collection = #collection; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 2c6d79a03e..2c06ad1fe6 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -587,7 +587,10 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } -#[proc_macro_derive(Component, attributes(component, relationship, relationship_target))] +#[proc_macro_derive( + Component, + attributes(component, relationship, relationship_target, entities) +)] pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c8fedf4743..89e7788e01 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -9,7 +9,7 @@ use crate::{ query::DebugCheckedUnwrap, resource::Resource, storage::{SparseSetIndex, SparseSets, Table, TableRow}, - system::{Local, SystemParam}, + system::{Commands, Local, SystemParam}, world::{DeferredWorld, FromWorld, World}, }; #[cfg(feature = "bevy_reflect")] @@ -179,10 +179,6 @@ pub use bevy_ecs_macros::require; /// } /// /// # let mut world = World::default(); -/// // This will implicitly also insert C with the init_c() constructor -/// let id = world.spawn(A).id(); -/// assert_eq!(&C(10), world.entity(id).get::().unwrap()); -/// /// // This will implicitly also insert C with the `|| C(20)` constructor closure /// let id = world.spawn(B).id(); /// assert_eq!(&C(20), world.entity(id).get::().unwrap()); @@ -446,10 +442,20 @@ pub trait Component: Send + Sync + 'static { /// Called when registering this component, allowing to override clone function (or disable cloning altogether) for this component. /// - /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::default_handler() + /// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority. + #[inline] + fn clone_behavior() -> ComponentCloneBehavior { + ComponentCloneBehavior::Default } + + /// Visits entities stored on the component. + #[inline] + fn visit_entities(_this: &Self, _f: impl FnMut(Entity)) {} + + /// Returns pointers to every entity stored on the component. This will be used to remap entity references when this entity + /// is cloned. + #[inline] + fn visit_entities_mut(_this: &mut Self, _f: impl FnMut(&mut Entity)) {} } mod private { @@ -793,6 +799,12 @@ impl ComponentInfo { self.descriptor.mutable } + /// Returns [`ComponentCloneBehavior`] of the current component. + #[inline] + pub fn clone_behavior(&self) -> &ComponentCloneBehavior { + &self.descriptor.clone_behavior + } + /// Returns the [`TypeId`] of the underlying component type. /// Returns `None` if the component does not correspond to a Rust type. #[inline] @@ -949,6 +961,7 @@ pub struct ComponentDescriptor { // None if the underlying type doesn't need to be dropped drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, + clone_behavior: ComponentCloneBehavior, } // We need to ignore the `drop` field in our `Debug` impl @@ -961,6 +974,7 @@ impl Debug for ComponentDescriptor { .field("type_id", &self.type_id) .field("layout", &self.layout) .field("mutable", &self.mutable) + .field("clone_behavior", &self.clone_behavior) .finish() } } @@ -986,6 +1000,7 @@ impl ComponentDescriptor { layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: T::Mutability::MUTABLE, + clone_behavior: T::clone_behavior(), } } @@ -1000,6 +1015,7 @@ impl ComponentDescriptor { layout: Layout, drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, + clone_behavior: ComponentCloneBehavior, ) -> Self { Self { name: name.into(), @@ -1009,6 +1025,7 @@ impl ComponentDescriptor { layout, drop, mutable, + clone_behavior, } } @@ -1026,6 +1043,7 @@ impl ComponentDescriptor { layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, + clone_behavior: ComponentCloneBehavior::Default, } } @@ -1038,6 +1056,7 @@ impl ComponentDescriptor { layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, + clone_behavior: ComponentCloneBehavior::Default, } } @@ -1068,107 +1087,55 @@ impl ComponentDescriptor { } /// Function type that can be used to clone an entity. -pub type ComponentCloneFn = fn(&mut DeferredWorld, &mut ComponentCloneCtx); +pub type ComponentCloneFn = fn(&mut Commands, &mut ComponentCloneCtx); -/// A struct instructing which clone handler to use when cloning a component. -#[derive(Debug)] -pub struct ComponentCloneHandler(Option); - -impl ComponentCloneHandler { - /// Use the global default function to clone the component with this handler. - pub fn default_handler() -> Self { - Self(None) - } - - /// Do not clone the component. When a command to clone an entity is issued, component with this handler will be skipped. - pub fn ignore() -> Self { - Self(Some(component_clone_ignore)) - } +/// The clone behavior to use when cloning a [`Component`]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum ComponentCloneBehavior { + /// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`]) + #[default] + Default, + /// Do not clone this component. + Ignore, + /// Uses a custom [`ComponentCloneFn`]. + Custom(ComponentCloneFn), + /// Uses a [`ComponentCloneFn`] that produces an empty version of the given relationship target. + // TODO: this exists so that the current scene spawning code can know when to skip these components. + // When we move to actually cloning entities in scene spawning code, this should be removed in favor of Custom, as the + // distinction will no longer be necessary. + RelationshipTarget(ComponentCloneFn), +} +impl ComponentCloneBehavior { /// Set clone handler based on `Clone` trait. /// /// If set as a handler for a component that is not the same as the one used to create this handler, it will panic. - pub fn clone_handler() -> Self { - Self(Some(component_clone_via_clone::)) + pub fn clone() -> Self { + Self::Custom(component_clone_via_clone::) } /// Set clone handler based on `Reflect` trait. #[cfg(feature = "bevy_reflect")] - pub fn reflect_handler() -> Self { - Self(Some(component_clone_via_reflect)) + pub fn reflect() -> Self { + Self::Custom(component_clone_via_reflect) } - /// Set a custom handler for the component. - pub fn custom_handler(handler: ComponentCloneFn) -> Self { - Self(Some(handler)) + /// Returns the "global default" + pub fn global_default_fn() -> ComponentCloneFn { + #[cfg(feature = "bevy_reflect")] + return component_clone_via_reflect; + #[cfg(not(feature = "bevy_reflect"))] + return component_clone_ignore; } - /// Get [`ComponentCloneFn`] representing this handler or `None` if set to default handler. - pub fn get_handler(&self) -> Option { - self.0 - } -} - -/// A registry of component clone handlers. Allows to set global default and per-component clone function for all components in the world. -#[derive(Debug)] -pub struct ComponentCloneHandlers { - handlers: Vec>, - default_handler: ComponentCloneFn, -} - -impl ComponentCloneHandlers { - /// Sets the default handler for this registry. All components with [`default`](ComponentCloneHandler::default_handler) handler, as well as any component that does not have an - /// explicitly registered clone function will use this handler. - /// - /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. - pub fn set_default_handler(&mut self, handler: ComponentCloneFn) { - self.default_handler = handler; - } - - /// Returns the currently registered default handler. - pub fn get_default_handler(&self) -> ComponentCloneFn { - self.default_handler - } - - /// Sets a handler for a specific component. - /// - /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. - pub fn set_component_handler(&mut self, id: ComponentId, handler: ComponentCloneHandler) { - if id.0 >= self.handlers.len() { - self.handlers.resize(id.0 + 1, None); - } - self.handlers[id.0] = handler.0; - } - - /// Checks if the specified component is registered. If not, the component will use the default global handler. - /// - /// This will return an incorrect result if `id` did not come from the same world as `self`. - pub fn is_handler_registered(&self, id: ComponentId) -> bool { - self.handlers.get(id.0).is_some_and(Option::is_some) - } - - /// Gets a handler to clone a component. This can be one of the following: - /// - Custom clone function for this specific component. - /// - Default global handler. - /// - A [`component_clone_ignore`] (no cloning). - /// - /// This will return an incorrect result if `id` did not come from the same world as `self`. - pub fn get_handler(&self, id: ComponentId) -> ComponentCloneFn { - match self.handlers.get(id.0) { - Some(Some(handler)) => *handler, - Some(None) | None => self.default_handler, - } - } -} - -impl Default for ComponentCloneHandlers { - fn default() -> Self { - Self { - handlers: Default::default(), - #[cfg(feature = "bevy_reflect")] - default_handler: component_clone_via_reflect, - #[cfg(not(feature = "bevy_reflect"))] - default_handler: component_clone_ignore, + /// Resolves the [`ComponentCloneBehavior`] to a [`ComponentCloneFn`]. If [`ComponentCloneBehavior::Default`] is + /// specified, the given `default` function will be used. + pub fn resolve(&self, default: ComponentCloneFn) -> ComponentCloneFn { + match self { + ComponentCloneBehavior::Default => default, + ComponentCloneBehavior::Ignore => component_clone_ignore, + ComponentCloneBehavior::Custom(custom) + | ComponentCloneBehavior::RelationshipTarget(custom) => *custom, } } } @@ -1179,7 +1146,6 @@ pub struct Components { components: Vec, indices: TypeIdMap, resource_indices: TypeIdMap, - component_clone_handlers: ComponentCloneHandlers, } impl Components { @@ -1237,9 +1203,6 @@ impl Components { T::register_component_hooks(&mut info.hooks); info.required_components = required_components; - let clone_handler = T::get_component_clone_handler(); - self.component_clone_handlers - .set_component_handler(id, clone_handler); } id } @@ -1596,16 +1559,6 @@ impl Components { .map(|info| &mut info.required_by) } - /// Retrieves the [`ComponentCloneHandlers`]. Can be used to get clone functions for components. - pub fn get_component_clone_handlers(&self) -> &ComponentCloneHandlers { - &self.component_clone_handlers - } - - /// Retrieves a mutable reference to the [`ComponentCloneHandlers`]. Can be used to set and update clone functions for components. - pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers { - &mut self.component_clone_handlers - } - /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { @@ -2248,12 +2201,11 @@ pub fn enforce_no_required_components_recursion( } /// Component [clone handler function](ComponentCloneFn) implemented using the [`Clone`] trait. -/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for the specific component it is implemented for. +/// Can be [set](Component::clone_behavior) as clone handler for the specific component it is implemented for. /// It will panic if set as handler for any other component. /// -/// See [`ComponentCloneHandlers`] for more details. pub fn component_clone_via_clone( - _world: &mut DeferredWorld, + _commands: &mut Commands, ctx: &mut ComponentCloneCtx, ) { if let Some(component) = ctx.read_source_component::() { @@ -2262,7 +2214,7 @@ pub fn component_clone_via_clone( } /// Component [clone handler function](ComponentCloneFn) implemented using reflect. -/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for any registered component, +/// Can be [set](Component::clone_behavior) as clone handler for any registered component, /// but only reflected components will be cloned. /// /// To clone a component using this handler, the following must be true: @@ -2275,10 +2227,10 @@ pub fn component_clone_via_clone( /// /// If any of the conditions is not satisfied, the component will be skipped. /// -/// See [`EntityCloneBuilder`](crate::entity::EntityCloneBuilder) for details. +/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details. #[cfg(feature = "bevy_reflect")] -pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let Some(registry) = ctx.type_registry() else { +pub fn component_clone_via_reflect(commands: &mut Commands, ctx: &mut ComponentCloneCtx) { + let Some(app_registry) = ctx.type_registry().cloned() else { return; }; let Some(source_component_reflect) = ctx.read_source_component_reflect() else { @@ -2287,16 +2239,24 @@ pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut Componen let component_info = ctx.component_info(); // checked in read_source_component_reflect let type_id = component_info.type_id().unwrap(); - let registry = registry.read(); + let registry = app_registry.read(); // Try to clone using ReflectFromReflect if let Some(reflect_from_reflect) = registry.get_type_data::(type_id) { - if let Some(component) = + if let Some(mut component) = reflect_from_reflect.from_reflect(source_component_reflect.as_partial_reflect()) { + if let Some(reflect_component) = + registry.get_type_data::(type_id) + { + reflect_component.visit_entities_mut(&mut *component, &mut |entity| { + *entity = ctx.entity_mapper().get_mapped(*entity); + }); + } drop(registry); + ctx.write_target_component_reflect(component); return; } @@ -2316,14 +2276,36 @@ pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut Componen registry.get_type_data::(type_id) { let reflect_from_world = reflect_from_world.clone(); + let mut mapped_entities = Vec::new(); + if let Some(reflect_component) = + registry.get_type_data::(type_id) + { + reflect_component.visit_entities(source_component_reflect, &mut |entity| { + mapped_entities.push(entity); + }); + } let source_component_cloned = source_component_reflect.clone_value(); let component_layout = component_info.layout(); let target = ctx.target(); let component_id = ctx.component_id(); - world.commands().queue(move |world: &mut World| { + for entity in mapped_entities.iter_mut() { + *entity = ctx.entity_mapper().get_mapped(*entity); + } + drop(registry); + commands.queue(move |world: &mut World| { let mut component = reflect_from_world.from_world(world); assert_eq!(type_id, (*component).type_id()); component.apply(source_component_cloned.as_partial_reflect()); + if let Some(reflect_component) = app_registry + .read() + .get_type_data::(type_id) + { + let mut i = 0; + reflect_component.visit_entities_mut(&mut *component, &mut |entity| { + *entity = mapped_entities[i]; + i += 1; + }); + } // SAFETY: // - component_id is from the same world as target entity // - component is a valid value represented by component_id @@ -2341,14 +2323,14 @@ pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut Componen /// Noop implementation of component clone handler function. /// -/// See [`EntityCloneBuilder`](crate::entity::EntityCloneBuilder) for details. -pub fn component_clone_ignore(_world: &mut DeferredWorld, _ctx: &mut ComponentCloneCtx) {} +/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details. +pub fn component_clone_ignore(_commands: &mut Commands, _ctx: &mut ComponentCloneCtx) {} /// Wrapper for components clone specialization using autoderef. #[doc(hidden)] -pub struct ComponentCloneSpecializationWrapper(PhantomData); +pub struct DefaultCloneBehaviorSpecialization(PhantomData); -impl Default for ComponentCloneSpecializationWrapper { +impl Default for DefaultCloneBehaviorSpecialization { fn default() -> Self { Self(PhantomData) } @@ -2356,22 +2338,22 @@ impl Default for ComponentCloneSpecializationWrapper { /// Base trait for components clone specialization using autoderef. #[doc(hidden)] -pub trait ComponentCloneBase { - fn get_component_clone_handler(&self) -> ComponentCloneHandler; +pub trait DefaultCloneBehaviorBase { + fn default_clone_behavior(&self) -> ComponentCloneBehavior; } -impl ComponentCloneBase for ComponentCloneSpecializationWrapper { - fn get_component_clone_handler(&self) -> ComponentCloneHandler { - ComponentCloneHandler::default_handler() +impl DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization { + fn default_clone_behavior(&self) -> ComponentCloneBehavior { + ComponentCloneBehavior::Default } } /// Specialized trait for components clone specialization using autoderef. #[doc(hidden)] -pub trait ComponentCloneViaClone { - fn get_component_clone_handler(&self) -> ComponentCloneHandler; +pub trait DefaultCloneBehaviorViaClone { + fn default_clone_behavior(&self) -> ComponentCloneBehavior; } -impl ComponentCloneViaClone for &ComponentCloneSpecializationWrapper { - fn get_component_clone_handler(&self) -> ComponentCloneHandler { - ComponentCloneHandler::clone_handler::() +impl DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization { + fn default_clone_behavior(&self) -> ComponentCloneBehavior { + ComponentCloneBehavior::clone::() } } diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index b337b3c079..5ee0295bec 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1,6 +1,5 @@ use alloc::{borrow::ToOwned, vec::Vec}; use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_platform_support::sync::Arc; use bevy_ptr::{Ptr, PtrMut}; use bumpalo::Bump; use core::{any::TypeId, ptr::NonNull}; @@ -8,14 +7,19 @@ use core::{any::TypeId, ptr::NonNull}; #[cfg(feature = "bevy_reflect")] use alloc::boxed::Box; +use crate::component::{ComponentCloneBehavior, ComponentCloneFn}; +use crate::entity::hash_map::EntityHashMap; +use crate::entity::EntityMapper; +use crate::system::Commands; use crate::{ bundle::Bundle, - component::{Component, ComponentCloneHandler, ComponentId, ComponentInfo, Components}, + component::{Component, ComponentId, ComponentInfo, Components}, entity::Entity, - hierarchy::{ChildOf, Children}, query::DebugCheckedUnwrap, - world::{DeferredWorld, World}, + world::World, }; +use alloc::collections::VecDeque; +use core::cell::RefCell; /// Context for component clone handlers. /// @@ -25,16 +29,19 @@ pub struct ComponentCloneCtx<'a, 'b> { component_id: ComponentId, source_component_ptr: Ptr<'a>, target_component_written: bool, - target_components_ptrs: &'a mut Vec>, - target_components_buffer: &'b Bump, + bundle_scratch: &'a mut BundleScratch<'b>, + bundle_scratch_allocator: &'b Bump, + source: Entity, + target: Entity, components: &'a Components, component_info: &'a ComponentInfo, - entity_cloner: &'a EntityCloner, + entity_cloner: &'a mut EntityCloner, + mapper: &'a mut dyn EntityMapper, #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, #[cfg(not(feature = "bevy_reflect"))] #[expect(dead_code)] - type_registry: Option<()>, + type_registry: Option<&'a ()>, } impl<'a, 'b> ComponentCloneCtx<'a, 'b> { @@ -46,21 +53,27 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// - `source_component_ptr` points to a valid component of type represented by `component_id`. unsafe fn new( component_id: ComponentId, + source: Entity, + target: Entity, source_component_ptr: Ptr<'a>, - target_components_ptrs: &'a mut Vec>, - target_components_buffer: &'b Bump, + bundle_scratch_allocator: &'b Bump, + bundle_scratch: &'a mut BundleScratch<'b>, components: &'a Components, - entity_cloner: &'a EntityCloner, + entity_cloner: &'a mut EntityCloner, + mapper: &'a mut dyn EntityMapper, #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, - #[cfg(not(feature = "bevy_reflect"))] type_registry: Option<()>, + #[cfg(not(feature = "bevy_reflect"))] type_registry: Option<&'a ()>, ) -> Self { Self { component_id, + source, + target, source_component_ptr, - target_components_ptrs, + bundle_scratch, target_component_written: false, - target_components_buffer, + bundle_scratch_allocator, components, + mapper, component_info: components.get_info_unchecked(component_id), entity_cloner, type_registry, @@ -74,12 +87,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// Returns the current source entity. pub fn source(&self) -> Entity { - self.entity_cloner.source + self.source } /// Returns the current target entity. pub fn target(&self) -> Entity { - self.entity_cloner.target + self.target } /// Returns the [`ComponentId`] of the component being cloned. @@ -92,6 +105,19 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { self.component_info } + /// Returns true if the [`EntityCloner`] is configured to recursively clone entities. When this is enabled, + /// entities stored in a cloned entity's [`RelationshipTarget`](crate::relationship::RelationshipTarget) component with + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) will also be cloned. + #[inline] + pub fn is_recursive(&self) -> bool { + self.entity_cloner.is_recursive + } + + /// Returns this context's [`EntityMapper`]. + pub fn entity_mapper(&mut self) -> &mut dyn EntityMapper { + self.mapper + } + /// Returns a reference to the component on the source entity. /// /// Will return `None` if `ComponentId` of requested component does not match `ComponentId` of source component @@ -137,21 +163,26 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// - Component has already been written once. /// - Component being written is not registered in the world. /// - `ComponentId` of component being written does not match expected `ComponentId`. - pub fn write_target_component(&mut self, component: T) { - let short_name = disqualified::ShortName::of::(); + pub fn write_target_component(&mut self, mut component: C) { + C::visit_entities_mut(&mut component, |entity| { + *entity = self.mapper.get_mapped(*entity); + }); + let short_name = disqualified::ShortName::of::(); if self.target_component_written { panic!("Trying to write component '{short_name}' multiple times") } if self .component_info .type_id() - .is_none_or(|id| id != TypeId::of::()) + .is_none_or(|id| id != TypeId::of::()) { panic!("TypeId of component '{short_name}' does not match source component TypeId") }; - let component_ref = self.target_components_buffer.alloc(component); - self.target_components_ptrs - .push(PtrMut::from(component_ref)); + // SAFETY: the TypeId of self.component_id has been checked to ensure it matches `C` + unsafe { + self.bundle_scratch + .push(self.bundle_scratch_allocator, self.component_id, component); + }; self.target_component_written = true; } @@ -175,11 +206,11 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { panic!("Trying to write component multiple times") } let layout = self.component_info.layout(); - let target_component_data_ptr = self.target_components_buffer.alloc_layout(layout); + let target_component_data_ptr = self.bundle_scratch_allocator.alloc_layout(layout); if clone_fn(self.source_component_ptr, target_component_data_ptr) { - self.target_components_ptrs - .push(PtrMut::new(target_component_data_ptr)); + self.bundle_scratch + .push_ptr(self.component_id, PtrMut::new(target_component_data_ptr)); self.target_component_written = true; } } @@ -210,7 +241,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { let component_data_ptr = Box::into_raw(component).cast::(); let target_component_data_ptr = - self.target_components_buffer.alloc_layout(component_layout); + self.bundle_scratch_allocator.alloc_layout(component_layout); // SAFETY: // - target_component_data_ptr and component_data have the same data type. // - component_data_ptr has layout of component_layout @@ -220,34 +251,14 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { target_component_data_ptr.as_ptr(), component_layout.size(), ); - self.target_components_ptrs - .push(PtrMut::new(target_component_data_ptr)); + self.bundle_scratch + .push_ptr(self.component_id, PtrMut::new(target_component_data_ptr)); alloc::alloc::dealloc(component_data_ptr, component_layout); } self.target_component_written = true; } - /// Return a reference to this context's `EntityCloner` instance. - /// - /// This can be used to issue clone commands using the same cloning configuration: - /// ``` - /// # use bevy_ecs::world::{DeferredWorld, World}; - /// # use bevy_ecs::entity::ComponentCloneCtx; - /// fn clone_handler(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - /// let another_target = world.commands().spawn_empty().id(); - /// let mut entity_cloner = ctx - /// .entity_cloner() - /// .with_source_and_target(ctx.source(), another_target); - /// world.commands().queue(move |world: &mut World| { - /// entity_cloner.clone_entity(world); - /// }); - /// } - /// ``` - pub fn entity_cloner(&self) -> &EntityCloner { - self.entity_cloner - } - /// Returns instance of [`Components`]. pub fn components(&self) -> &Components { self.components @@ -260,138 +271,24 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { pub fn type_registry(&self) -> Option<&crate::reflect::AppTypeRegistry> { self.type_registry } -} -/// A helper struct to clone an entity. Used internally by [`EntityCloneBuilder::clone_entity`]. -pub struct EntityCloner { - source: Entity, - target: Entity, - filter_allows_components: bool, - filter: Arc>, - clone_handlers_overrides: Arc>, - move_components: bool, -} - -impl EntityCloner { - /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. - #[track_caller] - pub fn clone_entity(&mut self, world: &mut World) { - // SAFETY: - // - `source_entity` is read-only. - // - `type_registry` is read-only. - // - `components` is read-only. - // - `deferred_world` disallows structural ecs changes, which means all read-only resources above a not affected. - let (type_registry, source_entity, components, mut deferred_world) = unsafe { - let world = world.as_unsafe_world_cell(); - let source_entity = world - .get_entity(self.source) - .expect("Source entity must exist"); - - #[cfg(feature = "bevy_reflect")] - let app_registry = world.get_resource::(); - #[cfg(not(feature = "bevy_reflect"))] - let app_registry = Option::<()>::None; - - ( - app_registry, - source_entity, - world.components(), - world.into_deferred(), - ) - }; - let archetype = source_entity.archetype(); - - let component_data = Bump::new(); - let mut component_ids: Vec = Vec::with_capacity(archetype.component_count()); - let mut component_data_ptrs: Vec = Vec::with_capacity(archetype.component_count()); - - for component in archetype.components() { - if !self.is_cloning_allowed(&component) { - continue; - } - - let global_handlers = components.get_component_clone_handlers(); - let handler = match self.clone_handlers_overrides.get(&component) { - Some(handler) => handler - .get_handler() - .unwrap_or_else(|| global_handlers.get_default_handler()), - None => global_handlers.get_handler(component), - }; - - // SAFETY: - // - There are no other mutable references to source entity. - // - `component` is from `source_entity`'s archetype - let source_component_ptr = - unsafe { source_entity.get_by_id(component).debug_checked_unwrap() }; - - // SAFETY: - // - `components` and `component` are from the same world - // - `source_component_ptr` is valid and points to the same type as represented by `component` - let mut ctx = unsafe { - ComponentCloneCtx::new( - component, - source_component_ptr, - &mut component_data_ptrs, - &component_data, - components, - self, - type_registry, - ) - }; - - (handler)(&mut deferred_world, &mut ctx); - - if ctx.target_component_written { - component_ids.push(component); - } - } - - world.flush(); - - if !world.entities.contains(self.target) { - panic!("Target entity does not exist"); - } - - debug_assert_eq!(component_data_ptrs.len(), component_ids.len()); - - // SAFETY: - // - All `component_ids` are from the same world as `target` entity - // - All `component_data_ptrs` are valid types represented by `component_ids` - unsafe { - world.entity_mut(self.target).insert_by_ids( - &component_ids, - component_data_ptrs.into_iter().map(|ptr| ptr.promote()), - ); - } - - if self.move_components { - world.entity_mut(self.source).remove_by_ids(&component_ids); - } - } - - fn is_cloning_allowed(&self, component: &ComponentId) -> bool { - (self.filter_allows_components && self.filter.contains(component)) - || (!self.filter_allows_components && !self.filter.contains(component)) - } - - /// Reuse existing [`EntityCloner`] configuration with new source and target. - pub fn with_source_and_target(&self, source: Entity, target: Entity) -> EntityCloner { - EntityCloner { - source, - target, - filter: self.filter.clone(), - clone_handlers_overrides: self.clone_handlers_overrides.clone(), - ..*self - } + /// Queues the `entity` to be cloned by the current [`EntityCloner`] + pub fn queue_entity_clone(&self, entity: Entity) { + self.entity_cloner + .clone_queue + .borrow_mut() + .push_back(entity); } } -/// Builder struct to clone an entity. Allows configuring which components to clone, as well as how to clone them. +/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build`], which +/// returns an [`EntityClonerBuilder`]. +/// /// After configuration is complete an entity can be cloned using [`Self::clone_entity`]. /// ///``` /// use bevy_ecs::prelude::*; -/// use bevy_ecs::entity::EntityCloneBuilder; +/// use bevy_ecs::entity::EntityCloner; /// /// #[derive(Component, Clone, PartialEq, Eq)] /// struct A { @@ -405,7 +302,7 @@ impl EntityCloner { /// let entity = world.spawn(component.clone()).id(); /// let entity_clone = world.spawn_empty().id(); /// -/// EntityCloneBuilder::new(&mut world).clone_entity(entity, entity_clone); +/// EntityCloner::build(&mut world).clone_entity(entity, entity_clone); /// /// assert!(world.get::(entity_clone).is_some_and(|c| *c == component)); ///``` @@ -416,77 +313,293 @@ impl EntityCloner { /// /// It should be noted that if `Component` is implemented manually or if `Clone` implementation is conditional /// (like when deriving `Clone` for a type with a generic parameter without `Clone` bound), -/// the component will be cloned using the [default cloning strategy](crate::component::ComponentCloneHandlers::get_default_handler). -/// To use `Clone`-based handler ([`ComponentCloneHandler::clone_handler`]) in this case it should be set manually using one -/// of the methods mentioned in the [Handlers](#handlers) section +/// the component will be cloned using the [default cloning strategy](crate::component::ComponentCloneBehavior::global_default_fn). +/// To use `Clone`-based handler ([`ComponentCloneBehavior::clone`]) in this case it should be set manually using one +/// of the methods mentioned in the [Clone Behaviors](#Clone-Behaviors) section /// -/// Here's an example of how to do it using [`get_component_clone_handler`](Component::get_component_clone_handler): +/// Here's an example of how to do it using [`clone_behavior`](Component::clone_behavior): /// ``` /// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler, Mutable}; +/// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable}; /// #[derive(Clone)] /// struct SomeComponent; /// /// impl Component for SomeComponent { /// const STORAGE_TYPE: StorageType = StorageType::Table; /// type Mutability = Mutable; -/// fn get_component_clone_handler() -> ComponentCloneHandler { -/// ComponentCloneHandler::clone_handler::() +/// fn clone_behavior() -> ComponentCloneBehavior { +/// ComponentCloneBehavior::clone::() /// } /// } /// ``` /// -/// # Handlers -/// `EntityCloneBuilder` clones entities by cloning components using [`handlers`](ComponentCloneHandler), and there are multiple layers +/// # Clone Behaviors +/// [`EntityCloner`] clones entities by cloning components using [`ComponentCloneBehavior`], and there are multiple layers /// to decide which handler to use for which component. The overall hierarchy looks like this (priority from most to least): -/// 1. local overrides using [`override_component_clone_handler`](Self::override_component_clone_handler) -/// 2. global overrides using [`set_component_handler`](crate::component::ComponentCloneHandlers::set_component_handler) -/// 3. component-defined handler using [`get_component_clone_handler`](Component::get_component_clone_handler) -/// 4. default handler override using [`set_default_handler`](crate::component::ComponentCloneHandlers::set_default_handler) -/// 5. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not. +/// 1. local overrides using [`EntityClonerBuilder::override_clone_behavior`] +/// 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(Debug)] -pub struct EntityCloneBuilder<'w> { - world: &'w mut World, +pub struct EntityCloner { filter_allows_components: bool, filter: HashSet, - clone_handlers_overrides: HashMap, - attach_required_components: bool, + clone_behavior_overrides: HashMap, move_components: bool, + is_recursive: bool, + default_clone_fn: ComponentCloneFn, + clone_queue: RefCell>, } -impl<'w> EntityCloneBuilder<'w> { - /// Creates a new [`EntityCloneBuilder`] for world. - pub fn new(world: &'w mut World) -> Self { +impl Default for EntityCloner { + fn default() -> Self { Self { - world, filter_allows_components: false, filter: Default::default(), - clone_handlers_overrides: Default::default(), - attach_required_components: true, + clone_behavior_overrides: Default::default(), move_components: false, + is_recursive: false, + default_clone_fn: ComponentCloneBehavior::global_default_fn(), + clone_queue: Default::default(), + } + } +} + +/// An expandable scratch space for defining a dynamic bundle. +struct BundleScratch<'a> { + component_ids: Vec, + component_ptrs: Vec>, +} + +impl<'a> BundleScratch<'a> { + pub(crate) fn with_capacity(capacity: usize) -> Self { + Self { + component_ids: Vec::with_capacity(capacity), + component_ptrs: Vec::with_capacity(capacity), } } - /// Finishes configuring the builder and clones `source` entity to `target`. - pub fn clone_entity(self, source: Entity, target: Entity) { - let EntityCloneBuilder { - world, - filter_allows_components, - filter, - clone_handlers_overrides, - move_components, - .. - } = self; + /// Pushes the `ptr` component onto this storage with the given `id` [`ComponentId`]. + /// + /// # Safety + /// The `id` [`ComponentId`] must match the component `ptr` for whatever [`World`] this scratch will + /// be written to. `ptr` must contain valid uniquely-owned data that matches the type of component referenced + /// in `id`. + pub(crate) unsafe fn push_ptr(&mut self, id: ComponentId, ptr: PtrMut<'a>) { + self.component_ids.push(id); + self.component_ptrs.push(ptr); + } - EntityCloner { - source, - target, - filter_allows_components, - filter: Arc::new(filter), - clone_handlers_overrides: Arc::new(clone_handlers_overrides), - move_components, + /// Pushes the `C` component onto this storage with the given `id` [`ComponentId`], using the given `bump` allocator. + /// + /// # Safety + /// The `id` [`ComponentId`] must match the component `C` for whatever [`World`] this scratch will + /// be written to. + pub(crate) unsafe fn push( + &mut self, + allocator: &'a Bump, + id: ComponentId, + component: C, + ) { + let component_ref = allocator.alloc(component); + self.component_ids.push(id); + self.component_ptrs.push(PtrMut::from(component_ref)); + } + + /// Writes the scratch components to the given entity in the given world. + /// + /// # Safety + /// All [`ComponentId`] values in this instance must come from `world`. + pub(crate) unsafe fn write(self, world: &mut World, entity: Entity) { + // SAFETY: + // - All `component_ids` are from the same world as `target` entity + // - All `component_data_ptrs` are valid types represented by `component_ids` + unsafe { + world.entity_mut(entity).insert_by_ids( + &self.component_ids, + self.component_ptrs.into_iter().map(|ptr| ptr.promote()), + ); } - .clone_entity(world); + } +} + +impl EntityCloner { + /// Returns a new [`EntityClonerBuilder`] using the given `world`. + pub fn build(world: &mut World) -> EntityClonerBuilder { + EntityClonerBuilder { + world, + attach_required_components: true, + entity_cloner: EntityCloner::default(), + } + } + + /// Returns `true` if this cloner is configured to clone entities recursively. + #[inline] + pub fn is_recursive(&self) -> bool { + self.is_recursive + } + + /// 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, + world: &mut World, + source: Entity, + mapper: &mut dyn EntityMapper, + ) -> Entity { + let target = mapper.get_mapped(source); + // PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`. + let bundle_scratch_allocator = Bump::new(); + let mut bundle_scratch: BundleScratch; + { + let world = world.as_unsafe_world_cell(); + let source_entity = world.get_entity(source).expect("Source entity must exist"); + + #[cfg(feature = "bevy_reflect")] + // SAFETY: we have unique access to `world`, nothing else accesses the registry at this moment, and we clone + // the registry, which prevents future conflicts. + let app_registry = unsafe { + world + .get_resource::() + .cloned() + }; + #[cfg(not(feature = "bevy_reflect"))] + let app_registry = Option::<()>::None; + + let archetype = source_entity.archetype(); + bundle_scratch = BundleScratch::with_capacity(archetype.component_count()); + // SAFETY: no other references to command queue exist + let mut commands = unsafe { + Commands::new_raw_from_entities(world.get_raw_command_queue(), world.entities()) + }; + + for component in archetype.components() { + if !self.is_cloning_allowed(&component) { + continue; + } + + let handler = match self.clone_behavior_overrides.get(&component) { + Some(clone_behavior) => clone_behavior.resolve(self.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), + }; + + // SAFETY: + // - There are no other mutable references to source entity. + // - `component` is from `source_entity`'s archetype + let source_component_ptr = + unsafe { source_entity.get_by_id(component).debug_checked_unwrap() }; + + // SAFETY: + // - `components` and `component` are from the same world + // - `source_component_ptr` is valid and points to the same type as represented by `component` + let mut ctx = unsafe { + ComponentCloneCtx::new( + component, + source, + target, + source_component_ptr, + &bundle_scratch_allocator, + &mut bundle_scratch, + world.components(), + self, + mapper, + app_registry.as_ref(), + ) + }; + + (handler)(&mut commands, &mut ctx); + } + } + + world.flush(); + + if !world.entities.contains(target) { + panic!("Target entity does not exist"); + } + + if self.move_components { + world + .entity_mut(source) + .remove_by_ids(&bundle_scratch.component_ids); + } + + // SAFETY: + // - All `component_ids` are from the same world as `target` entity + // - All `component_data_ptrs` are valid types represented by `component_ids` + unsafe { bundle_scratch.write(world, target) }; + target + } + + /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. + /// If this [`EntityCloner`] has [`EntityCloner::is_recursive`], 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::is_recursive`], 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 { + let target = self.clone_entity_internal(world, source, mapper); + loop { + let queued = self.clone_queue.borrow_mut().pop_front(); + if let Some(queued) = queued { + let target = world.entities.reserve_entity(); + mapper.set_mapped(queued, target); + self.clone_entity_internal(world, queued, mapper); + } else { + break; + } + } + target + } + + fn is_cloning_allowed(&self, component: &ComponentId) -> bool { + (self.filter_allows_components && self.filter.contains(component)) + || (!self.filter_allows_components && !self.filter.contains(component)) + } +} + +/// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information. +#[derive(Debug)] +pub struct EntityClonerBuilder<'w> { + world: &'w mut World, + entity_cloner: EntityCloner, + attach_required_components: bool, +} + +impl<'w> EntityClonerBuilder<'w> { + /// 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); + 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 @@ -496,7 +609,7 @@ impl<'w> EntityCloneBuilder<'w> { /// will not involve required components. pub fn without_required_components( &mut self, - builder: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + builder: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.attach_required_components = false; builder(self); @@ -504,15 +617,21 @@ impl<'w> EntityCloneBuilder<'w> { self } + /// 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 + } + /// Sets whether the cloner should remove any components that were cloned, /// effectively moving them from the source entity to the target. /// /// This is disabled by default. /// /// The setting only applies to components that are allowed through the filter - /// at the time [`EntityCloneBuilder::clone_entity`] is called. + /// at the time [`EntityClonerBuilder::clone_entity`] is called. pub fn move_components(&mut self, enable: bool) -> &mut Self { - self.move_components = enable; + self.entity_cloner.move_components = enable; self } @@ -555,8 +674,8 @@ impl<'w> EntityCloneBuilder<'w> { /// Resets the filter to allow all components to be cloned. pub fn allow_all(&mut self) -> &mut Self { - self.filter_allows_components = false; - self.filter.clear(); + self.entity_cloner.filter_allows_components = false; + self.entity_cloner.filter.clear(); self } @@ -590,70 +709,82 @@ impl<'w> EntityCloneBuilder<'w> { /// Sets the filter to deny all components. pub fn deny_all(&mut self) -> &mut Self { - self.filter_allows_components = true; - self.filter.clear(); + self.entity_cloner.filter_allows_components = true; + self.entity_cloner.filter.clear(); self } - /// Overrides the [`ComponentCloneHandler`] for a component in this builder. - /// This handler will be used to clone the component instead of the global one defined by [`ComponentCloneHandlers`](crate::component::ComponentCloneHandlers) + /// 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`]. /// - /// See [Handlers section of `EntityCloneBuilder`](EntityCloneBuilder#handlers) to understand how this affects handler priority. - pub fn override_component_clone_handler( + /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. + pub fn override_clone_behavior( &mut self, - handler: ComponentCloneHandler, + clone_behavior: ComponentCloneBehavior, ) -> &mut Self { if let Some(id) = self.world.components().component_id::() { - self.clone_handlers_overrides.insert(id, handler); + self.entity_cloner + .clone_behavior_overrides + .insert(id, clone_behavior); } self } - /// Removes a previously set override of [`ComponentCloneHandler`] for a component in this builder. - pub fn remove_component_clone_handler_override(&mut self) -> &mut 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`]. + /// + /// 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); + 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().component_id::() { - self.clone_handlers_overrides.remove(&id); + self.entity_cloner.clone_behavior_overrides.remove(&id); } self } - /// Sets the option to recursively clone entities. - /// When set to true all children will be cloned with the same options as the parent. - pub fn recursive(&mut self, recursive: bool) -> &mut Self { - if recursive { - self.override_component_clone_handler::( - ComponentCloneHandler::custom_handler(component_clone_children), - ) - } else { - self.remove_component_clone_handler_override::() - } + /// 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 } - /// Sets the option to add cloned entity as a child to the parent entity. - pub fn as_child(&mut self, as_child: bool) -> &mut Self { - if as_child { - self.override_component_clone_handler::(ComponentCloneHandler::custom_handler( - component_clone_parent, - )) - } else { - self.remove_component_clone_handler_override::() - } + /// If `true`, makes the built [`EntityCloner`] recursively clone entities, as defined by + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN). + pub fn recursive(&mut self, is_recursive: bool) -> &mut Self { + self.entity_cloner.is_recursive = is_recursive; + self } /// Helper function that allows a component through the filter. fn filter_allow(&mut self, id: ComponentId) { - if self.filter_allows_components { - self.filter.insert(id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.insert(id); } else { - self.filter.remove(&id); + 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.filter_allows_components { - self.filter.insert(required_id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.insert(required_id); } else { - self.filter.remove(&required_id); + self.entity_cloner.filter.remove(&required_id); } } } @@ -662,18 +793,18 @@ impl<'w> EntityCloneBuilder<'w> { /// Helper function that disallows a component through the filter. fn filter_deny(&mut self, id: ComponentId) { - if self.filter_allows_components { - self.filter.remove(&id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.remove(&id); } else { - self.filter.insert(id); + self.entity_cloner.filter.insert(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.filter_allows_components { - self.filter.remove(&required_id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.remove(&required_id); } else { - self.filter.insert(required_id); + self.entity_cloner.filter.insert(required_id); } } } @@ -681,55 +812,33 @@ impl<'w> EntityCloneBuilder<'w> { } } -/// Clone handler for the [`Children`] component. Allows to clone the entity recursively. -fn component_clone_children(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let children = ctx - .read_source_component::() - .expect("Source entity must have Children component") - .iter(); - let parent = ctx.target(); - for child in children { - let child_clone = world.commands().spawn_empty().id(); - let mut clone_entity = ctx - .entity_cloner() - .with_source_and_target(*child, child_clone); - world.commands().queue(move |world: &mut World| { - clone_entity.clone_entity(world); - world.entity_mut(child_clone).insert(ChildOf(parent)); - }); - } -} - -/// Clone handler for the [`ChildOf`] component. Allows to add clone as a child to the parent entity. -fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let parent = ctx - .read_source_component::() - .map(|p| p.0) - .expect("Source entity must have a ChildOf component"); - world - .commands() - .entity(ctx.target()) - .insert(ChildOf(parent)); -} - #[cfg(test)] mod tests { use super::ComponentCloneCtx; use crate::{ self as bevy_ecs, - component::{Component, ComponentCloneHandler, ComponentDescriptor, StorageType}, - entity::EntityCloneBuilder, - world::{DeferredWorld, World}, + component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, + entity::{hash_map::EntityHashMap, Entity, EntityCloner}, + hierarchy::{ChildOf, Children}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, + resource::Resource, + system::Commands, + world::{FromWorld, World}, }; use alloc::vec::Vec; use bevy_ecs_macros::require; use bevy_ptr::OwningPtr; - use core::alloc::Layout; + use bevy_reflect::Reflect; + use core::{alloc::Layout, ops::Deref}; #[cfg(feature = "bevy_reflect")] mod reflect { use super::*; - use crate::reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}; + use crate::{ + entity::EntityCloner, + reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, + system::Commands, + }; use alloc::vec; use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr}; @@ -747,17 +856,14 @@ mod tests { registry.write().register::(); world.register_component::(); - let id = world.component_id::().unwrap(); - world - .get_component_clone_handlers_mut() - .set_component_handler(id, ComponentCloneHandler::reflect_handler()); - let component = A { field: 5 }; let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior::(ComponentCloneBehavior::reflect()) + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); } @@ -798,11 +904,6 @@ mod tests { let a_id = world.register_component::(); let b_id = world.register_component::(); let c_id = world.register_component::(); - let handlers = world.get_component_clone_handlers_mut(); - handlers.set_component_handler(a_id, ComponentCloneHandler::reflect_handler()); - handlers.set_component_handler(b_id, ComponentCloneHandler::reflect_handler()); - handlers.set_component_handler(c_id, ComponentCloneHandler::reflect_handler()); - let component_a = A { field: 5, field2: vec![1, 2, 3, 4, 5], @@ -819,7 +920,11 @@ mod tests { let e = world.spawn((component_a, component_b, component_c)).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&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()) + .clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), Some(world.get::(e).unwrap())); assert_eq!(world.get::(e_clone), Some(world.get::(e).unwrap())); @@ -834,7 +939,7 @@ mod tests { #[derive(Component, Reflect)] struct B; - fn test_handler(_world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { + fn test_handler(_commands: &mut Commands, ctx: &mut ComponentCloneCtx) { assert!(ctx.read_source_component_reflect().is_none()); } @@ -850,15 +955,12 @@ mod tests { .insert(>::from_type()); } - let a_id = world.register_component::(); - let handlers = world.get_component_clone_handlers_mut(); - handlers - .set_component_handler(a_id, ComponentCloneHandler::custom_handler(test_handler)); - let e = world.spawn(A).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior::(ComponentCloneBehavior::Custom(test_handler)) + .clone_entity(e, e_clone); } #[test] @@ -885,7 +987,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world).clone_entity(e, e_clone); assert!(world .get::(e_clone) @@ -905,16 +1007,14 @@ mod tests { struct B; let mut world = World::default(); - let a_id = world.register_component::(); - let b_id = world.register_component::(); - let handlers = world.get_component_clone_handlers_mut(); - handlers.set_component_handler(a_id, ComponentCloneHandler::reflect_handler()); - handlers.set_component_handler(b_id, ComponentCloneHandler::reflect_handler()); // No AppTypeRegistry let e = world.spawn((A, B)).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior::(ComponentCloneBehavior::reflect()) + .override_clone_behavior::(ComponentCloneBehavior::reflect()) + .clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), None); assert_eq!(world.get::(e_clone), None); @@ -925,7 +1025,7 @@ mod tests { let e = world.spawn((A, B)).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world).clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), None); assert_eq!(world.get::(e_clone), None); } @@ -945,7 +1045,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world).clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); } @@ -967,10 +1067,10 @@ mod tests { let e = world.spawn((component.clone(), B)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.allow::(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .allow::() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -996,9 +1096,9 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny::(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&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()); @@ -1025,13 +1125,13 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.allow::(); - builder.allow::(); - builder.allow::(); - builder.deny::(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .allow::() + .allow::() + .allow::() + .deny::() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -1058,11 +1158,11 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.allow::<(A, B, C)>(); - builder.deny::<(B, C)>(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .allow::<(A, B, C)>() + .deny::<(B, C)>() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -1087,12 +1187,12 @@ mod tests { let e = world.spawn(A).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.without_required_components(|builder| { - builder.allow::(); - }); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .without_required_components(|builder| { + builder.allow::(); + }) + .clone_entity(e, e_clone); assert_eq!(world.entity(e_clone).get::(), None); assert_eq!(world.entity(e_clone).get::(), Some(&B)); @@ -1102,7 +1202,7 @@ mod tests { #[test] fn clone_entity_with_dynamic_components() { const COMPONENT_SIZE: usize = 10; - fn test_handler(_world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { + fn test_handler(_commands: &mut Commands, ctx: &mut ComponentCloneCtx) { // SAFETY: this handler is only going to be used with a component represented by [u8; COMPONENT_SIZE] unsafe { ctx.write_target_component_ptr(move |source_ptr, target_ptr| { @@ -1129,16 +1229,11 @@ mod tests { layout, None, true, + ComponentCloneBehavior::Custom(test_handler), ) }; let component_id = world.register_component_with_descriptor(descriptor); - let handlers = world.get_component_clone_handlers_mut(); - handlers.set_component_handler( - component_id, - ComponentCloneHandler::custom_handler(test_handler), - ); - let mut entity = world.spawn_empty(); let data = [5u8; COMPONENT_SIZE]; @@ -1151,8 +1246,7 @@ mod tests { let entity = entity.id(); let entity_clone = world.spawn_empty().id(); - let builder = EntityCloneBuilder::new(&mut world); - builder.clone_entity(entity, entity_clone); + EntityCloner::build(&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(); @@ -1164,4 +1258,71 @@ mod tests { ); } } + + #[test] + fn recursive_clone() { + let mut world = World::new(); + let root = world.spawn_empty().id(); + let child1 = world.spawn(ChildOf(root)).id(); + let grandchild = world.spawn(ChildOf(child1)).id(); + let child2 = world.spawn(ChildOf(root)).id(); + + let clone_root = world.spawn_empty().id(); + EntityCloner::build(&mut world) + .recursive(true) + .clone_entity(root, clone_root); + + let root_children = world + .entity(clone_root) + .get::() + .unwrap() + .iter() + .cloned() + .collect::>(); + + assert!(root_children.iter().all(|e| *e != child1 && *e != child2)); + assert_eq!(root_children.len(), 2); + let child1_children = world.entity(root_children[0]).get::().unwrap(); + assert_eq!(child1_children.len(), 1); + assert_ne!(child1_children[0], grandchild); + assert!(world.entity(root_children[1]).get::().is_none()); + + assert_eq!( + world.entity(root).get::().unwrap().deref(), + &[child1, child2] + ); + } + + #[test] + fn clone_with_reflect_from_world() { + #[derive(Component, Reflect, PartialEq, Eq, Debug)] + #[reflect(Component, FromWorld, from_reflect = false)] + struct SomeRef(#[entities] Entity); + + #[derive(Resource)] + struct FromWorldCalled(bool); + + impl FromWorld for SomeRef { + fn from_world(world: &mut World) -> Self { + world.insert_resource(FromWorldCalled(true)); + SomeRef(Entity::PLACEHOLDER) + } + } + 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)).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)); + assert!(world.resource::().0); + } } diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index 05c11781de..caa02eaeac 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -39,8 +39,8 @@ use super::{hash_map::EntityHashMap, VisitEntitiesMut}; /// /// impl MapEntities for Spring { /// fn map_entities(&mut self, entity_mapper: &mut M) { -/// self.a = entity_mapper.map_entity(self.a); -/// self.b = entity_mapper.map_entity(self.b); +/// self.a = entity_mapper.get_mapped(self.a); +/// self.b = entity_mapper.get_mapped(self.b); /// } /// } /// ``` @@ -55,7 +55,7 @@ pub trait MapEntities { impl MapEntities for T { fn map_entities(&mut self, entity_mapper: &mut M) { self.visit_entities_mut(|entity| { - *entity = entity_mapper.map_entity(*entity); + *entity = entity_mapper.get_mapped(*entity); }); } } @@ -67,6 +67,9 @@ impl MapEntities for T { /// /// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World). /// +/// This can be used in tandem with [`Component::visit_entities`](crate::component::Component::visit_entities) +/// and [`Component::visit_entities_mut`](crate::component::Component::visit_entities_mut) to map a component's entities. +/// /// ## Example /// /// ``` @@ -80,26 +83,61 @@ impl MapEntities for T { /// // Example implementation of EntityMapper where we map an entity to another entity if it exists /// // in the underlying `EntityHashMap`, otherwise we just return the original entity. /// impl EntityMapper for SimpleEntityMapper { -/// fn map_entity(&mut self, entity: Entity) -> Entity { +/// fn get_mapped(&mut self, entity: Entity) -> Entity { /// self.map.get(&entity).copied().unwrap_or(entity) /// } +/// +/// fn set_mapped(&mut self, source: Entity, target: Entity) { +/// self.map.insert(source, target); +/// } /// } /// ``` pub trait EntityMapper { - /// Map an entity to another entity - fn map_entity(&mut self, entity: Entity) -> Entity; + /// Returns the "target" entity that maps to the given `source`. + fn get_mapped(&mut self, source: Entity) -> Entity; + + /// Maps the `target` entity to the given `source`. For some implementations this might not actually determine the result + /// of [`EntityMapper::get_mapped`]. + fn set_mapped(&mut self, source: Entity, target: Entity); +} + +impl EntityMapper for () { + #[inline] + fn get_mapped(&mut self, source: Entity) -> Entity { + source + } + + #[inline] + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} +} + +impl EntityMapper for (Entity, Entity) { + #[inline] + fn get_mapped(&mut self, source: Entity) -> Entity { + if source == self.0 { + self.1 + } else { + source + } + } + + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} } impl EntityMapper for &mut dyn EntityMapper { - fn map_entity(&mut self, entity: Entity) -> Entity { - (*self).map_entity(entity) + fn get_mapped(&mut self, source: Entity) -> Entity { + (*self).get_mapped(source) + } + + fn set_mapped(&mut self, source: Entity, target: Entity) { + (*self).set_mapped(source, target); } } impl EntityMapper for SceneEntityMapper<'_> { /// Returns the corresponding mapped entity or reserves a new dead entity ID in the current world if it is absent. - fn map_entity(&mut self, entity: Entity) -> Entity { - if let Some(&mapped) = self.map.get(&entity) { + fn get_mapped(&mut self, source: Entity) -> Entity { + if let Some(&mapped) = self.map.get(&source) { return mapped; } @@ -112,10 +150,25 @@ impl EntityMapper for SceneEntityMapper<'_> { // Prevent generations counter from being a greater value than HIGH_MASK. self.generations = (self.generations + 1) & HIGH_MASK; - self.map.insert(entity, new); + self.map.insert(source, new); new } + + fn set_mapped(&mut self, source: Entity, target: Entity) { + self.map.insert(source, target); + } +} + +impl EntityMapper for EntityHashMap { + /// Returns the corresponding mapped entity or returns `entity` if there is no mapped entity + fn get_mapped(&mut self, source: Entity) -> Entity { + self.get(&source).cloned().unwrap_or(source) + } + + fn set_mapped(&mut self, source: Entity, target: Entity) { + self.insert(source, target); + } } /// A wrapper for [`EntityHashMap`], augmenting it with the ability to allocate new [`Entity`] references in a destination @@ -208,15 +261,15 @@ mod tests { let mut mapper = SceneEntityMapper::new(&mut map, &mut world); let mapped_ent = Entity::from_raw(FIRST_IDX); - let dead_ref = mapper.map_entity(mapped_ent); + let dead_ref = mapper.get_mapped(mapped_ent); assert_eq!( dead_ref, - mapper.map_entity(mapped_ent), + mapper.get_mapped(mapped_ent), "should persist the allocated mapping from the previous line" ); assert_eq!( - mapper.map_entity(Entity::from_raw(SECOND_IDX)).index(), + mapper.get_mapped(Entity::from_raw(SECOND_IDX)).index(), dead_ref.index(), "should re-use the same index for further dead refs" ); @@ -234,7 +287,7 @@ mod tests { let mut world = World::new(); let dead_ref = SceneEntityMapper::world_scope(&mut map, &mut world, |_, mapper| { - mapper.map_entity(Entity::from_raw(0)) + mapper.get_mapped(Entity::from_raw(0)) }); // Next allocated entity should be a further generation on the same index @@ -253,7 +306,7 @@ mod tests { // Create and exercise a SceneEntityMapper - should not panic because it flushes // `Entities` first. SceneEntityMapper::world_scope(&mut Default::default(), &mut world, |_, m| { - m.map_entity(Entity::PLACEHOLDER); + m.get_mapped(Entity::PLACEHOLDER); }); // The SceneEntityMapper should leave `Entities` in a flushed state. diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index d4eb2475b9..b4ed612a1d 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -7,21 +7,17 @@ //! [`RelationshipTarget`]: crate::relationship::RelationshipTarget #[cfg(feature = "bevy_reflect")] -use crate::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; +use crate::reflect::{ReflectComponent, ReflectFromWorld}; use crate::{ self as bevy_ecs, bundle::Bundle, component::{Component, HookContext}, - entity::{Entity, VisitEntities}, + entity::Entity, relationship::{RelatedSpawner, RelatedSpawnerCommands}, system::EntityCommands, world::{DeferredWorld, EntityWorldMut, FromWorld, World}, }; use alloc::{format, string::String, vec::Vec}; -use bevy_ecs_macros::VisitEntitiesMut; use core::ops::Deref; use core::slice; use disqualified::ShortName; @@ -90,19 +86,11 @@ use log::warn; /// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); /// ``` -#[derive(Component, Clone, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[derive(Component, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( feature = "bevy_reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) + reflect(Component, PartialEq, Debug, FromWorld) )] #[relationship(relationship_target = Children)] pub struct ChildOf(pub Entity); @@ -139,13 +127,10 @@ impl FromWorld for ChildOf { /// /// Together, these components form the "canonical parent-child hierarchy". See the [`ChildOf`] component for all full /// description of this relationship and instructions on how to use it. -#[derive(Component, Default, VisitEntitiesMut, Debug, PartialEq, Eq)] -#[relationship_target(relationship = ChildOf, despawn_descendants)] +#[derive(Component, Default, Debug, PartialEq, Eq)] +#[relationship_target(relationship = ChildOf, linked_spawn)] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "bevy_reflect", - reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut, FromWorld) -)] +#[cfg_attr(feature = "bevy_reflect", reflect(Component, FromWorld))] pub struct Children(Vec); impl<'a> IntoIterator for &'a Children { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8d0bdcc38f..57d173b975 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -79,7 +79,7 @@ pub mod prelude { event::{Event, EventMutator, EventReader, EventWriter, Events}, hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, name::{Name, NameOrEntity}, - observer::{CloneEntityWithObserversExt, Observer, Trigger}, + observer::{Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, removal_detection::RemovedComponents, resource::Resource, @@ -2663,6 +2663,102 @@ mod tests { World::new().register_component::(); } + #[test] + fn visit_struct_entities() { + #[derive(Component)] + #[expect( + unused, + reason = "extra fields are used to ensure the derive works properly" + )] + struct Foo(usize, #[entities] Entity); + + #[derive(Component)] + #[expect( + unused, + reason = "extra fields are used to ensure the derive works properly" + )] + struct Bar { + #[entities] + a: Entity, + b: usize, + #[entities] + c: Vec, + } + + let mut world = World::new(); + let e1 = world.spawn_empty().id(); + let e2 = world.spawn_empty().id(); + let e3 = world.spawn_empty().id(); + + let mut foo = Foo(1, e1); + let mut entities = Vec::new(); + Component::visit_entities(&foo, |e| entities.push(e)); + assert_eq!(&entities, &[e1]); + + let mut entities = Vec::new(); + Component::visit_entities_mut(&mut foo, |e| entities.push(*e)); + assert_eq!(&entities, &[e1]); + + let mut bar = Bar { + a: e1, + b: 1, + c: vec![e2, e3], + }; + let mut entities = Vec::new(); + Component::visit_entities(&bar, |e| entities.push(e)); + assert_eq!(&entities, &[e1, e2, e3]); + + let mut entities = Vec::new(); + Component::visit_entities_mut(&mut bar, |e| entities.push(*e)); + assert_eq!(&entities, &[e1, e2, e3]); + } + + #[test] + fn visit_enum_entities() { + #[derive(Component)] + #[expect( + unused, + reason = "extra fields are used to ensure the derive works properly" + )] + enum Foo { + Bar(usize, #[entities] Entity), + Baz { + #[entities] + a: Entity, + b: usize, + #[entities] + c: Vec, + }, + } + + let mut world = World::new(); + let e1 = world.spawn_empty().id(); + let e2 = world.spawn_empty().id(); + let e3 = world.spawn_empty().id(); + + let mut foo = Foo::Bar(1, e1); + let mut entities = Vec::new(); + Component::visit_entities(&foo, |e| entities.push(e)); + assert_eq!(&entities, &[e1]); + + let mut entities = Vec::new(); + Component::visit_entities_mut(&mut foo, |e| entities.push(*e)); + assert_eq!(&entities, &[e1]); + + let mut foo = Foo::Baz { + a: e1, + b: 1, + c: vec![e2, e3], + }; + let mut entities = Vec::new(); + Component::visit_entities(&foo, |e| entities.push(e)); + assert_eq!(&entities, &[e1, e2, e3]); + + let mut entities = Vec::new(); + Component::visit_entities_mut(&mut foo, |e| entities.push(*e)); + assert_eq!(&entities, &[e1, e2, e3]); + } + #[expect( dead_code, reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index fe7a9fb3c9..893bd3d491 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,10 +1,11 @@ use crate::{ component::{ - Component, ComponentCloneHandler, ComponentHook, HookContext, Mutable, StorageType, + Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType, }, - entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, + entity::{ComponentCloneCtx, Entity, EntityClonerBuilder}, observer::ObserverState, - world::{DeferredWorld, World}, + system::Commands, + world::World, }; use alloc::vec::Vec; @@ -45,34 +46,29 @@ impl Component for ObservedBy { }) } - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::ignore() + fn clone_behavior() -> ComponentCloneBehavior { + ComponentCloneBehavior::Ignore } } -/// Trait that holds functions for configuring interaction with observers during entity cloning. -pub trait CloneEntityWithObserversExt { +impl EntityClonerBuilder<'_> { /// Sets the option to automatically add cloned entities to the observers targeting source entity. - fn add_observers(&mut self, add_observers: bool) -> &mut Self; -} - -impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> { - fn add_observers(&mut self, add_observers: bool) -> &mut Self { + pub fn add_observers(&mut self, add_observers: bool) -> &mut Self { if add_observers { - self.override_component_clone_handler::( - ComponentCloneHandler::custom_handler(component_clone_observed_by), - ) + self.override_clone_behavior::(ComponentCloneBehavior::Custom( + component_clone_observed_by, + )) } else { - self.remove_component_clone_handler_override::() + self.remove_clone_behavior_override::() } } } -fn component_clone_observed_by(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { +fn component_clone_observed_by(commands: &mut Commands, ctx: &mut ComponentCloneCtx) { let target = ctx.target(); let source = ctx.source(); - world.commands().queue(move |world: &mut World| { + commands.queue(move |world: &mut World| { let observed_by = world .get::(source) .map(|observed_by| observed_by.0.clone()) @@ -114,13 +110,8 @@ fn component_clone_observed_by(world: &mut DeferredWorld, ctx: &mut ComponentClo #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, - entity::EntityCloneBuilder, - event::Event, - observer::{CloneEntityWithObserversExt, Trigger}, - resource::Resource, - system::ResMut, - world::World, + self as bevy_ecs, entity::EntityCloner, event::Event, observer::Trigger, + resource::Resource, system::ResMut, world::World, }; #[derive(Resource, Default)] @@ -143,9 +134,9 @@ mod tests { world.trigger_targets(E, e); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.add_observers(true); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .add_observers(true) + .clone_entity(e, e_clone); world.trigger_targets(E, [e, e_clone]); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 1fd58610fb..82eb260cb4 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -3,7 +3,7 @@ mod entity_observer; mod runner; -pub use entity_observer::{CloneEntityWithObserversExt, ObservedBy}; +pub use entity_observer::ObservedBy; pub use runner::*; use crate::{ diff --git a/crates/bevy_ecs/src/reflect/bundle.rs b/crates/bevy_ecs/src/reflect/bundle.rs index 248ca1a704..baa6bc7d08 100644 --- a/crates/bevy_ecs/src/reflect/bundle.rs +++ b/crates/bevy_ecs/src/reflect/bundle.rs @@ -8,6 +8,7 @@ use alloc::boxed::Box; use core::any::{Any, TypeId}; use crate::{ + entity::EntityMapper, prelude::Bundle, world::{EntityMut, EntityWorldMut}, }; @@ -33,8 +34,9 @@ pub struct ReflectBundleFns { pub insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), /// Function pointer implementing [`ReflectBundle::apply`]. pub apply: fn(EntityMut, &dyn PartialReflect, &TypeRegistry), - /// Function pointer implementing [`ReflectBundle::apply_or_insert`]. - pub apply_or_insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), + /// Function pointer implementing [`ReflectBundle::apply_or_insert_mapped`]. + pub apply_or_insert_mapped: + fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry, &mut dyn EntityMapper), /// Function pointer implementing [`ReflectBundle::remove`]. pub remove: fn(&mut EntityWorldMut), /// Function pointer implementing [`ReflectBundle::take`]. @@ -78,13 +80,14 @@ impl ReflectBundle { } /// Uses reflection to set the value of this [`Bundle`] type in the entity to the given value or insert a new one if it does not exist. - pub fn apply_or_insert( + pub fn apply_or_insert_mapped( &self, entity: &mut EntityWorldMut, bundle: &dyn PartialReflect, registry: &TypeRegistry, + mapper: &mut dyn EntityMapper, ) { - (self.0.apply_or_insert)(entity, bundle, registry); + (self.0.apply_or_insert_mapped)(entity, bundle, registry, mapper); } /// Removes this [`Bundle`] type from the entity. Does nothing if it doesn't exist. @@ -166,19 +169,24 @@ impl FromType for ReflectBundle { } } }, - apply_or_insert: |entity, reflected_bundle, registry| { + apply_or_insert_mapped: |entity, reflected_bundle, registry, mapper| { if let Some(reflect_component) = registry.get_type_data::(TypeId::of::()) { - reflect_component.apply_or_insert(entity, reflected_bundle, registry); + reflect_component.apply_or_insert_mapped( + entity, + reflected_bundle, + registry, + mapper, + ); } else { match reflected_bundle.reflect_ref() { - ReflectRef::Struct(bundle) => bundle - .iter_fields() - .for_each(|field| apply_or_insert_field(entity, field, registry)), - ReflectRef::Tuple(bundle) => bundle - .iter_fields() - .for_each(|field| apply_or_insert_field(entity, field, registry)), + ReflectRef::Struct(bundle) => bundle.iter_fields().for_each(|field| { + apply_or_insert_field_mapped(entity, field, registry, mapper); + }), + ReflectRef::Tuple(bundle) => bundle.iter_fields().for_each(|field| { + apply_or_insert_field_mapped(entity, field, registry, mapper); + }), _ => panic!( "expected bundle `{}` to be a named struct or tuple", // FIXME: once we have unique reflect, use `TypePath`. @@ -218,10 +226,11 @@ fn apply_field(entity: &mut EntityMut, field: &dyn PartialReflect, registry: &Ty } } -fn apply_or_insert_field( +fn apply_or_insert_field_mapped( entity: &mut EntityWorldMut, field: &dyn PartialReflect, registry: &TypeRegistry, + mapper: &mut dyn EntityMapper, ) { let Some(type_id) = field.try_as_reflect().map(Any::type_id) else { panic!( @@ -231,9 +240,9 @@ fn apply_or_insert_field( }; if let Some(reflect_component) = registry.get_type_data::(type_id) { - reflect_component.apply_or_insert(entity, field, registry); + reflect_component.apply_or_insert_mapped(entity, field, registry, mapper); } else if let Some(reflect_bundle) = registry.get_type_data::(type_id) { - reflect_bundle.apply_or_insert(entity, field, registry); + reflect_bundle.apply_or_insert_mapped(entity, field, registry, mapper); } else { let is_component = entity.world().components().get_id(type_id).is_some(); diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index 6af8e34a31..bffd2e9c29 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -61,7 +61,7 @@ use super::from_reflect_with_fallback; use crate::{ change_detection::Mut, component::{ComponentId, ComponentMutability}, - entity::Entity, + entity::{Entity, EntityMapper}, prelude::Component, world::{ unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut, @@ -104,8 +104,9 @@ pub struct ReflectComponentFns { pub insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), /// Function pointer implementing [`ReflectComponent::apply()`]. pub apply: fn(EntityMut, &dyn PartialReflect), - /// Function pointer implementing [`ReflectComponent::apply_or_insert()`]. - pub apply_or_insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), + /// Function pointer implementing [`ReflectComponent::apply_or_insert_mapped()`]. + pub apply_or_insert_mapped: + fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry, &mut dyn EntityMapper), /// Function pointer implementing [`ReflectComponent::remove()`]. pub remove: fn(&mut EntityWorldMut), /// Function pointer implementing [`ReflectComponent::contains()`]. @@ -114,6 +115,10 @@ pub struct ReflectComponentFns { pub reflect: fn(FilteredEntityRef) -> Option<&dyn Reflect>, /// Function pointer implementing [`ReflectComponent::reflect_mut()`]. pub reflect_mut: fn(FilteredEntityMut) -> Option>, + /// Function pointer implementing [`ReflectComponent::visit_entities()`]. + pub visit_entities: fn(&dyn Reflect, &mut dyn FnMut(Entity)), + /// Function pointer implementing [`ReflectComponent::visit_entities_mut()`]. + pub visit_entities_mut: fn(&mut dyn Reflect, &mut dyn FnMut(&mut Entity)), /// Function pointer implementing [`ReflectComponent::reflect_unchecked_mut()`]. /// /// # Safety @@ -163,13 +168,14 @@ impl ReflectComponent { /// # Panics /// /// Panics if [`Component`] is immutable. - pub fn apply_or_insert( + pub fn apply_or_insert_mapped( &self, entity: &mut EntityWorldMut, component: &dyn PartialReflect, registry: &TypeRegistry, + map: &mut dyn EntityMapper, ) { - (self.0.apply_or_insert)(entity, component, registry); + (self.0.apply_or_insert_mapped)(entity, component, registry, map); } /// Removes this [`Component`] type from the entity. Does nothing if it doesn't exist. @@ -277,6 +283,20 @@ impl ReflectComponent { pub fn fn_pointers(&self) -> &ReflectComponentFns { &self.0 } + + /// Calls a dynamic version of [`Component::visit_entities`]. + pub fn visit_entities(&self, component: &dyn Reflect, func: &mut dyn FnMut(Entity)) { + (self.0.visit_entities)(component, func); + } + + /// Calls a dynamic version of [`Component::visit_entities_mut`]. + pub fn visit_entities_mut( + &self, + component: &mut dyn Reflect, + func: &mut dyn FnMut(&mut Entity), + ) { + (self.0.visit_entities_mut)(component, func); + } } impl FromType for ReflectComponent { @@ -300,21 +320,28 @@ impl FromType for ReflectComponent { let mut component = unsafe { entity.get_mut_assume_mutable::() }.unwrap(); component.apply(reflected_component); }, - apply_or_insert: |entity, reflected_component, registry| { + apply_or_insert_mapped: |entity, reflected_component, registry, mapper| { + // TODO: if we can externalize this impl to cut down on monomorphization that would be great + let map_fn = move |entity: &mut Entity| { + *entity = mapper.get_mapped(*entity); + }; if C::Mutability::MUTABLE { // SAFETY: guard ensures `C` is a mutable component if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::() } { component.apply(reflected_component.as_partial_reflect()); + C::visit_entities_mut(&mut component, map_fn); } else { - let component = entity.world_scope(|world| { + let mut component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) }); + C::visit_entities_mut(&mut component, map_fn); entity.insert(component); } } else { - let component = entity.world_scope(|world| { + let mut component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) }); + C::visit_entities_mut(&mut component, map_fn); entity.insert(component); } }, @@ -359,6 +386,14 @@ impl FromType for ReflectComponent { register_component: |world: &mut World| -> ComponentId { world.register_component::() }, + visit_entities: |reflect: &dyn Reflect, func: &mut dyn FnMut(Entity)| { + let component = reflect.downcast_ref::().unwrap(); + Component::visit_entities(component, func); + }, + visit_entities_mut: |reflect: &mut dyn Reflect, func: &mut dyn FnMut(&mut Entity)| { + let component = reflect.downcast_mut::().unwrap(); + Component::visit_entities_mut(component, func); + }, }) } } diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index da03bbb506..4a9b5e61f6 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -12,11 +12,11 @@ pub use relationship_source_collection::*; use crate::{ component::{Component, HookContext, Mutable}, - entity::Entity, + entity::{ComponentCloneCtx, Entity}, system::{ command::HandleError, entity_command::{self, CommandWithEntity}, - error_handler, + error_handler, Commands, }, world::{DeferredWorld, EntityWorldMut}, }; @@ -47,7 +47,7 @@ use log::warn; /// pub struct Children(Vec); /// ``` /// -/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(despawn_descendants)]` attribute to +/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(linked_spawn)]` attribute to /// automatically despawn entities stored in an entity's [`RelationshipTarget`] when that entity is despawned: /// /// ``` @@ -58,7 +58,7 @@ use log::warn; /// pub struct ChildOf(pub Entity); /// /// #[derive(Component)] -/// #[relationship_target(relationship = ChildOf, despawn_descendants)] +/// #[relationship_target(relationship = ChildOf, linked_spawn)] /// pub struct Children(Vec); /// ``` pub trait Relationship: Component + Sized { @@ -143,6 +143,14 @@ pub type SourceIter<'w, R> = /// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. /// See the [`Relationship`] documentation for more information. pub trait RelationshipTarget: Component + Sized { + /// If this is true, when despawning or cloning (when [recursion is enabled](crate::entity::EntityClonerBuilder::recursive)), the related entities targeting this entity will also be despawned or cloned. + /// + /// For example, this is set to `true` for Bevy's built-in parent-child relation, defined by [`ChildOf`](crate::prelude::ChildOf) and [`Children`](crate::prelude::Children). + /// This means that when a parent is despawned, any children targeting that parent are also despawned (and the same applies to cloning). + /// + /// To get around this behavior, you can first break the relationship between entities, and *then* despawn or clone. + /// This defaults to false when derived. + const LINKED_SPAWN: bool; /// The [`Relationship`] that populates this [`RelationshipTarget`] collection. type Relationship: Relationship; /// The collection type that stores the "source" entities for this [`RelationshipTarget`] component. @@ -254,6 +262,28 @@ pub trait RelationshipTarget: Component + Sized { } } +/// The "clone behavior" for [`RelationshipTarget`]. This actually creates an empty +/// [`RelationshipTarget`] instance with space reserved for the number of targets in the +/// original instance. The [`RelationshipTarget`] will then be populated with the proper components +/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities +/// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that! +/// +/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured +/// to spawn recursively. +pub fn clone_relationship_target( + _commands: &mut Commands, + context: &mut ComponentCloneCtx, +) { + if let Some(component) = context.read_source_component::() { + if context.is_recursive() && T::LINKED_SPAWN { + for entity in component.iter() { + context.queue_entity_clone(entity); + } + } + context.write_target_component(T::with_capacity(component.len())); + } +} + #[cfg(test)] mod tests { use crate as bevy_ecs; diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index d9b26a720d..7894452e9f 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -130,7 +130,7 @@ mod tests { struct Rel(Entity); #[derive(Component)] - #[relationship_target(relationship = Rel, despawn_descendants)] + #[relationship_target(relationship = Rel, linked_spawn)] struct RelTarget(Vec); let mut world = World::new(); @@ -151,7 +151,7 @@ mod tests { struct Rel(Entity); #[derive(Component)] - #[relationship_target(relationship = Rel, despawn_descendants)] + #[relationship_target(relationship = Rel, linked_spawn)] struct RelTarget(EntityHashSet); let mut world = World::new(); @@ -172,7 +172,7 @@ mod tests { struct Rel(Entity); #[derive(Component)] - #[relationship_target(relationship = Rel, despawn_descendants)] + #[relationship_target(relationship = Rel, linked_spawn)] struct RelTarget(SmallVec<[Entity; 4]>); let mut world = World::new(); diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index e5a1097abc..e0b0bffc69 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -13,7 +13,7 @@ use core::panic::Location; use crate::{ bundle::{Bundle, InsertMode}, component::{Component, ComponentId, ComponentInfo}, - entity::{Entity, EntityCloneBuilder}, + entity::{Entity, EntityClonerBuilder}, event::Event, result::Result, system::{command::HandleError, Command, IntoObserverSystem}, @@ -324,10 +324,10 @@ pub fn observe( } /// An [`EntityCommand`] that clones parts of an entity onto another entity, -/// configured through [`EntityCloneBuilder`]. +/// configured through [`EntityClonerBuilder`]. pub fn clone_with( target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> impl EntityCommand { move |mut entity: EntityWorldMut| { entity.clone_with(target, config); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 5694df4a49..5f4fb1976d 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -21,7 +21,7 @@ use crate::{ bundle::{Bundle, InsertMode}, change_detection::Mut, component::{Component, ComponentId, Mutable}, - entity::{Entities, Entity, EntityCloneBuilder}, + entity::{Entities, Entity, EntityClonerBuilder}, event::Event, observer::{Observer, TriggerTargets}, resource::Resource, @@ -1913,7 +1913,7 @@ impl<'a> EntityCommands<'a> { } /// Clones parts of an entity (components, observers, etc.) onto another entity, - /// configured through [`EntityCloneBuilder`]. + /// configured through [`EntityClonerBuilder`]. /// /// By default, the other entity will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). @@ -1924,7 +1924,7 @@ impl<'a> EntityCommands<'a> { /// /// # Example /// - /// Configure through [`EntityCloneBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// @@ -1948,14 +1948,11 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(example_system); /// ``` /// - /// See the following for more options: - /// - [`EntityCloneBuilder`] - /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) - /// - `CloneEntityHierarchyExt` + /// See [`EntityClonerBuilder`] for more options. pub fn clone_with( &mut self, target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.queue(entity_command::clone_with(target, config)) } @@ -1996,16 +1993,16 @@ impl<'a> EntityCommands<'a> { } /// Spawns a clone of this entity and allows configuring cloning behavior - /// using [`EntityCloneBuilder`], returning the [`EntityCommands`] of the clone. + /// 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 [`EntityCloneBuilder::deny`]. - /// To only include specific components, use [`EntityCloneBuilder::deny_all`] - /// followed by [`EntityCloneBuilder::allow`]. + /// To exclude specific components, use [`EntityClonerBuilder::deny`]. + /// To only include specific components, use [`EntityClonerBuilder::deny_all`] + /// followed by [`EntityClonerBuilder::allow`]. /// - /// See the methods on [`EntityCloneBuilder`] for more options. + /// See the methods on [`EntityClonerBuilder`] for more options. /// /// # Note /// @@ -2034,7 +2031,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(example_system); pub fn clone_and_spawn_with( &mut self, - config: impl FnOnce(&mut EntityCloneBuilder) + 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); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1240fba66e..a6cab26fd6 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,8 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType}, entity::{ - Entities, Entity, EntityBorrow, EntityCloneBuilder, EntityLocation, TrustedEntityBorrow, + Entities, Entity, EntityBorrow, EntityCloner, EntityClonerBuilder, EntityLocation, + TrustedEntityBorrow, }, event::Event, observer::Observer, @@ -2591,12 +2592,12 @@ impl<'w> EntityWorldMut<'w> { } /// Clones parts of an entity (components, observers, etc.) onto another entity, - /// configured through [`EntityCloneBuilder`]. + /// configured through [`EntityClonerBuilder`]. /// /// By default, the other entity will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// - /// Configure through [`EntityCloneBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// # #[derive(Component, Clone, PartialEq, Debug)] @@ -2613,10 +2614,7 @@ impl<'w> EntityWorldMut<'w> { /// # assert_eq!(world.get::(target), None); /// ``` /// - /// See the following for more options: - /// - [`EntityCloneBuilder`] - /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) - /// - `CloneEntityHierarchyExt` + /// See [`EntityClonerBuilder`] for more options. /// /// # Panics /// @@ -2625,11 +2623,11 @@ impl<'w> EntityWorldMut<'w> { pub fn clone_with( &mut self, target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloneBuilder::new(self.world); + let mut builder = EntityCloner::build(self.world); config(&mut builder); builder.clone_entity(self.entity, target); @@ -2654,12 +2652,12 @@ impl<'w> EntityWorldMut<'w> { } /// Spawns a clone of this entity and allows configuring cloning behavior - /// using [`EntityCloneBuilder`], returning the [`Entity`] of the clone. + /// 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). /// - /// Configure through [`EntityCloneBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// # #[derive(Component, Clone, PartialEq, Debug)] @@ -2675,24 +2673,21 @@ impl<'w> EntityWorldMut<'w> { /// # assert_eq!(world.get::(entity_clone), None); /// ``` /// - /// See the following for more options: - /// - [`EntityCloneBuilder`] - /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) - /// - `CloneEntityHierarchyExt` + /// See [`EntityClonerBuilder`] for more options. /// /// # Panics /// /// If this entity has been despawned while this `EntityWorldMut` is still alive. pub fn clone_and_spawn_with( &mut self, - config: impl FnOnce(&mut EntityCloneBuilder) + 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 = EntityCloneBuilder::new(self.world); + let mut builder = EntityCloner::build(self.world); config(&mut builder); builder.clone_entity(self.entity, entity_clone); @@ -2713,9 +2708,10 @@ impl<'w> EntityWorldMut<'w> { pub fn clone_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloneBuilder::new(self.world); - builder.deny_all().allow::(); - builder.clone_entity(self.entity, target); + EntityCloner::build(self.world) + .deny_all() + .allow::() + .clone_entity(self.entity, target); self.world.flush(); self.update_location(); @@ -2735,10 +2731,11 @@ impl<'w> EntityWorldMut<'w> { pub fn move_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloneBuilder::new(self.world); - builder.deny_all().allow::(); - builder.move_components(true); - builder.clone_entity(self.entity, target); + EntityCloner::build(self.world) + .deny_all() + .allow::() + .move_components(true) + .clone_entity(self.entity, target); self.world.flush(); self.update_location(); @@ -5679,10 +5676,11 @@ mod tests { let entity_b = world.spawn_empty().id(); world.entity_mut(entity_a).clone_with(entity_b, |builder| { - builder.move_components(true); - builder.without_required_components(|builder| { - builder.deny::(); - }); + builder + .move_components(true) + .without_required_components(|builder| { + builder.deny::(); + }); }); assert_eq!(world.entity(entity_a).get::(), Some(&A)); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 732e02e189..b7ecf5b6f3 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -35,9 +35,8 @@ use crate::{ bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode}, change_detection::{MutUntyped, TicksMut}, component::{ - Component, ComponentCloneHandlers, ComponentDescriptor, ComponentHooks, ComponentId, - ComponentInfo, ComponentTicks, Components, Mutable, RequiredComponents, - RequiredComponentsError, Tick, + Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks, + Components, Mutable, RequiredComponents, RequiredComponentsError, Tick, }, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, entity_disabling::{DefaultQueryFilters, Disabled}, @@ -3190,35 +3189,6 @@ impl World { // SAFETY: We just initialized the bundle so its id should definitely be valid. unsafe { self.bundles.get(id).debug_checked_unwrap() } } - - /// Retrieves a mutable reference to the [`ComponentCloneHandlers`]. Can be used to set and update clone functions for components. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// use bevy_ecs::component::{ComponentId, ComponentCloneHandler}; - /// use bevy_ecs::entity::ComponentCloneCtx; - /// use bevy_ecs::world::DeferredWorld; - /// - /// fn custom_clone_handler( - /// _world: &mut DeferredWorld, - /// _ctx: &mut ComponentCloneCtx, - /// ) { - /// // Custom cloning logic for component - /// } - /// - /// #[derive(Component)] - /// struct ComponentA; - /// - /// let mut world = World::new(); - /// - /// let component_id = world.register_component::(); - /// - /// world.get_component_clone_handlers_mut() - /// .set_component_handler(component_id, ComponentCloneHandler::custom_handler(custom_clone_handler)) - /// ``` - pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers { - self.components.get_component_clone_handlers_mut() - } } impl World { @@ -3770,7 +3740,7 @@ mod tests { use super::{FromWorld, World}; use crate::{ change_detection::DetectChangesMut, - component::{ComponentDescriptor, ComponentInfo, StorageType}, + component::{ComponentCloneBehavior, ComponentDescriptor, ComponentInfo, StorageType}, entity::hash_set::EntityHashSet, entity_disabling::{DefaultQueryFilters, Disabled}, ptr::OwningPtr, @@ -4074,6 +4044,7 @@ mod tests { DROP_COUNT.fetch_add(1, Ordering::SeqCst); }), true, + ComponentCloneBehavior::Default, ) }; diff --git a/crates/bevy_mesh/src/skinning.rs b/crates/bevy_mesh/src/skinning.rs index faf4e8be3e..f55f51af10 100644 --- a/crates/bevy_mesh/src/skinning.rs +++ b/crates/bevy_mesh/src/skinning.rs @@ -1,26 +1,14 @@ use bevy_asset::{Asset, Handle}; -use bevy_ecs::{ - component::Component, - entity::{Entity, VisitEntities, VisitEntitiesMut}, - prelude::ReflectComponent, - reflect::{ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut}, -}; +use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; use bevy_math::Mat4; use bevy_reflect::prelude::*; use core::ops::Deref; -#[derive(Component, Debug, Default, Clone, Reflect, VisitEntities, VisitEntitiesMut)] -#[reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - Default, - Debug -)] +#[derive(Component, Debug, Default, Clone, Reflect)] +#[reflect(Component, Default, Debug)] pub struct SkinnedMesh { - #[visit_entities(ignore)] pub inverse_bindposes: Handle, + #[entities] pub joints: Vec, } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index a40053ed5f..be45b54a81 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,15 +1,16 @@ use crate::{ron, DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; -use bevy_ecs::reflect::ReflectResource; +use bevy_ecs::reflect::{ReflectMapEntities, ReflectResource}; use bevy_ecs::{ entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper}, - reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities}, + reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; use bevy_reflect::{PartialReflect, TypePath, TypeRegistry}; #[cfg(feature = "serialize")] use crate::serde::SceneSerializer; +use bevy_ecs::component::ComponentCloneBehavior; #[cfg(feature = "serialize")] use serde::Serialize; @@ -85,7 +86,7 @@ impl DynamicScene { // Apply/ add each component to the given entity. for component in &scene_entity.components { - let mut component = component.clone_value(); + let component = component.clone_value(); let type_info = component.get_represented_type_info().ok_or_else(|| { SceneSpawnError::NoRepresentedType { type_path: component.reflect_type_path().to_string(), @@ -103,19 +104,27 @@ impl DynamicScene { } })?; - // If this component references entities in the scene, update - // them to the entities in the world. - if let Some(map_entities) = registration.data::() { - SceneEntityMapper::world_scope(entity_map, world, |_, mapper| { - map_entities.map_entities(component.as_partial_reflect_mut(), mapper); - }); + { + let component_id = reflect_component.register_component(world); + // SAFETY: we registered the component above. the info exists + #[expect(unsafe_code, reason = "this is faster")] + let component_info = + unsafe { world.components().get_info_unchecked(component_id) }; + match component_info.clone_behavior() { + ComponentCloneBehavior::Ignore + | ComponentCloneBehavior::RelationshipTarget(_) => continue, + _ => {} + } } - reflect_component.apply_or_insert( - &mut world.entity_mut(entity), - component.as_partial_reflect(), - &type_registry, - ); + SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { + reflect_component.apply_or_insert_mapped( + &mut world.entity_mut(entity), + component.as_partial_reflect(), + &type_registry, + mapper, + ); + }); } } @@ -340,13 +349,13 @@ mod tests { #[reflect(Component)] struct A; - #[derive(Component, Reflect, VisitEntities)] - #[reflect(Component, MapEntities)] + #[derive(Component, Reflect)] + #[reflect(Component)] struct B(pub Entity); impl MapEntities for B { fn map_entities(&mut self, entity_mapper: &mut M) { - self.0 = entity_mapper.map_entity(self.0); + self.0 = entity_mapper.get_mapped(self.0); } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 507f18f63a..fe15866c14 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 7d74ce8cb2..9040b15e85 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -3,9 +3,10 @@ use core::any::TypeId; use crate::{DynamicScene, SceneSpawnError}; use bevy_asset::Asset; use bevy_ecs::{ + component::ComponentCloneBehavior, entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, - reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, world::World, }; use bevy_reflect::{PartialReflect, TypePath}; @@ -123,6 +124,12 @@ impl Scene { .get_info(component_id) .expect("component_ids in archetypes should have ComponentInfo"); + match component_info.clone_behavior() { + ComponentCloneBehavior::Ignore + | ComponentCloneBehavior::RelationshipTarget(_) => continue, + _ => {} + } + let registration = type_registry .get(component_info.type_id().unwrap()) .ok_or_else(|| SceneSpawnError::UnregisteredType { @@ -135,7 +142,7 @@ impl Scene { } })?; - let Some(mut component) = reflect_component + let Some(component) = reflect_component .reflect(self.world.entity(scene_entity.id())) .map(PartialReflect::clone_value) else { @@ -144,16 +151,14 @@ impl Scene { // If this component references entities in the scene, // update them to the entities in the world. - if let Some(map_entities) = registration.data::() { - SceneEntityMapper::world_scope(entity_map, world, |_, mapper| { - map_entities.map_entities(component.as_partial_reflect_mut(), mapper); - }); - } - reflect_component.apply_or_insert( - &mut world.entity_mut(entity), - component.as_partial_reflect(), - &type_registry, - ); + SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { + reflect_component.apply_or_insert_mapped( + &mut world.entity_mut(entity), + component.as_partial_reflect(), + &type_registry, + mapper, + ); + }); } } } diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index ede9076ea2..8e26863e79 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -515,10 +515,10 @@ mod tests { DynamicScene, DynamicSceneBuilder, }; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity, VisitEntities, VisitEntitiesMut}, + entity::{hash_map::EntityHashMap, Entity}, prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, query::{With, Without}, - reflect::{AppTypeRegistry, ReflectMapEntities}, + reflect::AppTypeRegistry, world::FromWorld, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; @@ -584,9 +584,9 @@ mod tests { foo: i32, } - #[derive(Clone, Component, Reflect, PartialEq, VisitEntities, VisitEntitiesMut)] - #[reflect(Component, MapEntities, PartialEq)] - struct MyEntityRef(Entity); + #[derive(Clone, Component, Reflect, PartialEq)] + #[reflect(Component, PartialEq)] + struct MyEntityRef(#[entities] Entity); impl FromWorld for MyEntityRef { fn from_world(_world: &mut World) -> Self { diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index d5ca961f77..09db13ae2c 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -10,7 +10,9 @@ use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull}; use bevy::{ ecs::{ - component::{ComponentDescriptor, ComponentId, ComponentInfo, StorageType}, + component::{ + ComponentCloneBehavior, ComponentDescriptor, ComponentId, ComponentInfo, StorageType, + }, query::QueryData, world::FilteredEntityMut, }, @@ -94,6 +96,7 @@ fn main() { Layout::array::(size).unwrap(), None, true, + ComponentCloneBehavior::Default, ) }); let Some(info) = world.components().get_info(id) else { diff --git a/examples/ecs/immutable_components.rs b/examples/ecs/immutable_components.rs index 1fe1dde23a..6fd434f446 100644 --- a/examples/ecs/immutable_components.rs +++ b/examples/ecs/immutable_components.rs @@ -2,7 +2,9 @@ use bevy::{ ecs::{ - component::{ComponentDescriptor, ComponentId, HookContext, StorageType}, + component::{ + ComponentCloneBehavior, ComponentDescriptor, ComponentId, HookContext, StorageType, + }, world::DeferredWorld, }, platform_support::collections::HashMap, @@ -152,6 +154,7 @@ fn demo_3(world: &mut World) { Layout::array::(size).unwrap(), None, false, + ComponentCloneBehavior::Default, ) }; diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 9729b8573c..6234ca90e8 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -17,7 +17,7 @@ use bevy::{ DiagnosticPath, DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin, }, ecs::{ - component::{ComponentDescriptor, ComponentId, StorageType}, + component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType}, system::QueryParamBuilder, world::FilteredEntityMut, }, @@ -99,6 +99,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { Layout::new::(), None, true, // is mutable + ComponentCloneBehavior::Default, ) }, )