Replace VisitEntities with MapEntities (#18432)

# Objective

There are currently too many disparate ways to handle entity mapping,
especially after #17687. We now have MapEntities, VisitEntities,
VisitEntitiesMut, Component::visit_entities,
Component::visit_entities_mut.

Our only known use case at the moment for these is entity mapping. This
means we have significant consolidation potential.

Additionally, VisitEntitiesMut cannot be implemented for map-style
collections like HashSets, as you cant "just" mutate a `&mut Entity`.
Our current approach to Component mapping requires VisitEntitiesMut,
meaning this category of entity collection isn't mappable. `MapEntities`
is more generally applicable. Additionally, the _existence_ of the
blanket From impl on VisitEntitiesMut blocks us from implementing
MapEntities for HashSets (or any types we don't own), because the owner
could always add a conflicting impl in the future.

## Solution

Use `MapEntities` everywhere and remove all "visit entities" usages.

* Add `Component::map_entities`
* Remove `Component::visit_entities`, `Component::visit_entities_mut`,
`VisitEntities`, and `VisitEntitiesMut`
* Support deriving `Component::map_entities` in `#[derive(Coomponent)]`
* Add `#[derive(MapEntities)]`, and share logic with the
`Component::map_entities` derive.
* Add `ComponentCloneCtx::queue_deferred`, which is command-like logic
that runs immediately after normal clones. Reframe `FromWorld` fallback
logic in the "reflect clone" impl to use it. This cuts out a lot of
unnecessary work and I think justifies the existence of a pseudo-command
interface (given how niche, yet performance sensitive this is).

Note that we no longer auto-impl entity mapping for ` IntoIterator<Item
= &'a Entity>` types, as this would block our ability to implement cases
like `HashMap`. This means the onus is on us (or type authors) to add
explicit support for types that should be mappable.

Also note that the Component-related changes do not require a migration
guide as there hasn't been a release with them yet.

## Migration Guide

If you were previously implementing `VisitEntities` or
`VisitEntitiesMut` (likely via a derive), instead use `MapEntities`.
Those were almost certainly used in the context of Bevy Scenes or
reflection via `ReflectMapEntities`. If you have a case that uses
`VisitEntities` or `VisitEntitiesMut` directly, where `MapEntities` is
not a viable replacement, please let us know!

```rust
// before
#[derive(VisitEntities, VisitEntitiesMut)]
struct Inventory {
  items: Vec<Entity>,
  #[visit_entities(ignore)]
  label: String,
}

// after
#[derive(MapEntities)]
struct Inventory {
  #[entities]
  items: Vec<Entity>,
  label: String,
}
```
This commit is contained in:
Carter Anderson 2025-03-20 17:18:10 -07:00 committed by GitHub
parent 55fd10502c
commit a033f1b206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 203 additions and 568 deletions

View File

@ -92,12 +92,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
Err(err) => err.into_compile_error().into(), Err(err) => err.into_compile_error().into(),
}; };
let visit_entities = visit_entities( let map_entities = map_entities(
&ast.data, &ast.data,
&bevy_ecs_path, Ident::new("this", Span::call_site()),
relationship.is_some(), relationship.is_some(),
relationship_target.is_some(), relationship_target.is_some(),
); ).map(|map_entities_impl| quote! {
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
use #bevy_ecs_path::entity::MapEntities;
#map_entities_impl
}
});
let storage = storage_path(&bevy_ecs_path, attrs.storage); let storage = storage_path(&bevy_ecs_path, attrs.storage);
@ -295,7 +300,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
#clone_behavior #clone_behavior
} }
#visit_entities #map_entities
} }
#relationship #relationship
@ -306,19 +311,18 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
const ENTITIES: &str = "entities"; const ENTITIES: &str = "entities";
fn visit_entities( pub(crate) fn map_entities(
data: &Data, data: &Data,
bevy_ecs_path: &Path, self_ident: Ident,
is_relationship: bool, is_relationship: bool,
is_relationship_target: bool, is_relationship_target: bool,
) -> TokenStream2 { ) -> Option<TokenStream2> {
match data { match data {
Data::Struct(DataStruct { fields, .. }) => { Data::Struct(DataStruct { fields, .. }) => {
let mut visit = Vec::with_capacity(fields.len()); let mut map = Vec::with_capacity(fields.len());
let mut visit_mut = Vec::with_capacity(fields.len());
let relationship = if is_relationship || is_relationship_target { let relationship = if is_relationship || is_relationship_target {
relationship_field(fields, "VisitEntities", fields.span()).ok() relationship_field(fields, "MapEntities", fields.span()).ok()
} else { } else {
None None
}; };
@ -335,27 +339,17 @@ fn visit_entities(
.clone() .clone()
.map_or(Member::from(index), Member::Named); .map_or(Member::from(index), Member::Named);
visit.push(quote!(this.#field_member.visit_entities(&mut func);)); map.push(quote!(#self_ident.#field_member.map_entities(mapper);));
visit_mut.push(quote!(this.#field_member.visit_entities_mut(&mut func);));
}); });
if visit.is_empty() { if map.is_empty() {
return quote!(); return None;
}; };
quote!( Some(quote!(
fn visit_entities(this: &Self, mut func: impl FnMut(#bevy_ecs_path::entity::Entity)) { #(#map)*
use #bevy_ecs_path::entity::VisitEntities; ))
#(#visit)*
}
fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut #bevy_ecs_path::entity::Entity)) {
use #bevy_ecs_path::entity::VisitEntitiesMut;
#(#visit_mut)*
}
)
} }
Data::Enum(DataEnum { variants, .. }) => { Data::Enum(DataEnum { variants, .. }) => {
let mut visit = Vec::with_capacity(variants.len()); let mut map = Vec::with_capacity(variants.len());
let mut visit_mut = Vec::with_capacity(variants.len());
for variant in variants.iter() { for variant in variants.iter() {
let field_members = variant let field_members = variant
@ -377,40 +371,25 @@ fn visit_entities(
.map(|member| format_ident!("__self_{}", member)) .map(|member| format_ident!("__self_{}", member))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
visit.push( map.push(
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => { quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
#(#field_idents.visit_entities(&mut func);)* #(#field_idents.map_entities(mapper);)*
}),
);
visit_mut.push(
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
#(#field_idents.visit_entities_mut(&mut func);)*
}), }),
); );
} }
if visit.is_empty() { if map.is_empty() {
return quote!(); return None;
}; };
quote!(
fn visit_entities(this: &Self, mut func: impl FnMut(#bevy_ecs_path::entity::Entity)) {
use #bevy_ecs_path::entity::VisitEntities;
match this {
#(#visit,)*
_ => {}
}
}
fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut #bevy_ecs_path::entity::Entity)) { Some(quote!(
use #bevy_ecs_path::entity::VisitEntitiesMut; match #self_ident {
match this { #(#map,)*
#(#visit_mut,)* _ => {}
_ => {}
}
} }
) ))
} }
Data::Union(_) => quote!(), Data::Union(_) => None,
} }
} }

