Skip to content

Commit f041668

Browse files
committed
bring back VisitEntitiesMut
1 parent d8dde38 commit f041668

File tree

12 files changed

+248
-71
lines changed

12 files changed

+248
-71
lines changed

crates/bevy_animation/src/lib.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ use bevy_app::{App, Plugin, PostUpdate};
2828
use bevy_asset::{Asset, AssetApp, Assets, Handle};
2929
use bevy_core::Name;
3030
use bevy_ecs::{
31-
entity::{MapEntities, VisitEntities},
31+
entity::{VisitEntities, VisitEntitiesMut},
3232
prelude::*,
33-
reflect::{ReflectMapEntities, ReflectVisitEntities},
33+
reflect::{ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut},
3434
world::EntityMutExcept,
3535
};
3636
use bevy_math::FloatExt;
@@ -530,8 +530,8 @@ impl Hash for AnimationTargetId {
530530
/// Note that each entity can only be animated by one animation player at a
531531
/// time. However, you can change [`AnimationTarget`]'s `player` property at
532532
/// runtime to change which player is responsible for animating the entity.
533-
#[derive(Clone, Copy, Component, Reflect, VisitEntities)]
534-
#[reflect(Component, MapEntities, VisitEntities)]
533+
#[derive(Clone, Copy, Component, Reflect, VisitEntities, VisitEntitiesMut)]
534+
#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)]
535535
pub struct AnimationTarget {
536536
/// The ID of this animation target.
537537
///
@@ -1302,12 +1302,6 @@ impl From<&Name> for AnimationTargetId {
13021302
}
13031303
}
13041304

1305-
impl MapEntities for AnimationTarget {
1306-
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
1307-
self.player = entity_mapper.map_entity(self.player);
1308-
}
1309-
}
1310-
13111305
impl AnimationGraphEvaluator {
13121306
// Starts a new depth-first search.
13131307
fn reset(&mut self, root: AnimationNodeIndex, node_count: usize) {

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,85 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
180180
})
181181
}
182182

183+
#[proc_macro_derive(VisitEntitiesMut, attributes(visit_entities))]
184+
pub fn derive_visit_entities_mut(input: TokenStream) -> TokenStream {
185+
let ast = parse_macro_input!(input as DeriveInput);
186+
let ecs_path = bevy_ecs_path();
187+
188+
let named_fields = match get_struct_fields(&ast.data) {
189+
Ok(fields) => fields,
190+
Err(e) => return e.into_compile_error().into(),
191+
};
192+
193+
let field = named_fields
194+
.iter()
195+
.filter_map(|field| {
196+
if let Some(attr) = field
197+
.attrs
198+
.iter()
199+
.find(|a| a.path().is_ident("visit_entities"))
200+
{
201+
let ignore = attr.parse_nested_meta(|meta| {
202+
if meta.path.is_ident("ignore") {
203+
Ok(())
204+
} else {
205+
Err(meta.error("Invalid visit_entities attribute. Use `ignore`"))
206+
}
207+
});
208+
return match ignore {
209+
Ok(()) => None,
210+
Err(e) => Some(Err(e)),
211+
};
212+
}
213+
Some(Ok(field))
214+
})
215+
.map(|res| res.map(|field| field.ident.as_ref()))
216+
.collect::<Result<Vec<_>, _>>();
217+
218+
let field = match field {
219+
Ok(field) => field,
220+
Err(e) => return e.into_compile_error().into(),
221+
};
222+
223+
if field.is_empty() {
224+
return syn::Error::new(
225+
ast.span(),
226+
"Invalid `VisitEntitiesMut` type: at least one field",
227+
)
228+
.into_compile_error()
229+
.into();
230+
}
231+
232+
let field_access = field
233+
.iter()
234+
.enumerate()
235+
.map(|(n, f)| {
236+
if let Some(ident) = f {
237+
quote! {
238+
self.#ident
239+
}
240+
} else {
241+
let idx = Index::from(n);
242+
quote! {
243+
self.#idx
244+
}
245+
}
246+
})
247+
.collect::<Vec<_>>();
248+
249+
let generics = ast.generics;
250+
let (impl_generics, ty_generics, _) = generics.split_for_impl();
251+
let struct_name = &ast.ident;
252+
253+
TokenStream::from(quote! {
254+
impl #impl_generics #ecs_path::entity::VisitEntitiesMut for #struct_name #ty_generics {
255+
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
256+
#(#field_access.visit_entities_mut(&mut f);)*
257+
}
258+
}
259+
})
260+
}
261+
183262
#[proc_macro_derive(VisitEntities, attributes(visit_entities))]
184263
pub fn derive_visit_entities(input: TokenStream) -> TokenStream {
185264
let ast = parse_macro_input!(input as DeriveInput);

crates/bevy_ecs/src/entity/map_entities.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
world::World,
55
};
66

7-
use super::EntityHashMap;
7+
use super::{EntityHashMap, VisitEntitiesMut};
88

