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<Entity>,
}
```

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<Entity>,
}
```

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<Item=Entity>`. 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<ComponentCloneHandler>` 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 <alice.i.cecile@gmail.com>
This commit is contained in:
Carter Anderson 2025-02-06 14:13:41 -08:00 committed by GitHub
parent f2a65c2dd3
commit 3c8fae2390
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1240 additions and 777 deletions

View File

@ -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<B: Bundle + GetTypeRegistration>(world: &mut World) {
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
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::<AppTypeRegistry>();
@ -67,12 +71,15 @@ fn set_reflect_clone_handler<B: Bundle + GetTypeRegistration>(world: &mut World)
// this bundle are saved.
let component_ids: Vec<_> = world.register_bundle::<B>().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<B: Bundle + Default + GetTypeRegistration>(
) {
let mut world = World::default();
if clone_via_reflect {
set_reflect_clone_handler::<B>(&mut world);
}
let mut cloner = if clone_via_reflect {
reflection_cloner::<B>(&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<B: Bundle + Default + GetTypeRegistration>(
) {
let mut world = World::default();
if clone_via_reflect {
set_reflect_clone_handler::<B>(&mut world);
}
let mut cloner = if clone_via_reflect {
reflection_cloner::<B>(&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<B: Bundle + Default + GetTypeRegistration>(
}
}
// 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();
});
}

View File

@ -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,
}

View File

@ -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::<Self>))
} else {
quote!(
use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase};
(&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::<Self>::default()).get_component_clone_handler()
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::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::<Require, Comma>::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<Self> {
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::<relationship>()?;
input.parse::<Token![=]>()?;
relationship_ident = Some(input.parse::<Ident>()?);
} else if input.peek(despawn_descendants) {
input.parse::<despawn_descendants>()?;
despawn_descendants_exists = true;
} else if input.peek(linked_spawn) {
input.parse::<linked_spawn>()?;
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;

View File

@ -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)
}

View File

@ -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::<C>().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::<C>().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<for<'a> 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::<T>(),
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
mutable: T::Mutability::MUTABLE,
clone_behavior: T::clone_behavior(),
}
}
@ -1000,6 +1015,7 @@ impl ComponentDescriptor {
layout: Layout,
drop: Option<for<'a> 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::<T>(),
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
mutable: true,
clone_behavior: ComponentCloneBehavior::Default,
}
}
@ -1038,6 +1056,7 @@ impl ComponentDescriptor {
layout: Layout::new::<T>(),
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> 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<ComponentCloneFn>);
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<C: Component + Clone>() -> Self {
Self(Some(component_clone_via_clone::<C>))
pub fn clone<C: Component + Clone>() -> Self {
Self::Custom(component_clone_via_clone::<C>)
}
/// 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))
}
/// Get [`ComponentCloneFn`] representing this handler or `None` if set to default handler.
pub fn get_handler(&self) -> Option<ComponentCloneFn> {
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<Option<ComponentCloneFn>>,
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(),
/// Returns the "global default"
pub fn global_default_fn() -> ComponentCloneFn {
#[cfg(feature = "bevy_reflect")]
default_handler: component_clone_via_reflect,
return component_clone_via_reflect;
#[cfg(not(feature = "bevy_reflect"))]
default_handler: component_clone_ignore,
return 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<ComponentInfo>,
indices: TypeIdMap<ComponentId>,
resource_indices: TypeIdMap<ComponentId>,
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<ComponentId> {
@ -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<C: Clone + Component>(
_world: &mut DeferredWorld,
_commands: &mut Commands,
ctx: &mut ComponentCloneCtx,
) {
if let Some(component) = ctx.read_source_component::<C>() {
@ -2262,7 +2214,7 @@ pub fn component_clone_via_clone<C: Clone + Component>(
}
/// 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<C: Clone + Component>(
///
/// 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::<bevy_reflect::ReflectFromReflect>(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::<crate::reflect::ReflectComponent>(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::<crate::reflect::ReflectFromWorld>(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::<crate::reflect::ReflectComponent>(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::<crate::reflect::ReflectComponent>(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<T>(PhantomData<T>);
pub struct DefaultCloneBehaviorSpecialization<T>(PhantomData<T>);
impl<T> Default for ComponentCloneSpecializationWrapper<T> {
impl<T> Default for DefaultCloneBehaviorSpecialization<T> {
fn default() -> Self {
Self(PhantomData)
}
@ -2356,22 +2338,22 @@ impl<T> Default for ComponentCloneSpecializationWrapper<T> {
/// 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<C: Component> ComponentCloneBase for ComponentCloneSpecializationWrapper<C> {
fn get_component_clone_handler(&self) -> ComponentCloneHandler {
ComponentCloneHandler::default_handler()
impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
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<C: Clone + Component> ComponentCloneViaClone for &ComponentCloneSpecializationWrapper<C> {
fn get_component_clone_handler(&self) -> ComponentCloneHandler {
ComponentCloneHandler::clone_handler::<C>()
impl<C: Clone + Component> DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization<C> {
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::clone::<C>()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -39,8 +39,8 @@ use super::{hash_map::EntityHashMap, VisitEntitiesMut};
///
/// impl MapEntities for Spring {
/// fn map_entities<M: EntityMapper>(&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<T: VisitEntitiesMut> MapEntities for T {
fn map_entities<M: EntityMapper>(&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<T: VisitEntitiesMut> 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<T: VisitEntitiesMut> 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<Entity> {
/// 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<Entity>`], 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.

View File

@ -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::<Children>().unwrap(), &[child1, child2]);
/// assert_eq!(&**world.entity(child1).get::<Children>().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<Entity>);
impl<'a> IntoIterator for &'a Children {

View File

@ -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::<A>();
}
#[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<Entity>,
}
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<Entity>,
},
}
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."

View File

@ -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::<ObservedBy>(
ComponentCloneHandler::custom_handler(component_clone_observed_by),
)
self.override_clone_behavior::<ObservedBy>(ComponentCloneBehavior::Custom(
component_clone_observed_by,
))
} else {
self.remove_component_clone_handler_override::<ObservedBy>()
self.remove_clone_behavior_override::<ObservedBy>()
}
}
}
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::<ObservedBy>(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]);

View File

@ -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::{

View File

@ -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<B: Bundle + Reflect + TypePath> FromType<B> 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::<ReflectComponent>(TypeId::of::<B>())
{
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::<ReflectComponent>(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::<ReflectBundle>(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();

View File

@ -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<Mut<dyn Reflect>>,
/// 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<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
@ -300,21 +320,28 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
let mut component = unsafe { entity.get_mut_assume_mutable::<C>() }.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::<C>() } {
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::<C>(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::<C>(reflected_component, world, registry)
});
C::visit_entities_mut(&mut component, map_fn);
entity.insert(component);
}
},
@ -359,6 +386,14 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
register_component: |world: &mut World| -> ComponentId {
world.register_component::<C>()
},
visit_entities: |reflect: &dyn Reflect, func: &mut dyn FnMut(Entity)| {
let component = reflect.downcast_ref::<C>().unwrap();
Component::visit_entities(component, func);
},
visit_entities_mut: |reflect: &mut dyn Reflect, func: &mut dyn FnMut(&mut Entity)| {
let component = reflect.downcast_mut::<C>().unwrap();
Component::visit_entities_mut(component, func);
},
})
}
}

View File

@ -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<Entity>);
/// ```
///
/// 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<Entity>);
/// ```
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<Mutability = Mutable> + 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<RelationshipTarget = Self>;
/// The collection type that stores the "source" entities for this [`RelationshipTarget`] component.
@ -254,6 +262,28 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
}
}
/// The "clone behavior" for [`RelationshipTarget`]. This actually creates an empty
/// [`RelationshipTarget`] instance with space reserved for the number of targets in the
/// original instance. The [`RelationshipTarget`] will then be populated with the proper components
/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities
/// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that!
///
/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured
/// to spawn recursively.
pub fn clone_relationship_target<T: RelationshipTarget>(
_commands: &mut Commands,
context: &mut ComponentCloneCtx,
) {
if let Some(component) = context.read_source_component::<T>() {
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;

View File

@ -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<Entity>);
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();

View File

@ -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<E: Event, B: Bundle, M>(
}
/// 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);

View File

@ -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);