View File

@ -9,10 +9,13 @@ mod query_filter;
mod states; mod states;
mod world_query; mod world_query;
use crate::{query_data::derive_query_data_impl, query_filter::derive_query_filter_impl}; use crate::{
component::map_entities, query_data::derive_query_data_impl,
query_filter::derive_query_filter_impl,
};
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest}; use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Ident, Span};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{ use syn::{
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
@ -185,105 +188,22 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}) })
} }
fn derive_visit_entities_base( #[proc_macro_derive(MapEntities, attributes(entities))]
input: TokenStream, pub fn derive_map_entities(input: TokenStream) -> TokenStream {
trait_name: TokenStream2,
gen_methods: impl FnOnce(Vec<TokenStream2>) -> TokenStream2,
) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path(); let ecs_path = bevy_ecs_path();
let map_entities_impl = map_entities(
let named_fields = match get_struct_fields(&ast.data) { &ast.data,
Ok(fields) => fields, Ident::new("self", Span::call_site()),
Err(e) => return e.into_compile_error().into(), false,
}; false,
);
let field = named_fields
.iter()
.filter_map(|field| {
if let Some(attr) = field
.attrs
.iter()
.find(|a| a.path().is_ident("visit_entities"))
{
let ignore = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("ignore") {
Ok(())
} else {
Err(meta.error("Invalid visit_entities attribute. Use `ignore`"))
}
});
return match ignore {
Ok(()) => None,
Err(e) => Some(Err(e)),
};
}
Some(Ok(field))
})
.map(|res| res.map(|field| field.ident.as_ref()))
.collect::<Result<Vec<_>, _>>();
let field = match field {
Ok(field) => field,
Err(e) => return e.into_compile_error().into(),
};
if field.is_empty() {
return syn::Error::new(
ast.span(),
format!("Invalid `{}` type: at least one field", trait_name),
)
.into_compile_error()
.into();
}
let field_access = field
.iter()
.enumerate()
.map(|(n, f)| {
if let Some(ident) = f {
quote! {
self.#ident
}
} else {
let idx = Index::from(n);
quote! {
self.#idx
}
}
})
.collect::<Vec<_>>();
let methods = gen_methods(field_access);
let generics = ast.generics;
let (impl_generics, ty_generics, _) = generics.split_for_impl();
let struct_name = &ast.ident; let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! { TokenStream::from(quote! {
impl #impl_generics #ecs_path::entity:: #trait_name for #struct_name #ty_generics { impl #impl_generics #ecs_path::entity::MapEntities for #struct_name #type_generics #where_clause {
#methods fn map_entities<M: #ecs_path::entity::EntityMapper>(&mut self, mapper: &mut M) {
} #map_entities_impl
})
}
#[proc_macro_derive(VisitEntitiesMut, attributes(visit_entities))]
pub fn derive_visit_entities_mut(input: TokenStream) -> TokenStream {
derive_visit_entities_base(input, quote! { VisitEntitiesMut }, |field| {
quote! {
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
#(#field.visit_entities_mut(&mut f);)*
}
}
})
}
#[proc_macro_derive(VisitEntities, attributes(visit_entities))]
pub fn derive_visit_entities(input: TokenStream) -> TokenStream {
derive_visit_entities_base(input, quote! { VisitEntities }, |field| {
quote! {
fn visit_entities<F: FnMut(Entity)>(&self, mut f: F) {
#(#field.visit_entities(&mut f);)*
} }
} }
}) })

View File

