Skip to content

Commit a033f1b

Browse files
authored
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, } ```
1 parent 55fd105 commit a033f1b

File tree

15 files changed

+202
-567
lines changed

15 files changed

+202
-567
lines changed

crates/bevy_ecs/macros/src/component.rs

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
9292
Err(err) => err.into_compile_error().into(),
9393
};
9494

95-
let visit_entities = visit_entities(
95+
let map_entities = map_entities(
9696
&ast.data,
97-
&bevy_ecs_path,
97+
Ident::new("this", Span::call_site()),
9898
relationship.is_some(),
9999
relationship_target.is_some(),
100-
);
100+
).map(|map_entities_impl| quote! {
101+
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
102+
use #bevy_ecs_path::entity::MapEntities;
103+
#map_entities_impl
104+
}
105+
});
101106

102107
let storage = storage_path(&bevy_ecs_path, attrs.storage);
103108

@@ -295,7 +300,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
295300
#clone_behavior
296301
}
297302

298-
#visit_entities
303+
#map_entities
299304
}
300305

301306
#relationship
@@ -306,19 +311,18 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
306311

307312
const ENTITIES: &str = "entities";
308313

309-
fn visit_entities(
314+
pub(crate) fn map_entities(
310315
data: &Data,
311-
bevy_ecs_path: &Path,
316+
self_ident: Ident,
312317
is_relationship: bool,
313318
is_relationship_target: bool,
314-
) -> TokenStream2 {
319+
) -> Option<TokenStream2> {
315320
match data {
316321
Data::Struct(DataStruct { fields, .. }) => {
317-
let mut visit = Vec::with_capacity(fields.len());
318-
let mut visit_mut = Vec::with_capacity(fields.len());
322+
let mut map = Vec::with_capacity(fields.len());
319323

320324
let relationship = if is_relationship || is_relationship_target {
321-
relationship_field(fields, "VisitEntities", fields.span()).ok()
325+
relationship_field(fields, "MapEntities", fields.span()).ok()
322326
} else {
323327
None
324328
};
@@ -335,27 +339,17 @@ fn visit_entities(
335339
.clone()
336340
.map_or(Member::from(index), Member::Named);
337341

338-
visit.push(quote!(this.#field_member.visit_entities(&mut func);));
339-
visit_mut.push(quote!(this.#field_member.visit_entities_mut(&mut func);));
342+
map.push(quote!(#self_ident.#field_member.map_entities(mapper);));
340343
});
341-
if visit.is_empty() {
342-
return quote!();
344+
if map.is_empty() {
345+
return None;
343346
};
344-
quote!(
345-
fn visit_entities(this: &Self, mut func: impl FnMut(#bevy_ecs_path::entity::Entity)) {
346-
use #bevy_ecs_path::entity::VisitEntities;
347-
#(#visit)*
348-
}
349-
350-
fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut #bevy_ecs_path::entity::Entity)) {
351-
use #bevy_ecs_path::entity::VisitEntitiesMut;
352-
#(#visit_mut)*
353-
}
354-
)
347+
Some(quote!(
348+
#(#map)*
349+
))
355350
}
356351
Data::Enum(DataEnum { variants, .. }) => {
357-
let mut visit = Vec::with_capacity(variants.len());
358-
let mut visit_mut = Vec::with_capacity(variants.len());
352+
let mut map = Vec::with_capacity(variants.len());
359353

360354
for variant in variants.iter() {
361355
let field_members = variant
@@ -377,40 +371,25 @@ fn visit_entities(
377371
.map(|member| format_ident!("__self_{}", member))
378372
.collect::<Vec<_>>();
379373

380-
visit.push(
374+
map.push(
381375
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
382-
#(#field_idents.visit_entities(&mut func);)*
383-
}),
384-
);
385-
visit_mut.push(
386-
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
387-
#(#field_idents.visit_entities_mut(&mut func);)*
376+
#(#field_idents.map_entities(mapper);)*
388377
}),
389378
);
390379
}
391380

392-
if visit.is_empty() {
393-
return quote!();
381+
if map.is_empty() {
382+
return None;
394383
};
395-
quote!(
396-
fn visit_entities(this: &Self, mut func: impl FnMut(#bevy_ecs_path::entity::Entity)) {
397-
use #bevy_ecs_path::entity::VisitEntities;
398-
match this {
399-
#(#visit,)*
400-
_ => {}
401-
}
402-
}
403384

404-
fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut #bevy_ecs_path::entity::Entity)) {
405-
use #bevy_ecs_path::entity::VisitEntitiesMut;
406-
match this {
407-
#(#visit_mut,)*
408-
_ => {}
409-
}
385+
Some(quote!(
386+
match #self_ident {
387+
#(#map,)*
388+
_ => {}
410389
}
411-
)
390+
))
412391
}
413-
Data::Union(_) => quote!(),
392+
Data::Union(_) => None,
414393
}
415394
}
416395

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 17 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ mod query_filter;
99
mod states;
1010
mod world_query;
1111

12-
use crate::{query_data::derive_query_data_impl, query_filter::derive_query_filter_impl};
12+
use crate::{
13+
component::map_entities, query_data::derive_query_data_impl,
14+
query_filter::derive_query_filter_impl,
15+
};
1316
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
1417
use proc_macro::TokenStream;
15-
use proc_macro2::{Span, TokenStream as TokenStream2};
18+
use proc_macro2::{Ident, Span};
1619
use quote::{format_ident, quote};
1720
use syn::{
1821
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
@@ -185,105 +188,22 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
185188
})
186189
}
187190

188-
fn derive_visit_entities_base(
189-
input: TokenStream,
190-
trait_name: TokenStream2,
191-
gen_methods: impl FnOnce(Vec<TokenStream2>) -> TokenStream2,
192-
) -> TokenStream {
191+
#[proc_macro_derive(MapEntities, attributes(entities))]
192+
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
193193
let ast = parse_macro_input!(input as DeriveInput);
194194
let ecs_path = bevy_ecs_path();
195-
196-
let named_fields = match get_struct_fields(&ast.data) {
197-
Ok(fields) => fields,
198-
Err(e) => return e.into_compile_error().into(),
199-
};
200-
201-
let field = named_fields
202-
.iter()
203-
.filter_map(|field| {
204-
if let Some(attr) = field
205-
.attrs
206-
.iter()
207-
.find(|a| a.path().is_ident("visit_entities"))
208-
{
209-
let ignore = attr.parse_nested_meta(|meta| {
210-
if meta.path.is_ident("ignore") {
211-
Ok(())
212-
} else {
213-
Err(meta.error("Invalid visit_entities attribute. Use `ignore`"))
214-
}
215-
});
216-
return match ignore {
217-
Ok(()) => None,
218-
Err(e) => Some(Err(e)),
219-
};
220-
}
221-
Some(Ok(field))
222-
})
223-
.map(|res| res.map(|field| field.ident.as_ref()))
224-
.collect::<Result<Vec<_>, _>>();
225-
226-
let field = match field {
227-
Ok(field) => field,
228-
Err(e) => return e.into_compile_error().into(),
229-
};
230-
231-
if field.is_empty() {
232-
return syn::Error::new(
233-
ast.span(),
234-
format!("Invalid `{}` type: at least one field", trait_name),
235-
)
236-
.into_compile_error()
237-
.into();
238-
}
239-
240-
let field_access = field
241-
.iter()
242-
.enumerate()
243-
.map(|(n, f)| {
244-
if let Some(ident) = f {
245-
quote! {
246-
self.#ident
247-
}
248-
} else {
249-
let idx = Index::from(n);
250-
quote! {
251-
self.#idx
252-
}
253-
}
254-
})
255-
.collect::<Vec<_>>();
256-
257-
let methods = gen_methods(field_access);
258-
259-
let generics = ast.generics;
260-
let (impl_generics, ty_generics, _) = generics.split_for_impl();
195+
let map_entities_impl = map_entities(
196+
&ast.data,
197+
Ident::new("self", Span::call_site()),
198+
false,
199+
false,
200+
);
261201
let struct_name = &ast.ident;
262-
202+
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
263203
TokenStream::from(quote! {
264-
impl #impl_generics #ecs_path::entity:: #trait_name for #struct_name #ty_generics {
265-
#methods
266-
}
267-
})
268-
}
269-
270-
#[proc_macro_derive(VisitEntitiesMut, attributes(visit_entities))]
271-
pub fn derive_visit_entities_mut(input: TokenStream) -> TokenStream {
272-
derive_visit_entities_base(input, quote! { VisitEntitiesMut }, |field| {
273-
quote! {
274-
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
275-
#(#field.visit_entities_mut(&mut f);)*
276-
}
277-
}
278-
})
279-
}
280-
281-
#[proc_macro_derive(VisitEntities, attributes(visit_entities))]
282-
pub fn derive_visit_entities(input: TokenStream) -> TokenStream {
283-
derive_visit_entities_base(input, quote! { VisitEntities }, |field| {
284-
quote! {
285-
fn visit_entities<F: FnMut(Entity)>(&self, mut f: F) {
286-
#(#field.visit_entities(&mut f);)*
204+
impl #impl_generics #ecs_path::entity::MapEntities for #struct_name #type_generics #where_clause {
205+
fn map_entities<M: #ecs_path::entity::EntityMapper>(&mut self, mapper: &mut M) {
206+
#map_entities_impl
287207
}
288208
}
289209
})

0 commit comments

Comments
 (0)