99
/// Operation to map all contained [`Entity`] fields in a type to new values.
1010
///
@@ -45,6 +45,14 @@ pub trait MapEntities {
4545
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M);
4646
}
4747

48+
impl<T: VisitEntitiesMut> MapEntities for T {
49+
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
50+
self.visit_entities_mut(|entity| {
51+
*entity = entity_mapper.map_entity(*entity);
52+
});
53+
}
54+
}
55+
4856
/// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`].
4957
///
5058
/// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities

crates/bevy_ecs/src/entity/visit_entities.rs

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pub use bevy_ecs_macros::VisitEntities;
1+
pub use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut};
22

33
use crate::entity::Entity;
44

@@ -28,14 +28,44 @@ impl VisitEntities for Entity {
2828
}
2929
}
3030

31+
/// Apply an operation to mutable references to all entities in a container.
32+
///
33+
/// This is implemented by default for types that implement [`IntoIterator`].
34+
///
35+
/// It may be useful to implement directly for types that can't produce an
36+
/// iterator for lifetime reasons, such as those involving internal mutexes.
37+
pub trait VisitEntitiesMut: VisitEntities {
38+
/// Apply an operation to mutable references to all contained entities.
39+
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, f: F);
40+
}
41+
42+
impl<T: VisitEntities> VisitEntitiesMut for T
43+
where
44+
for<'a> &'a mut T: IntoIterator<Item = &'a mut Entity>,
45+
{
46+
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, f: F) {
47+
self.into_iter().for_each(f);
48+
}
49+
}
50+
51+
impl VisitEntitiesMut for Entity {
52+
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
53+
f(self);
54+
}
55+
}
56+
3157
#[cfg(test)]
3258
mod tests {
33-
use crate::{self as bevy_ecs, entity::Entities};
59+
use crate::{
60+
self as bevy_ecs,
61+
entity::{Entities, EntityHashMap, MapEntities, SceneEntityMapper},
62+
world::World,
63+
};
3464
use bevy_utils::HashSet;
3565

3666
use super::*;
3767

38-
#[derive(VisitEntities)]
68+
#[derive(VisitEntities, Debug, PartialEq)]
3969
struct Foo {
4070
ordered: Vec<Entity>,
4171
unordered: HashSet<Entity>,
@@ -45,10 +75,29 @@ mod tests {
4575
not_an_entity: String,
4676
}
4777

78+
// Need a manual impl since VisitEntitiesMut isn't implemented for `HashSet`.
79+
// We don't expect users to actually do this - it's only for test purposes
80+
// to prove out the automatic `MapEntites` impl we get with `VisitEntitiesMut`.
81+
impl VisitEntitiesMut for Foo {
82+
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
83+
self.ordered.visit_entities_mut(&mut f);
84+
self.unordered = self
85+
.unordered
86+
.drain()
87+
.map(|mut entity| {
88+
f(&mut entity);
89+
entity
90+
})
91+
.collect();
92+
f(&mut self.single)
93+
}
94+
}
95+
4896
#[test]
4997
fn visit_entities() {
50-
let entities = Entities::new();
51-
let foo = Foo {
98+
let mut world = World::new();
99+
let entities = world.entities();
100+
let mut foo = Foo {
52101
ordered: vec![entities.reserve_entity(), entities.reserve_entity()],
53102
unordered: [
54103
entities.reserve_entity(),
@@ -61,21 +110,41 @@ mod tests {
61110
not_an_entity: "Bar".into(),
62111
};
63112

113+
let mut entity_map = EntityHashMap::<Entity>::default();
114+
let mut remapped = Foo {
115+
ordered: vec![],
116+
unordered: HashSet::new(),
117+
single: Entity::PLACEHOLDER,
118+
not_an_entity: foo.not_an_entity.clone(),
119+
};
120+
64121
// Note: this assumes that the VisitEntities derive is field-ordered,
65122
// which isn't explicitly stated/guaranteed.
66123
// If that changes, this test will fail, but that might be OK if
67124
// we're intentionally breaking that assumption.
68125
let mut i = 0;
69126
foo.visit_entities(|entity| {
127+
let new_entity = entities.reserve_entity();
70128
if i < foo.ordered.len() {
71129
assert_eq!(entity, foo.ordered[i]);
130+
remapped.ordered.push(new_entity);
72131
} else if i < foo.ordered.len() + foo.unordered.len() {
73132
assert!(foo.unordered.contains(&entity));
133+
remapped.unordered.insert(new_entity);
74134
} else {
75135
assert_eq!(entity, foo.single);
136+
remapped.single = new_entity;
76137
}
77138

139+
entity_map.insert(entity, new_entity);
140+
78141
i += 1;
79142
});
143+
144+
SceneEntityMapper::world_scope(&mut entity_map, &mut world, |_, mapper| {
145+
foo.map_entities(mapper);
146+
});
147+
148+
assert_eq!(foo, remapped);
80149
}
81150
}

crates/bevy_ecs/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ mod tests {
9393
world::{EntityMut, EntityRef, Mut, World},
9494
};
9595
use alloc::sync::Arc;
96-
use bevy_ecs_macros::VisitEntities;
96+
use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut};
9797
use bevy_tasks::{ComputeTaskPool, TaskPool};
9898
use bevy_utils::HashSet;
9999
use core::{
@@ -2054,7 +2054,7 @@ mod tests {
20542054
}
20552055

20562056
#[allow(dead_code)]
2057-
#[derive(Component, VisitEntities)]
2057+
#[derive(Component, VisitEntities, VisitEntitiesMut)]
20582058
struct MyEntities {
20592059
entities: Vec<Entity>,
20602060
another_one: Entity,
@@ -2064,6 +2064,6 @@ mod tests {
20642064
}
20652065

20662066
#[allow(dead_code)]
2067-
#[derive(Component, VisitEntities)]
2067+
#[derive(Component, VisitEntities, VisitEntitiesMut)]
20682068
struct MyEntitiesTuple(Vec<Entity>, Entity, #[visit_entities(ignore)] usize);
20692069
}

crates/bevy_ecs/src/reflect/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use entity_commands::ReflectCommandExt;
2626
pub use from_world::{ReflectFromWorld, ReflectFromWorldFns};
2727
pub use map_entities::{ReflectMapEntities, ReflectMapEntitiesResource};
2828
pub use resource::{ReflectResource, ReflectResourceFns};
29-
pub use visit_entities::ReflectVisitEntities;
29+
pub use visit_entities::{ReflectVisitEntities, ReflectVisitEntitiesMut};
3030

3131
/// A [`Resource`] storing [`TypeRegistry`] for
3232
/// type registrations relevant to a whole app.

crates/bevy_ecs/src/reflect/visit_entities.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::entity::{Entity, VisitEntities};
1+
use crate::entity::{Entity, VisitEntities, VisitEntitiesMut};
22
use bevy_reflect::{FromReflect, FromType, PartialReflect};
33

44
/// For a reflected value, apply an operation to all contained entities.
@@ -27,3 +27,36 @@ impl<C: FromReflect + VisitEntities> FromType<C> for ReflectVisitEntities {
2727
}
2828
}
2929
}
30+
31+
/// For a reflected value, apply an operation to mutable references to all
32+
/// contained entities.
33+
///
34+
/// See [`VisitEntitiesMut`] for more details.
35+
#[derive(Clone)]
36+
pub struct ReflectVisitEntitiesMut {
37+
visit_entities_mut: fn(&mut dyn PartialReflect, &mut dyn FnMut(&mut Entity)),
38+
}
39+
40+
impl ReflectVisitEntitiesMut {
41+
/// A general method for applying an operation to all entities in a
42+
/// reflected component.
43+
pub fn visit_entities(
44+
&self,
45+
component: &mut dyn PartialReflect,
46+
f: &mut dyn FnMut(&mut Entity),
47+
) {
48+
(self.visit_entities_mut)(component, f);
49+
}
50+
}
51+
52+
impl<C: FromReflect + VisitEntitiesMut> FromType<C> for ReflectVisitEntitiesMut {
53+
fn from_type() -> Self {
54+
ReflectVisitEntitiesMut {
55+
visit_entities_mut: |component, f| {
56+
let mut concrete = C::from_reflect(component).unwrap();
57+
concrete.visit_entities_mut(f);
58+
component.apply(&concrete);
59+
},
60+
}
61+
}
62+
}

crates/bevy_hierarchy/src/components/children.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#[cfg(feature = "reflect")]
22
use bevy_ecs::reflect::{
33
ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities,
4+
ReflectVisitEntitiesMut,
45
};
56
use bevy_ecs::{
67
component::Component,
7-
entity::{Entity, EntityMapper, MapEntities},
8+
entity::{Entity, VisitEntitiesMut},
89
prelude::FromWorld,
910
world::World,
1011
};
@@ -24,22 +25,21 @@ use smallvec::SmallVec;
2425
/// [`Query`]: bevy_ecs::system::Query
2526
/// [`Parent`]: crate::components::parent::Parent
2627
/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children
27-
#[derive(Component, Debug)]
28+
#[derive(Component, Debug, VisitEntitiesMut)]
2829
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
2930
#[cfg_attr(
3031
feature = "reflect",
31-
reflect(Component, MapEntities, VisitEntities, Debug, FromWorld)
32+
reflect(
33+
Component,
34+
MapEntities,
35+
VisitEntities,
36+
VisitEntitiesMut,
37+
Debug,
38+
FromWorld
39+
)
3240
)]
3341
pub struct Children(pub(crate) SmallVec<[Entity; 8]>);
3442

35-
impl MapEntities for Children {
36-
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
37-
for entity in &mut self.0 {
38-
*entity = entity_mapper.map_entity(*entity);
39-
}
40-
}
41-
}
42-
4343
// TODO: We need to impl either FromWorld or Default so Children can be registered as Reflect.
4444
// This is because Reflect deserialize by creating an instance and apply a patch on top.
4545
// However Children should only ever be set with a real user-defined entities. Its worth looking

0 commit comments

Comments
 (0)