@ -4,12 +4,12 @@ use crate::{
archetype::ArchetypeFlags, archetype::ArchetypeFlags,
bundle::BundleInfo, bundle::BundleInfo,
change_detection::{MaybeLocation, MAX_CHANGE_AGE}, change_detection::{MaybeLocation, MAX_CHANGE_AGE},
entity::{ComponentCloneCtx, Entity, SourceComponent}, entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
query::DebugCheckedUnwrap, query::DebugCheckedUnwrap,
relationship::RelationshipHookMode, relationship::RelationshipHookMode,
resource::Resource, resource::Resource,
storage::{SparseSetIndex, SparseSets, Table, TableRow}, storage::{SparseSetIndex, SparseSets, Table, TableRow},
system::{Commands, Local, SystemParam}, system::{Local, SystemParam},
world::{DeferredWorld, FromWorld, World}, world::{DeferredWorld, FromWorld, World},
}; };
use alloc::boxed::Box; use alloc::boxed::Box;
@ -517,14 +517,21 @@ pub trait Component: Send + Sync + 'static {
ComponentCloneBehavior::Default ComponentCloneBehavior::Default
} }
/// Visits entities stored on the component. /// Maps the entities on this component using the given [`EntityMapper`]. This is used to remap entities in contexts like scenes and entity cloning.
/// When deriving [`Component`], this is populated by annotating fields containing entities with `#[entities]`
///
/// ```
/// # use bevy_ecs::{component::Component, entity::Entity};
/// #[derive(Component)]
/// struct Inventory {
/// #[entities]
/// items: Vec<Entity>
/// }
/// ```
///
/// Fields with `#[entities]` must implement [`MapEntities`](crate::entity::MapEntities).
#[inline] #[inline]
fn visit_entities(_this: &Self, _f: impl FnMut(Entity)) {} fn map_entities<E: EntityMapper>(_this: &mut Self, _mapper: &mut E) {}
/// 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 { mod private {
@ -1158,7 +1165,7 @@ impl ComponentDescriptor {
} }
/// Function type that can be used to clone an entity. /// Function type that can be used to clone an entity.
pub type ComponentCloneFn = fn(&mut Commands, &SourceComponent, &mut ComponentCloneCtx); pub type ComponentCloneFn = fn(&SourceComponent, &mut ComponentCloneCtx);
/// The clone behavior to use when cloning a [`Component`]. /// The clone behavior to use when cloning a [`Component`].
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
@ -2925,7 +2932,6 @@ pub fn enforce_no_required_components_recursion(
/// It will panic if set as handler for any other component. /// It will panic if set as handler for any other component.
/// ///
pub fn component_clone_via_clone<C: Clone + Component>( pub fn component_clone_via_clone<C: Clone + Component>(
_commands: &mut Commands,
source: &SourceComponent, source: &SourceComponent,
ctx: &mut ComponentCloneCtx, ctx: &mut ComponentCloneCtx,
) { ) {
@ -2952,11 +2958,7 @@ pub fn component_clone_via_clone<C: Clone + Component>(
/// ///
/// [`PartialReflect::reflect_clone`]: bevy_reflect::PartialReflect::reflect_clone /// [`PartialReflect::reflect_clone`]: bevy_reflect::PartialReflect::reflect_clone
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
pub fn component_clone_via_reflect( pub fn component_clone_via_reflect(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
let Some(app_registry) = ctx.type_registry().cloned() else { let Some(app_registry) = ctx.type_registry().cloned() else {
return; return;
}; };
@ -2973,9 +2975,7 @@ pub fn component_clone_via_reflect(
if let Some(reflect_component) = if let Some(reflect_component) =
registry.get_type_data::<crate::reflect::ReflectComponent>(type_id) registry.get_type_data::<crate::reflect::ReflectComponent>(type_id)
{ {
reflect_component.visit_entities_mut(&mut *component, &mut |entity| { reflect_component.map_entities(&mut *component, ctx.entity_mapper());
*entity = ctx.entity_mapper().get_mapped(*entity);
});
} }
drop(registry); drop(registry);
@ -2993,9 +2993,7 @@ pub fn component_clone_via_reflect(
if let Some(reflect_component) = if let Some(reflect_component) =
registry.get_type_data::<crate::reflect::ReflectComponent>(type_id) registry.get_type_data::<crate::reflect::ReflectComponent>(type_id)
{ {
reflect_component.visit_entities_mut(&mut *component, &mut |entity| { reflect_component.map_entities(&mut *component, ctx.entity_mapper());
*entity = ctx.entity_mapper().get_mapped(*entity);
});
} }
drop(registry); drop(registry);
@ -3018,23 +3016,12 @@ pub fn component_clone_via_reflect(
registry.get_type_data::<crate::reflect::ReflectFromWorld>(type_id) registry.get_type_data::<crate::reflect::ReflectFromWorld>(type_id)
{ {
let reflect_from_world = reflect_from_world.clone(); 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.to_dynamic(); let source_component_cloned = source_component_reflect.to_dynamic();
let component_layout = component_info.layout(); let component_layout = component_info.layout();
let target = ctx.target(); let target = ctx.target();
let component_id = ctx.component_id(); let component_id = ctx.component_id();
for entity in mapped_entities.iter_mut() {
*entity = ctx.entity_mapper().get_mapped(*entity);
}
drop(registry); drop(registry);
commands.queue(move |world: &mut World| { ctx.queue_deferred(move |world: &mut World, mapper: &mut dyn EntityMapper| {
let mut component = reflect_from_world.from_world(world); let mut component = reflect_from_world.from_world(world);
assert_eq!(type_id, (*component).type_id()); assert_eq!(type_id, (*component).type_id());
component.apply(source_component_cloned.as_partial_reflect()); component.apply(source_component_cloned.as_partial_reflect());
@ -3042,11 +3029,7 @@ pub fn component_clone_via_reflect(
.read() .read()
.get_type_data::<crate::reflect::ReflectComponent>(type_id) .get_type_data::<crate::reflect::ReflectComponent>(type_id)
{ {
let mut i = 0; reflect_component.map_entities(&mut *component, mapper);
reflect_component.visit_entities_mut(&mut *component, &mut |entity| {
*entity = mapped_entities[i];
i += 1;
});
} }
// SAFETY: // SAFETY:
// - component_id is from the same world as target entity // - component_id is from the same world as target entity
@ -3070,12 +3053,7 @@ pub fn component_clone_via_reflect(
/// Noop implementation of component clone handler function. /// Noop implementation of component clone handler function.
/// ///
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details. /// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
pub fn component_clone_ignore( pub fn component_clone_ignore(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {}
_commands: &mut Commands,
_source: &SourceComponent,
_ctx: &mut ComponentCloneCtx,
) {
}
/// Wrapper for components clone specialization using autoderef. /// Wrapper for components clone specialization using autoderef.
#[doc(hidden)] #[doc(hidden)]

View File

@ -1,22 +1,15 @@
use alloc::{borrow::ToOwned, collections::VecDeque, vec::Vec}; use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, vec::Vec};
use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_platform_support::collections::{HashMap, HashSet};
use bevy_ptr::{Ptr, PtrMut}; use bevy_ptr::{Ptr, PtrMut};
use bumpalo::Bump; use bumpalo::Bump;
use core::any::TypeId; use core::any::TypeId;
#[cfg(feature = "bevy_reflect")]
use alloc::boxed::Box;
use crate::component::{ComponentCloneBehavior, ComponentCloneFn};
use crate::entity::hash_map::EntityHashMap;
use crate::entity::{Entities, EntityMapper};
use crate::relationship::RelationshipHookMode;
use crate::system::Commands;
use crate::{ use crate::{
bundle::Bundle, bundle::Bundle,
component::{Component, ComponentId, ComponentInfo}, component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo},
entity::Entity, entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper},
query::DebugCheckedUnwrap, query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
world::World, world::World,
}; };
@ -176,9 +169,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// - Component being written is not registered in the world. /// - Component being written is not registered in the world.
/// - `ComponentId` of component being written does not match expected `ComponentId`. /// - `ComponentId` of component being written does not match expected `ComponentId`.
pub fn write_target_component<C: Component>(&mut self, mut component: C) { pub fn write_target_component<C: Component>(&mut self, mut component: C) {
C::visit_entities_mut(&mut component, |entity| { C::map_entities(&mut component, &mut self.mapper);
*entity = self.mapper.get_mapped(*entity);
});
let short_name = disqualified::ShortName::of::<C>(); let short_name = disqualified::ShortName::of::<C>();
if self.target_component_written { if self.target_component_written {
panic!("Trying to write component '{short_name}' multiple times") panic!("Trying to write component '{short_name}' multiple times")
@ -280,6 +271,17 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
self.mapper.set_mapped(entity, target); self.mapper.set_mapped(entity, target);
self.entity_cloner.clone_queue.push_back(entity); self.entity_cloner.clone_queue.push_back(entity);
} }
/// Queues a deferred clone operation, which will run with exclusive [`World`] access immediately after calling the clone handler for each component on an entity.
/// This exists, despite its similarity to [`Commands`](crate::system::Commands), to provide access to the entity mapper in the current context.
pub fn queue_deferred(
&mut self,
deferred: impl FnOnce(&mut World, &mut dyn EntityMapper) + 'static,
) {
self.entity_cloner
.deferred_commands
.push_back(Box::new(deferred));
}
} }
/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build`], which /// A configuration determining how to clone entities. This can be built using [`EntityCloner::build`], which
@ -341,7 +343,6 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// 2. component-defined handler using [`Component::clone_behavior`] /// 2. component-defined handler using [`Component::clone_behavior`]
/// 3. default handler override using [`EntityClonerBuilder::with_default_clone_fn`]. /// 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. /// 4. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not.
#[derive(Debug)]
pub struct EntityCloner { pub struct EntityCloner {
filter_allows_components: bool, filter_allows_components: bool,
filter: HashSet<ComponentId>, filter: HashSet<ComponentId>,
@ -350,18 +351,20 @@ pub struct EntityCloner {
linked_cloning: bool, linked_cloning: bool,
default_clone_fn: ComponentCloneFn, default_clone_fn: ComponentCloneFn,
clone_queue: VecDeque<Entity>, clone_queue: VecDeque<Entity>,
deferred_commands: VecDeque<Box<dyn FnOnce(&mut World, &mut dyn EntityMapper)>>,
} }
impl Default for EntityCloner { impl Default for EntityCloner {
fn default() -> Self { fn default() -> Self {
Self { Self {
filter_allows_components: false, filter_allows_components: false,
filter: Default::default(),
clone_behavior_overrides: Default::default(),
move_components: false, move_components: false,
linked_cloning: false, linked_cloning: false,
default_clone_fn: ComponentCloneBehavior::global_default_fn(), default_clone_fn: ComponentCloneBehavior::global_default_fn(),
filter: Default::default(),
clone_behavior_overrides: Default::default(),
clone_queue: Default::default(), clone_queue: Default::default(),
deferred_commands: Default::default(),
} }
} }
} }
@ -476,10 +479,6 @@ impl EntityCloner {
let archetype = source_entity.archetype(); let archetype = source_entity.archetype();
bundle_scratch = BundleScratch::with_capacity(archetype.component_count()); 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() { for component in archetype.components() {
if !self.is_cloning_allowed(&component) { if !self.is_cloning_allowed(&component) {
@ -527,12 +526,16 @@ impl EntityCloner {
) )
}; };
(handler)(&mut commands, &source_component, &mut ctx); (handler)(&source_component, &mut ctx);
} }
} }
world.flush(); world.flush();
for deferred in self.deferred_commands.drain(..) {
(deferred)(world, mapper);
}
if !world.entities.contains(target) { if !world.entities.contains(target) {
panic!("Target entity does not exist"); panic!("Target entity does not exist");
} }
@ -609,7 +612,6 @@ impl EntityCloner {
} }
/// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information. /// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information.
#[derive(Debug)]
pub struct EntityClonerBuilder<'w> { pub struct EntityClonerBuilder<'w> {
world: &'w mut World, world: &'w mut World,
entity_cloner: EntityCloner, entity_cloner: EntityCloner,
@ -845,7 +847,6 @@ mod tests {
entity::{hash_map::EntityHashMap, Entity, EntityCloner, SourceComponent}, entity::{hash_map::EntityHashMap, Entity, EntityCloner, SourceComponent},
prelude::{ChildOf, Children, Resource}, prelude::{ChildOf, Children, Resource},
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
system::Commands,
world::{FromWorld, World}, world::{FromWorld, World},
}; };
use alloc::vec::Vec; use alloc::vec::Vec;
@ -861,7 +862,6 @@ mod tests {
component::{Component, ComponentCloneBehavior}, component::{Component, ComponentCloneBehavior},
entity::{EntityCloner, SourceComponent}, entity::{EntityCloner, SourceComponent},
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
system::Commands,
}; };
use alloc::vec; use alloc::vec;
use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr}; use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr};
@ -991,11 +991,7 @@ mod tests {
#[derive(Component, Reflect)] #[derive(Component, Reflect)]
struct B; struct B;
fn test_handler( fn test_handler(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
_commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
let registry = ctx.type_registry().unwrap(); let registry = ctx.type_registry().unwrap();
assert!(source.read_reflect(&registry.read()).is_none()); assert!(source.read_reflect(&registry.read()).is_none());
} }
@ -1287,11 +1283,7 @@ mod tests {
#[test] #[test]
fn clone_entity_with_dynamic_components() { fn clone_entity_with_dynamic_components() {
const COMPONENT_SIZE: usize = 10; const COMPONENT_SIZE: usize = 10;
fn test_handler( fn test_handler(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
_commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
// SAFETY: the passed in ptr corresponds to copy-able data that matches the type of the source / target component // SAFETY: the passed in ptr corresponds to copy-able data that matches the type of the source / target component
unsafe { unsafe {
ctx.write_target_component_ptr(source.ptr()); ctx.write_target_component_ptr(source.ptr());

View File

@ -1,10 +1,15 @@
pub use bevy_ecs_macros::MapEntities;
use crate::{ use crate::{
entity::Entity, entity::{hash_map::EntityHashMap, Entity},
identifier::masks::{IdentifierMask, HIGH_MASK}, identifier::masks::{IdentifierMask, HIGH_MASK},
world::World, world::World,
}; };
use super::{hash_map::EntityHashMap, VisitEntitiesMut}; use alloc::{collections::VecDeque, vec::Vec};
use bevy_platform_support::collections::HashSet;
use core::hash::BuildHasher;
use smallvec::SmallVec;
/// Operation to map all contained [`Entity`] fields in a type to new values. /// Operation to map all contained [`Entity`] fields in a type to new values.
/// ///
@ -15,13 +20,9 @@ use super::{hash_map::EntityHashMap, VisitEntitiesMut};
/// (usually by using an [`EntityHashMap<Entity>`] between source entities and entities in the /// (usually by using an [`EntityHashMap<Entity>`] between source entities and entities in the
/// current world). /// current world).
/// ///
/// This trait is similar to [`VisitEntitiesMut`]. They differ in that [`VisitEntitiesMut`] operates /// Components use [`Component::map_entities`](crate::component::Component::map_entities) to map
/// on `&mut Entity` and allows for in-place modification, while this trait makes no assumption that /// entities in the context of scenes and entity cloning, which generally uses [`MapEntities`] internally
/// such in-place modification is occurring, which is impossible for types such as [`HashSet<Entity>`] /// to map each field (see those docs for usage).
/// and [`EntityHashMap`] which must be rebuilt when their contained [`Entity`]s are remapped.
///
/// Implementing this trait correctly is required for properly loading components
/// with entity references from scenes.
/// ///
/// [`HashSet<Entity>`]: bevy_platform_support::collections::HashSet /// [`HashSet<Entity>`]: bevy_platform_support::collections::HashSet
/// ///
@ -49,17 +50,51 @@ pub trait MapEntities {
/// ///
/// Implementors should look up any and all [`Entity`] values stored within `self` and /// Implementors should look up any and all [`Entity`] values stored within `self` and
/// update them to the mapped values via `entity_mapper`. /// update them to the mapped values via `entity_mapper`.
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M); fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E);
} }
impl<T: VisitEntitiesMut> MapEntities for T { impl MapEntities for Entity {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) { fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
self.visit_entities_mut(|entity| { *self = entity_mapper.get_mapped(*self);
*entity = entity_mapper.get_mapped(*entity);
});
} }
} }
impl MapEntities for Option<Entity> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
if let Some(entity) = self {
*entity = entity_mapper.get_mapped(*entity);
}
}
}
impl<S: BuildHasher + Default> MapEntities for HashSet<Entity, S> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = self.drain().map(|e| entity_mapper.get_mapped(e)).collect();
}
}
impl MapEntities for Vec<Entity> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
*entity = entity_mapper.get_mapped(*entity);
}
}
}
impl MapEntities for VecDeque<Entity> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
*entity = entity_mapper.get_mapped(*entity);
}
}
}
impl<A: smallvec::Array<Item = Entity>> MapEntities for SmallVec<A> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
*entity = entity_mapper.get_mapped(*entity);
}
}
}
/// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`]. /// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`].
/// ///
/// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities /// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities
@ -67,8 +102,7 @@ impl<T: VisitEntitiesMut> MapEntities for T {
/// ///
/// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World). /// 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) /// This is used by [`MapEntities`] implementors.
/// and [`Component::visit_entities_mut`](crate::component::Component::visit_entities_mut) to map a component's entities.
/// ///
/// ## Example /// ## Example
/// ///

View File

@ -39,7 +39,6 @@
mod clone_entities; mod clone_entities;
mod entity_set; mod entity_set;
mod map_entities; mod map_entities;
mod visit_entities;
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))] #[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
@ -48,7 +47,6 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
pub use clone_entities::*; pub use clone_entities::*;
pub use entity_set::*; pub use entity_set::*;
pub use map_entities::*; pub use map_entities::*;
pub use visit_entities::*;
mod hash; mod hash;
pub use hash::*; pub use hash::*;

View File

@ -1,149 +0,0 @@
pub use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut};
use crate::entity::Entity;
/// Apply an operation to all entities in a container.
///
/// This is implemented by default for types that implement [`IntoIterator`].
///
/// It may be useful to implement directly for types that can't produce an
/// iterator for lifetime reasons, such as those involving internal mutexes.
pub trait VisitEntities {
/// Apply an operation to all contained entities.
fn visit_entities<F: FnMut(Entity)>(&self, f: F);
}
impl<T> VisitEntities for T
where
for<'a> &'a T: IntoIterator<Item = &'a Entity>,
{
fn visit_entities<F: FnMut(Entity)>(&self, f: F) {
self.into_iter().copied().for_each(f);
}
}
impl VisitEntities for Entity {
fn visit_entities<F: FnMut(Entity)>(&self, mut f: F) {
f(*self);
}
}
/// Apply an operation to mutable references to all entities in a container.
///
/// This is implemented by default for types that implement [`IntoIterator`].
///
/// It may be useful to implement directly for types that can't produce an
/// iterator for lifetime reasons, such as those involving internal mutexes.
pub trait VisitEntitiesMut: VisitEntities {
/// Apply an operation to mutable references to all contained entities.
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, f: F);
}
impl<T: VisitEntities> VisitEntitiesMut for T
where
for<'a> &'a mut T: IntoIterator<Item = &'a mut Entity>,
{
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, f: F) {
self.into_iter().for_each(f);
}
}
impl VisitEntitiesMut for Entity {
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
f(self);
}
}
#[cfg(test)]
mod tests {
use crate::{
entity::{hash_map::EntityHashMap, MapEntities, SceneEntityMapper},
world::World,
};
use alloc::{string::String, vec, vec::Vec};
use bevy_platform_support::collections::HashSet;
use super::*;
#[derive(VisitEntities, Debug, PartialEq)]
struct Foo {
ordered: Vec<Entity>,
unordered: HashSet<Entity>,
single: Entity,
#[visit_entities(ignore)]
not_an_entity: String,
}
// Need a manual impl since VisitEntitiesMut isn't implemented for `HashSet`.
// We don't expect users to actually do this - it's only for test purposes
// to prove out the automatic `MapEntities` impl we get with `VisitEntitiesMut`.
impl VisitEntitiesMut for Foo {
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
self.ordered.visit_entities_mut(&mut f);
self.unordered = self
.unordered
.drain()
.map(|mut entity| {
f(&mut entity);
entity
})
.collect();
f(&mut self.single);
}
}
#[test]
fn visit_entities() {
let mut world = World::new();
let entities = world.entities();
let mut foo = Foo {
ordered: vec![entities.reserve_entity(), entities.reserve_entity()],
unordered: [
entities.reserve_entity(),
entities.reserve_entity(),
entities.reserve_entity(),
]
.into_iter()
.collect(),
single: entities.reserve_entity(),
not_an_entity: "Bar".into(),
};
let mut entity_map = EntityHashMap::<Entity>::default();
let mut remapped = Foo {
ordered: vec![],
unordered: HashSet::default(),
single: Entity::PLACEHOLDER,
not_an_entity: foo.not_an_entity.clone(),
};
// Note: this assumes that the VisitEntities derive is field-ordered,
// which isn't explicitly stated/guaranteed.
// If that changes, this test will fail, but that might be OK if
// we're intentionally breaking that assumption.
let mut i = 0;
foo.visit_entities(|entity| {
let new_entity = entities.reserve_entity();
if i < foo.ordered.len() {
assert_eq!(entity, foo.ordered[i]);
remapped.ordered.push(new_entity);
} else if i < foo.ordered.len() + foo.unordered.len() {
assert!(foo.unordered.contains(&entity));
remapped.unordered.insert(new_entity);
} else {
assert_eq!(entity, foo.single);
remapped.single = new_entity;
}
entity_map.insert(entity, new_entity);
i += 1;
});
SceneEntityMapper::world_scope(&mut entity_map, &mut world, |_, mapper| {
foo.map_entities(mapper);
});
assert_eq!(foo, remapped);
}
}

View File

@ -133,7 +133,7 @@ mod tests {
bundle::Bundle, bundle::Bundle,
change_detection::Ref, change_detection::Ref,
component::{Component, ComponentId, RequiredComponents, RequiredComponentsError}, component::{Component, ComponentId, RequiredComponents, RequiredComponentsError},
entity::Entity, entity::{Entity, EntityMapper},
entity_disabling::DefaultQueryFilters, entity_disabling::DefaultQueryFilters,
prelude::Or, prelude::Or,
query::{Added, Changed, FilteredAccess, QueryFilter, With, Without}, query::{Added, Changed, FilteredAccess, QueryFilter, With, Without},
@ -146,7 +146,6 @@ mod tests {
vec, vec,
vec::Vec, vec::Vec,
}; };
use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut};
use bevy_platform_support::collections::HashSet; use bevy_platform_support::collections::HashSet;
use bevy_tasks::{ComputeTaskPool, TaskPool}; use bevy_tasks::{ComputeTaskPool, TaskPool};
use core::{ use core::{
@ -2705,8 +2704,19 @@ mod tests {
World::new().register_component::<A>(); World::new().register_component::<A>();
} }
#[derive(Default)]
struct CaptureMapper(Vec<Entity>);
impl EntityMapper for CaptureMapper {
fn get_mapped(&mut self, source: Entity) -> Entity {
self.0.push(source);
source
}
fn set_mapped(&mut self, _source: Entity, _target: Entity) {}
}
#[test] #[test]
fn visit_struct_entities() { fn map_struct_entities() {
#[derive(Component)] #[derive(Component)]
#[expect( #[expect(
unused, unused,
@ -2733,30 +2743,22 @@ mod tests {
let e3 = world.spawn_empty().id(); let e3 = world.spawn_empty().id();
let mut foo = Foo(1, e1); let mut foo = Foo(1, e1);
let mut entities = Vec::new(); let mut mapper = CaptureMapper::default();
Component::visit_entities(&foo, |e| entities.push(e)); Component::map_entities(&mut foo, &mut mapper);
assert_eq!(&entities, &[e1]); assert_eq!(&mapper.0, &[e1]);
let mut entities = Vec::new();
Component::visit_entities_mut(&mut foo, |e| entities.push(*e));
assert_eq!(&entities, &[e1]);
let mut bar = Bar { let mut bar = Bar {
a: e1, a: e1,
b: 1, b: 1,
c: vec![e2, e3], c: vec![e2, e3],
}; };
let mut entities = Vec::new(); let mut mapper = CaptureMapper::default();
Component::visit_entities(&bar, |e| entities.push(e)); Component::map_entities(&mut bar, &mut mapper);
assert_eq!(&entities, &[e1, e2, e3]); assert_eq!(&mapper.0, &[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] #[test]
fn visit_enum_entities() { fn map_enum_entities() {
#[derive(Component)] #[derive(Component)]
#[expect( #[expect(
unused, unused,
@ -2779,26 +2781,18 @@ mod tests {
let e3 = world.spawn_empty().id(); let e3 = world.spawn_empty().id();
let mut foo = Foo::Bar(1, e1); let mut foo = Foo::Bar(1, e1);
let mut entities = Vec::new(); let mut mapper = CaptureMapper::default();
Component::visit_entities(&foo, |e| entities.push(e)); Component::map_entities(&mut foo, &mut mapper);
assert_eq!(&entities, &[e1]); assert_eq!(&mapper.0, &[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 { let mut foo = Foo::Baz {
a: e1, a: e1,
b: 1, b: 1,
c: vec![e2, e3], c: vec![e2, e3],
}; };
let mut entities = Vec::new(); let mut mapper = CaptureMapper::default();
Component::visit_entities(&foo, |e| entities.push(e)); Component::map_entities(&mut foo, &mut mapper);
assert_eq!(&entities, &[e1, e2, e3]); assert_eq!(&mapper.0, &[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( #[expect(
@ -2827,16 +2821,18 @@ mod tests {
field1: ComponentB, field1: ComponentB,
} }
#[derive(Component, VisitEntities, VisitEntitiesMut)] #[derive(Component)]
struct MyEntities { struct MyEntities {
#[entities]
entities: Vec<Entity>, entities: Vec<Entity>,
#[entities]
another_one: Entity, another_one: Entity,
#[entities]
maybe_entity: Option<Entity>, maybe_entity: Option<Entity>,
#[expect( #[expect(
dead_code, dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such this field is intentionally never used." reason = "This struct is used as a compilation test to test the derive macros, and as such this field is intentionally never used."
)] )]
#[visit_entities(ignore)]
something_else: String, something_else: String,
} }
@ -2844,6 +2840,6 @@ mod tests {
dead_code, dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)] )]
#[derive(Component, VisitEntities, VisitEntitiesMut)] #[derive(Component)]
struct MyEntitiesTuple(Vec<Entity>, Entity, #[visit_entities(ignore)] usize); struct MyEntitiesTuple(#[entities] Vec<Entity>, #[entities] Entity, usize);
} }

View File

@ -2,9 +2,8 @@ use crate::{
component::{ component::{
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType, Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
}, },
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, SourceComponent}, entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
observer::ObserverState, observer::ObserverState,
system::Commands,
world::World, world::World,
}; };
use alloc::vec::Vec; use alloc::vec::Vec;
@ -64,15 +63,11 @@ impl EntityClonerBuilder<'_> {
} }
} }
fn component_clone_observed_by( fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
commands: &mut Commands,
_source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
let target = ctx.target(); let target = ctx.target();
let source = ctx.source(); let source = ctx.source();
commands.queue(move |world: &mut World| { ctx.queue_deferred(move |world: &mut World, _mapper: &mut dyn EntityMapper| {
let observed_by = world let observed_by = world
.get::<ObservedBy>(source) .get::<ObservedBy>(source)
.map(|observed_by| observed_by.0.clone()) .map(|observed_by| observed_by.0.clone())

View File

@ -121,10 +121,8 @@ pub struct ReflectComponentFns {
pub reflect: fn(FilteredEntityRef) -> Option<&dyn Reflect>, pub reflect: fn(FilteredEntityRef) -> Option<&dyn Reflect>,
/// Function pointer implementing [`ReflectComponent::reflect_mut()`]. /// Function pointer implementing [`ReflectComponent::reflect_mut()`].
pub reflect_mut: fn(FilteredEntityMut) -> Option<Mut<dyn Reflect>>, pub reflect_mut: fn(FilteredEntityMut) -> Option<Mut<dyn Reflect>>,
/// Function pointer implementing [`ReflectComponent::visit_entities()`]. /// Function pointer implementing [`ReflectComponent::map_entities()`].
pub visit_entities: fn(&dyn Reflect, &mut dyn FnMut(Entity)), pub map_entities: fn(&mut dyn Reflect, &mut dyn EntityMapper),
/// 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()`]. /// Function pointer implementing [`ReflectComponent::reflect_unchecked_mut()`].
/// ///
/// # Safety /// # Safety
@ -291,18 +289,9 @@ impl ReflectComponent {
&self.0 &self.0
} }
/// Calls a dynamic version of [`Component::visit_entities`]. /// Calls a dynamic version of [`Component::map_entities`].
pub fn visit_entities(&self, component: &dyn Reflect, func: &mut dyn FnMut(Entity)) { pub fn map_entities(&self, component: &mut dyn Reflect, func: &mut dyn EntityMapper) {
(self.0.visit_entities)(component, func); (self.0.map_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);
} }
} }
@ -330,19 +319,18 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
apply_or_insert_mapped: |entity, apply_or_insert_mapped: |entity,
reflected_component, reflected_component,
registry, registry,
mapper, mut mapper,
relationship_hook_mode| { relationship_hook_mode| {
let map_fn = map_function(mapper);
if C::Mutability::MUTABLE { if C::Mutability::MUTABLE {
// SAFETY: guard ensures `C` is a mutable component // SAFETY: guard ensures `C` is a mutable component
if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::<C>() } { if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::<C>() } {
component.apply(reflected_component.as_partial_reflect()); component.apply(reflected_component.as_partial_reflect());
C::visit_entities_mut(&mut component, map_fn); C::map_entities(&mut component, &mut mapper);
} else { } else {
let mut component = entity.world_scope(|world| { let mut component = entity.world_scope(|world| {
from_reflect_with_fallback::<C>(reflected_component, world, registry) from_reflect_with_fallback::<C>(reflected_component, world, registry)
}); });
C::visit_entities_mut(&mut component, map_fn); C::map_entities(&mut component, &mut mapper);
entity entity
.insert_with_relationship_hook_mode(component, relationship_hook_mode); .insert_with_relationship_hook_mode(component, relationship_hook_mode);
} }
@ -350,7 +338,7 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
let mut component = entity.world_scope(|world| { let mut component = entity.world_scope(|world| {
from_reflect_with_fallback::<C>(reflected_component, world, registry) from_reflect_with_fallback::<C>(reflected_component, world, registry)
}); });
C::visit_entities_mut(&mut component, map_fn); C::map_entities(&mut component, &mut mapper);
entity.insert_with_relationship_hook_mode(component, relationship_hook_mode); entity.insert_with_relationship_hook_mode(component, relationship_hook_mode);
} }
}, },
@ -395,20 +383,10 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
register_component: |world: &mut World| -> ComponentId { register_component: |world: &mut World| -> ComponentId {
world.register_component::<C>() world.register_component::<C>()
}, },
visit_entities: |reflect: &dyn Reflect, func: &mut dyn FnMut(Entity)| { map_entities: |reflect: &mut dyn Reflect, mut mapper: &mut dyn EntityMapper| {
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(); let component = reflect.downcast_mut::<C>().unwrap();
Component::visit_entities_mut(component, func); Component::map_entities(component, &mut mapper);
}, },
}) })
} }
} }
fn map_function(mapper: &mut dyn EntityMapper) -> impl FnMut(&mut Entity) + '_ {
move |entity: &mut Entity| {
*entity = mapper.get_mapped(*entity);
}
}