View File

@ -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::<ComponentB>(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::<ComponentB>(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<B: Bundle>(&mut self, target: Entity) -> &mut Self {
self.assert_not_despawned();
let mut builder = EntityCloneBuilder::new(self.world);
builder.deny_all().allow::<B>();
builder.clone_entity(self.entity, target);
EntityCloner::build(self.world)
.deny_all()
.allow::<B>()
.clone_entity(self.entity, target);
self.world.flush();
self.update_location();
@ -2735,10 +2731,11 @@ impl<'w> EntityWorldMut<'w> {
pub fn move_components<B: Bundle>(&mut self, target: Entity) -> &mut Self {
self.assert_not_despawned();
let mut builder = EntityCloneBuilder::new(self.world);
builder.deny_all().allow::<B>();
builder.move_components(true);
builder.clone_entity(self.entity, target);
EntityCloner::build(self.world)
.deny_all()
.allow::<B>()
.move_components(true)
.clone_entity(self.entity, target);
self.world.flush();
self.update_location();
@ -5679,8 +5676,9 @@ 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
.move_components(true)
.without_required_components(|builder| {
builder.deny::<A>();
});
});

View File

@ -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::<ComponentA>();
///
/// 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,
)
};

View File

@ -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<SkinnedMeshInverseBindposes>,
#[entities]
pub joints: Vec<Entity>,
}

View File

@ -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::<ReflectMapEntities>() {
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(
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<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.0 = entity_mapper.map_entity(self.0);
self.0 = entity_mapper.get_mapped(self.0);
}
}

View File

@ -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"

View File

@ -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::<ReflectMapEntities>() {
SceneEntityMapper::world_scope(entity_map, world, |_, mapper| {
map_entities.map_entities(component.as_partial_reflect_mut(), mapper);
});
}
reflect_component.apply_or_insert(
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,
);
});
}
}
}

View File

@ -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 {

View File

@ -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::<u64>(size).unwrap(),
None,
true,
ComponentCloneBehavior::Default,
)
});
let Some(info) = world.components().get_info(id) else {

View File

@ -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::<u8>(size).unwrap(),
None,
false,
ComponentCloneBehavior::Default,
)
};

View File

@ -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::<u8>(),
None,
true, // is mutable
ComponentCloneBehavior::Default,
)
},
)