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

View File

@ -9,10 +9,13 @@ mod query_filter;
mod states;
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 proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro2::{Ident, Span};
use quote::{format_ident, quote};
use syn::{
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(
input: TokenStream,
trait_name: TokenStream2,
gen_methods: impl FnOnce(Vec<TokenStream2>) -> TokenStream2,
) -> TokenStream {
#[proc_macro_derive(MapEntities, attributes(entities))]
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path();
let named_fields = match get_struct_fields(&ast.data) {
Ok(fields) => fields,
Err(e) => return e.into_compile_error().into(),
};
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 map_entities_impl = map_entities(
&ast.data,
Ident::new("self", Span::call_site()),
false,
false,
);
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #ecs_path::entity:: #trait_name for #struct_name #ty_generics {
#methods
}
})
}
#[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);)*
impl #impl_generics #ecs_path::entity::MapEntities for #struct_name #type_generics #where_clause {
fn map_entities<M: #ecs_path::entity::EntityMapper>(&mut self, mapper: &mut M) {
#map_entities_impl
}
}
})

View File

@ -4,12 +4,12 @@ use crate::{
archetype::ArchetypeFlags,
bundle::BundleInfo,
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
entity::{ComponentCloneCtx, Entity, SourceComponent},
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
resource::Resource,
storage::{SparseSetIndex, SparseSets, Table, TableRow},
system::{Commands, Local, SystemParam},
system::{Local, SystemParam},
world::{DeferredWorld, FromWorld, World},
};
use alloc::boxed::Box;
@ -517,14 +517,21 @@ pub trait Component: Send + Sync + 'static {
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]
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)) {}
fn map_entities<E: EntityMapper>(_this: &mut Self, _mapper: &mut E) {}
}
mod private {
@ -1158,7 +1165,7 @@ impl ComponentDescriptor {
}
/// 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`].
#[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.
///
pub fn component_clone_via_clone<C: Clone + Component>(
_commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
@ -2952,11 +2958,7 @@ pub fn component_clone_via_clone<C: Clone + Component>(
///
/// [`PartialReflect::reflect_clone`]: bevy_reflect::PartialReflect::reflect_clone
#[cfg(feature = "bevy_reflect")]
pub fn component_clone_via_reflect(
commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
pub fn component_clone_via_reflect(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
let Some(app_registry) = ctx.type_registry().cloned() else {
return;
};
@ -2973,9 +2975,7 @@ pub fn component_clone_via_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);
});
reflect_component.map_entities(&mut *component, ctx.entity_mapper());
}
drop(registry);
@ -2993,9 +2993,7 @@ pub fn component_clone_via_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);
});
reflect_component.map_entities(&mut *component, ctx.entity_mapper());
}
drop(registry);
@ -3018,23 +3016,12 @@ pub fn component_clone_via_reflect(
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.to_dynamic();
let component_layout = component_info.layout();
let target = ctx.target();
let component_id = ctx.component_id();
for entity in mapped_entities.iter_mut() {
*entity = ctx.entity_mapper().get_mapped(*entity);
}
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);
assert_eq!(type_id, (*component).type_id());
component.apply(source_component_cloned.as_partial_reflect());
@ -3042,11 +3029,7 @@ pub fn component_clone_via_reflect(
.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;
});
reflect_component.map_entities(&mut *component, mapper);
}
// SAFETY:
// - 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.
///
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
pub fn component_clone_ignore(
_commands: &mut Commands,
_source: &SourceComponent,
_ctx: &mut ComponentCloneCtx,
) {
}
pub fn component_clone_ignore(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {}
/// Wrapper for components clone specialization using autoderef.
#[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_ptr::{Ptr, PtrMut};
use bumpalo::Bump;
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::{
bundle::Bundle,
component::{Component, ComponentId, ComponentInfo},
entity::Entity,
component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo},
entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper},
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
world::World,
};
@ -176,9 +169,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// - Component being written is not registered in the world.
/// - `ComponentId` of component being written does not match expected `ComponentId`.
pub fn write_target_component<C: Component>(&mut self, mut component: C) {
C::visit_entities_mut(&mut component, |entity| {
*entity = self.mapper.get_mapped(*entity);
});
C::map_entities(&mut component, &mut self.mapper);
let short_name = disqualified::ShortName::of::<C>();
if self.target_component_written {
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.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
@ -341,7 +343,6 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// 2. component-defined handler using [`Component::clone_behavior`]
/// 3. default handler override using [`EntityClonerBuilder::with_default_clone_fn`].
/// 4. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not.
#[derive(Debug)]
pub struct EntityCloner {
filter_allows_components: bool,
filter: HashSet<ComponentId>,
@ -350,18 +351,20 @@ pub struct EntityCloner {
linked_cloning: bool,
default_clone_fn: ComponentCloneFn,
clone_queue: VecDeque<Entity>,
deferred_commands: VecDeque<Box<dyn FnOnce(&mut World, &mut dyn EntityMapper)>>,
}
impl Default for EntityCloner {
fn default() -> Self {
Self {
filter_allows_components: false,
filter: Default::default(),
clone_behavior_overrides: Default::default(),
move_components: false,
linked_cloning: false,
default_clone_fn: ComponentCloneBehavior::global_default_fn(),
filter: Default::default(),
clone_behavior_overrides: Default::default(),
clone_queue: Default::default(),
deferred_commands: Default::default(),
}
}
}
@ -476,10 +479,6 @@ impl EntityCloner {
let archetype = source_entity.archetype();
bundle_scratch = BundleScratch::with_capacity(archetype.component_count());
// SAFETY: no other references to command queue exist
let mut commands = unsafe {
Commands::new_raw_from_entities(world.get_raw_command_queue(), world.entities())
};
for component in archetype.components() {
if !self.is_cloning_allowed(&component) {
@ -527,12 +526,16 @@ impl EntityCloner {
)
};
(handler)(&mut commands, &source_component, &mut ctx);
(handler)(&source_component, &mut ctx);
}
}
world.flush();
for deferred in self.deferred_commands.drain(..) {
(deferred)(world, mapper);
}
if !world.entities.contains(target) {
panic!("Target entity does not exist");
}
@ -609,7 +612,6 @@ impl EntityCloner {
}
/// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information.
#[derive(Debug)]
pub struct EntityClonerBuilder<'w> {
world: &'w mut World,
entity_cloner: EntityCloner,
@ -845,7 +847,6 @@ mod tests {
entity::{hash_map::EntityHashMap, Entity, EntityCloner, SourceComponent},
prelude::{ChildOf, Children, Resource},
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
system::Commands,
world::{FromWorld, World},
};
use alloc::vec::Vec;
@ -861,7 +862,6 @@ mod tests {
component::{Component, ComponentCloneBehavior},
entity::{EntityCloner, SourceComponent},
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
system::Commands,
};
use alloc::vec;
use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr};
@ -991,11 +991,7 @@ mod tests {
#[derive(Component, Reflect)]
struct B;
fn test_handler(
_commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
fn test_handler(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
let registry = ctx.type_registry().unwrap();
assert!(source.read_reflect(&registry.read()).is_none());
}
@ -1287,11 +1283,7 @@ mod tests {
#[test]
fn clone_entity_with_dynamic_components() {
const COMPONENT_SIZE: usize = 10;
fn test_handler(
_commands: &mut Commands,
source: &SourceComponent,
ctx: &mut ComponentCloneCtx,
) {
fn test_handler(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
// SAFETY: the passed in ptr corresponds to copy-able data that matches the type of the source / target component
unsafe {
ctx.write_target_component_ptr(source.ptr());

View File

@ -1,10 +1,15 @@
pub use bevy_ecs_macros::MapEntities;
use crate::{
entity::Entity,
entity::{hash_map::EntityHashMap, Entity},
identifier::masks::{IdentifierMask, HIGH_MASK},
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.
///
@ -15,13 +20,9 @@ use super::{hash_map::EntityHashMap, VisitEntitiesMut};
/// (usually by using an [`EntityHashMap<Entity>`] between source entities and entities in the
/// current world).
///
/// This trait is similar to [`VisitEntitiesMut`]. They differ in that [`VisitEntitiesMut`] operates
/// on `&mut Entity` and allows for in-place modification, while this trait makes no assumption that
/// such in-place modification is occurring, which is impossible for types such as [`HashSet<Entity>`]
/// 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.
/// Components use [`Component::map_entities`](crate::component::Component::map_entities) to map
/// entities in the context of scenes and entity cloning, which generally uses [`MapEntities`] internally
/// to map each field (see those docs for usage).
///
/// [`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
/// 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 {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.visit_entities_mut(|entity| {
*entity = entity_mapper.get_mapped(*entity);
});
impl MapEntities for Entity {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = entity_mapper.get_mapped(*self);
}
}
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`].
///
/// 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).
///
/// 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.
/// This is used by [`MapEntities`] implementors.
///
/// ## Example
///

View File

@ -39,7 +39,6 @@
mod clone_entities;
mod entity_set;
mod map_entities;
mod visit_entities;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
@ -48,7 +47,6 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
pub use clone_entities::*;
pub use entity_set::*;
pub use map_entities::*;
pub use visit_entities::*;
mod 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,
change_detection::Ref,
component::{Component, ComponentId, RequiredComponents, RequiredComponentsError},
entity::Entity,
entity::{Entity, EntityMapper},
entity_disabling::DefaultQueryFilters,
prelude::Or,
query::{Added, Changed, FilteredAccess, QueryFilter, With, Without},
@ -146,7 +146,6 @@ mod tests {
vec,
vec::Vec,
};
use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut};
use bevy_platform_support::collections::HashSet;
use bevy_tasks::{ComputeTaskPool, TaskPool};
use core::{
@ -2705,8 +2704,19 @@ mod tests {
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]
fn visit_struct_entities() {
fn map_struct_entities() {
#[derive(Component)]
#[expect(
unused,
@ -2733,30 +2743,22 @@ mod tests {
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 mapper = CaptureMapper::default();
Component::map_entities(&mut foo, &mut mapper);
assert_eq!(&mapper.0, &[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]);
let mut mapper = CaptureMapper::default();
Component::map_entities(&mut bar, &mut mapper);
assert_eq!(&mapper.0, &[e1, e2, e3]);
}
#[test]
fn visit_enum_entities() {
fn map_enum_entities() {
#[derive(Component)]
#[expect(
unused,
@ -2779,26 +2781,18 @@ mod tests {
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 mapper = CaptureMapper::default();
Component::map_entities(&mut foo, &mut mapper);
assert_eq!(&mapper.0, &[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]);
let mut mapper = CaptureMapper::default();
Component::map_entities(&mut foo, &mut mapper);
assert_eq!(&mapper.0, &[e1, e2, e3]);
}
#[expect(
@ -2827,16 +2821,18 @@ mod tests {
field1: ComponentB,
}
#[derive(Component, VisitEntities, VisitEntitiesMut)]
#[derive(Component)]
struct MyEntities {
#[entities]
entities: Vec<Entity>,
#[entities]
another_one: Entity,
#[entities]
maybe_entity: Option<Entity>,
#[expect(
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."
)]
#[visit_entities(ignore)]
something_else: String,
}
@ -2844,6 +2840,6 @@ mod tests {
dead_code,
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)]
struct MyEntitiesTuple(Vec<Entity>, Entity, #[visit_entities(ignore)] usize);
#[derive(Component)]
struct MyEntitiesTuple(#[entities] Vec<Entity>, #[entities] Entity, usize);
}

View File

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

View File

@ -121,10 +121,8 @@ 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::map_entities()`].
pub map_entities: fn(&mut dyn Reflect, &mut dyn EntityMapper),
/// Function pointer implementing [`ReflectComponent::reflect_unchecked_mut()`].
///
/// # Safety
@ -291,18 +289,9 @@ impl ReflectComponent {
&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);
/// Calls a dynamic version of [`Component::map_entities`].
pub fn map_entities(&self, component: &mut dyn Reflect, func: &mut dyn EntityMapper) {
(self.0.map_entities)(component, func);
}
}
@ -330,19 +319,18 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
apply_or_insert_mapped: |entity,
reflected_component,
registry,
mapper,
mut mapper,
relationship_hook_mode| {
let map_fn = map_function(mapper);
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);
C::map_entities(&mut component, &mut mapper);
} else {
let mut component = entity.world_scope(|world| {
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);
}
@ -350,7 +338,7 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
let mut component = entity.world_scope(|world| {
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);
}
},
@ -395,20 +383,10 @@ 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)| {
map_entities: |reflect: &mut dyn Reflect, mut mapper: &mut dyn EntityMapper| {
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 map_entities;
mod resource;
mod visit_entities;
pub use bundle::{ReflectBundle, ReflectBundleFns};
pub use component::{ReflectComponent, ReflectComponentFns};
@ -25,7 +24,6 @@ pub use entity_commands::ReflectCommandExt;
pub use from_world::{ReflectFromWorld, ReflectFromWorldFns};
pub use map_entities::ReflectMapEntities;
pub use resource::{ReflectResource, ReflectResourceFns};
pub use visit_entities::{ReflectVisitEntities, ReflectVisitEntitiesMut};
/// A [`Resource`] storing [`TypeRegistry`] for
/// 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},
entity::{ComponentCloneCtx, Entity, SourceComponent},
error::{ignore, CommandWithEntity, HandleError},
system::{
entity_command::{self},
Commands,
},
system::entity_command::{self},
world::{DeferredWorld, EntityWorldMut},
};
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
/// to spawn recursively.
pub fn clone_relationship_target<T: RelationshipTarget>(
_commands: &mut Commands,
source: &SourceComponent,
context: &mut ComponentCloneCtx,
) {

View File

@ -214,24 +214,24 @@ where
mod tests {
use bevy_ecs::{
component::Component,
entity::{
hash_map::EntityHashMap, Entity, EntityMapper, MapEntities, VisitEntities,
VisitEntitiesMut,
},
entity::{hash_map::EntityHashMap, Entity, EntityMapper, MapEntities},
hierarchy::ChildOf,
reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource},
resource::Resource,
world::World,
};
use bevy_reflect::Reflect;
use crate::dynamic_scene::DynamicScene;
use crate::dynamic_scene_builder::DynamicSceneBuilder;
#[derive(Resource, Reflect, Debug, VisitEntities, VisitEntitiesMut)]
#[derive(Resource, Reflect, MapEntities, Debug)]
#[reflect(Resource, MapEntities)]
struct TestResource {
#[entities]
entity_a: Entity,
#[entities]
entity_b: Entity,
}
@ -360,7 +360,7 @@ mod tests {
struct B(pub Entity);
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);
}
}

View File

@ -2,7 +2,7 @@ use alloc::{borrow::ToOwned, string::String};
use core::num::NonZero;
use bevy_ecs::{
entity::{Entity, EntityBorrow, VisitEntities, VisitEntitiesMut},
entity::{Entity, EntityBorrow},
prelude::Component,
};
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.
///
/// For most purposes you probably want to use the unnormalized version [`WindowRef`].