View File

@ -17,7 +17,6 @@ mod entity_commands;
mod from_world; mod from_world;
mod map_entities; mod map_entities;
mod resource; mod resource;
mod visit_entities;
pub use bundle::{ReflectBundle, ReflectBundleFns}; pub use bundle::{ReflectBundle, ReflectBundleFns};
pub use component::{ReflectComponent, ReflectComponentFns}; pub use component::{ReflectComponent, ReflectComponentFns};
@ -25,7 +24,6 @@ pub use entity_commands::ReflectCommandExt;
pub use from_world::{ReflectFromWorld, ReflectFromWorldFns}; pub use from_world::{ReflectFromWorld, ReflectFromWorldFns};
pub use map_entities::ReflectMapEntities; pub use map_entities::ReflectMapEntities;
pub use resource::{ReflectResource, ReflectResourceFns}; pub use resource::{ReflectResource, ReflectResourceFns};
pub use visit_entities::{ReflectVisitEntities, ReflectVisitEntitiesMut};
/// A [`Resource`] storing [`TypeRegistry`] for /// A [`Resource`] storing [`TypeRegistry`] for
/// type registrations relevant to a whole app. /// type registrations relevant to a whole app.

View File

@ -1,62 +0,0 @@
use crate::entity::{Entity, VisitEntities, VisitEntitiesMut};
use bevy_reflect::{FromReflect, FromType, PartialReflect};
/// For a reflected value, apply an operation to all contained entities.
///
/// See [`VisitEntities`] for more details.
#[derive(Clone)]
pub struct ReflectVisitEntities {
visit_entities: fn(&dyn PartialReflect, &mut dyn FnMut(Entity)),
}
impl ReflectVisitEntities {
/// A general method for applying an operation to all entities in a
/// reflected component.
pub fn visit_entities(&self, component: &dyn PartialReflect, f: &mut dyn FnMut(Entity)) {
(self.visit_entities)(component, f);
}
}
impl<C: FromReflect + VisitEntities> FromType<C> for ReflectVisitEntities {
fn from_type() -> Self {
ReflectVisitEntities {
visit_entities: |component, f| {
let concrete = C::from_reflect(component).unwrap();
concrete.visit_entities(f);
},
}
}
}
/// For a reflected value, apply an operation to mutable references to all
/// contained entities.
///
/// See [`VisitEntitiesMut`] for more details.
#[derive(Clone)]
pub struct ReflectVisitEntitiesMut {
visit_entities_mut: fn(&mut dyn PartialReflect, &mut dyn FnMut(&mut Entity)),
}
impl ReflectVisitEntitiesMut {
/// A general method for applying an operation to all entities in a
/// reflected component.
pub fn visit_entities(
&self,
component: &mut dyn PartialReflect,
f: &mut dyn FnMut(&mut Entity),
) {
(self.visit_entities_mut)(component, f);
}
}
impl<C: FromReflect + VisitEntitiesMut> FromType<C> for ReflectVisitEntitiesMut {
fn from_type() -> Self {
ReflectVisitEntitiesMut {
visit_entities_mut: |component, f| {
let mut concrete = C::from_reflect(component).unwrap();
concrete.visit_entities_mut(f);
component.apply(&concrete);
},
}
}
}

View File

@ -14,10 +14,7 @@ use crate::{
component::{Component, HookContext, Mutable}, component::{Component, HookContext, Mutable},
entity::{ComponentCloneCtx, Entity, SourceComponent}, entity::{ComponentCloneCtx, Entity, SourceComponent},
error::{ignore, CommandWithEntity, HandleError}, error::{ignore, CommandWithEntity, HandleError},
system::{ system::entity_command::{self},
entity_command::{self},
Commands,
},
world::{DeferredWorld, EntityWorldMut}, world::{DeferredWorld, EntityWorldMut},
}; };
use log::warn; use log::warn;
@ -304,7 +301,6 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured /// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured
/// to spawn recursively. /// to spawn recursively.
pub fn clone_relationship_target<T: RelationshipTarget>( pub fn clone_relationship_target<T: RelationshipTarget>(
_commands: &mut Commands,
source: &SourceComponent, source: &SourceComponent,
context: &mut ComponentCloneCtx, context: &mut ComponentCloneCtx,
) { ) {

View File

@ -214,24 +214,24 @@ where
mod tests { mod tests {
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
entity::{ entity::{hash_map::EntityHashMap, Entity, EntityMapper, MapEntities},
hash_map::EntityHashMap, Entity, EntityMapper, MapEntities, VisitEntities,
VisitEntitiesMut,
},
hierarchy::ChildOf, hierarchy::ChildOf,
reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource},
resource::Resource, resource::Resource,
world::World, world::World,
}; };
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use crate::dynamic_scene::DynamicScene; use crate::dynamic_scene::DynamicScene;
use crate::dynamic_scene_builder::DynamicSceneBuilder; use crate::dynamic_scene_builder::DynamicSceneBuilder;
#[derive(Resource, Reflect, Debug, VisitEntities, VisitEntitiesMut)] #[derive(Resource, Reflect, MapEntities, Debug)]
#[reflect(Resource, MapEntities)] #[reflect(Resource, MapEntities)]
struct TestResource { struct TestResource {
#[entities]
entity_a: Entity, entity_a: Entity,
#[entities]
entity_b: Entity, entity_b: Entity,
} }
@ -360,7 +360,7 @@ mod tests {
struct B(pub Entity); struct B(pub Entity);
impl MapEntities for B { impl MapEntities for B {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) { fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
self.0 = entity_mapper.get_mapped(self.0); self.0 = entity_mapper.get_mapped(self.0);
} }
} }

View File

@ -2,7 +2,7 @@ use alloc::{borrow::ToOwned, string::String};
use core::num::NonZero; use core::num::NonZero;
use bevy_ecs::{ use bevy_ecs::{
entity::{Entity, EntityBorrow, VisitEntities, VisitEntitiesMut}, entity::{Entity, EntityBorrow},
prelude::Component, prelude::Component,
}; };
use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2}; use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2};
@ -74,24 +74,6 @@ impl WindowRef {
} }
} }
impl VisitEntities for WindowRef {
fn visit_entities<F: FnMut(Entity)>(&self, mut f: F) {
match self {
Self::Entity(entity) => f(*entity),
Self::Primary => {}
}
}
}
impl VisitEntitiesMut for WindowRef {
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
match self {
Self::Entity(entity) => f(entity),
Self::Primary => {}
}
}
}
/// A flattened representation of a window reference for equality/hashing purposes. /// A flattened representation of a window reference for equality/hashing purposes.
/// ///
/// For most purposes you probably want to use the unnormalized version [`WindowRef`]. /// For most purposes you probably want to use the unnormalized version [`WindowRef`].