Skip to content

Commit abceebe

Browse files
futileMrGVSV
andauthored
feat: Add World::get_reflect() and World::get_reflect_mut() (#14416)
# Objective Sometimes one wants to retrieve a `&dyn Reflect` for an entity's component, which so far required multiple, non-obvious steps and `unsafe`-code. The docs for [`MutUntyped`](https://docs.rs/bevy/latest/bevy/ecs/change_detection/struct.MutUntyped.html#method.map_unchanged) contain an example of the unsafe part. ## Solution This PR adds the two methods: ```rust // immutable variant World::get_reflect(&self, entity: Entity, type_id: TypeId) -> Result<&dyn Reflect, GetComponentReflectError> // mutable variant World::get_reflect_mut(&mut self, entity: Entity, type_id: TypeId) -> Result<Mut<'_, dyn Reflect>, GetComponentReflectError> ``` which take care of the necessary steps, check required invariants etc., and contain the unsafety so the caller doesn't have to deal with it. ## Testing - Did you test these changes? If so, how? - Added tests and a doc test, also (successfully) ran `cargo run -p ci`. - Are there any parts that need more testing? - Could add tests for each individual error variant, but it's not required imo. - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run `cargo test --doc --package bevy_ecs --all-features -- world::World::get_reflect --show-output` for the doctest - Run `cargo test --package bevy_ecs --lib --all-features -- world::tests::reflect_tests --show-output` for the unittests - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - Don't think it's relevant, but tested on 64bit linux (only). --- ## Showcase Copy of the doctest example which gives a good overview of what this enables: ```rust use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use std::any::TypeId; // define a `Component` and derive `Reflect` for it #[derive(Component, Reflect)] struct MyComponent; // create a `World` for this example let mut world = World::new(); // Note: This is usually handled by `App::register_type()`, but this example can not use `App`. world.init_resource::<AppTypeRegistry>(); world.get_resource_mut::<AppTypeRegistry>().unwrap().write().register::<MyComponent>(); // spawn an entity with a `MyComponent` let entity = world.spawn(MyComponent).id(); // retrieve a reflected reference to the entity's `MyComponent` let comp_reflected: &dyn Reflect = world.get_reflect(entity, TypeId::of::<MyComponent>()).unwrap(); // make sure we got the expected type assert!(comp_reflected.is::<MyComponent>()); ``` ## Migration Guide No breaking changes, but users can use the new methods if they did it manually before. --------- Co-authored-by: Gino Valente <[email protected]>
1 parent 8a79185 commit abceebe

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed

crates/bevy_ecs/src/world/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ mod identifier;
99
mod spawn_batch;
1010
pub mod unsafe_world_cell;
1111

12+
#[cfg(feature = "bevy_reflect")]
13+
pub mod reflect;
14+
1215
pub use crate::{
1316
change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD},
1417
world::command_queue::CommandQueue,

crates/bevy_ecs/src/world/reflect.rs

+337
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
//! Provides additional functionality for [`World`] when the `bevy_reflect` feature is enabled.
2+
3+
use core::any::TypeId;
4+
5+
use thiserror::Error;
6+
7+
use bevy_reflect::{Reflect, ReflectFromPtr};
8+
9+
use crate::prelude::*;
10+
use crate::world::ComponentId;
11+
12+
impl World {
13+
/// Retrieves a reference to the given `entity`'s [`Component`] of the given `type_id` using
14+
/// reflection.
15+
///
16+
/// Requires implementing [`Reflect`] for the [`Component`] (e.g., using [`#[derive(Reflect)`](derive@bevy_reflect::Reflect))
17+
/// and `app.register_type::<TheComponent>()` to have been called[^note-reflect-impl].
18+
///
19+
/// If you want to call this with a [`ComponentId`], see [`World::components`] and [`Components::get_id`] to get
20+
/// the corresponding [`TypeId`].
21+
///
22+
/// Also see the crate documentation for [`bevy_reflect`] for more information on
23+
/// [`Reflect`] and bevy's reflection capabilities.
24+
///
25+
/// # Errors
26+
///
27+
/// See [`GetComponentReflectError`] for the possible errors and their descriptions.
28+
///
29+
/// # Example
30+
///
31+
/// ```
32+
/// use bevy_ecs::prelude::*;
33+
/// use bevy_reflect::Reflect;
34+
/// use std::any::TypeId;
35+
///
36+
/// // define a `Component` and derive `Reflect` for it
37+
/// #[derive(Component, Reflect)]
38+
/// struct MyComponent;
39+
///
40+
/// // create a `World` for this example
41+
/// let mut world = World::new();
42+
///
43+
/// // Note: This is usually handled by `App::register_type()`, but this example cannot use `App`.
44+
/// world.init_resource::<AppTypeRegistry>();
45+
/// world.get_resource_mut::<AppTypeRegistry>().unwrap().write().register::<MyComponent>();
46+
///
47+
/// // spawn an entity with a `MyComponent`
48+
/// let entity = world.spawn(MyComponent).id();
49+
///
50+
/// // retrieve a reflected reference to the entity's `MyComponent`
51+
/// let comp_reflected: &dyn Reflect = world.get_reflect(entity, TypeId::of::<MyComponent>()).unwrap();
52+
///
53+
/// // make sure we got the expected type
54+
/// assert!(comp_reflected.is::<MyComponent>());
55+
/// ```
56+
///
57+
/// # Note
58+
/// Requires the `bevy_reflect` feature (included in the default features).
59+
///
60+
/// [`Components::get_id`]: crate::component::Components::get_id
61+
/// [`ReflectFromPtr`]: bevy_reflect::ReflectFromPtr
62+
/// [`TypeData`]: bevy_reflect::TypeData
63+
/// [`Reflect`]: bevy_reflect::Reflect
64+
/// [`App::register_type`]: ../../bevy_app/struct.App.html#method.register_type
65+
/// [^note-reflect-impl]: More specifically: Requires [`TypeData`] for [`ReflectFromPtr`] to be registered for the given `type_id`,
66+
/// which is automatically handled when deriving [`Reflect`] and calling [`App::register_type`].
67+
#[inline]
68+
pub fn get_reflect(
69+
&self,
70+
entity: Entity,
71+
type_id: TypeId,
72+
) -> Result<&dyn Reflect, GetComponentReflectError> {
73+
let Some(component_id) = self.components().get_id(type_id) else {
74+
return Err(GetComponentReflectError::NoCorrespondingComponentId(
75+
type_id,
76+
));
77+
};
78+
79+
let Some(comp_ptr) = self.get_by_id(entity, component_id) else {
80+
let component_name = self
81+
.components()
82+
.get_name(component_id)
83+
.map(ToString::to_string);
84+
85+
return Err(GetComponentReflectError::EntityDoesNotHaveComponent {
86+
entity,
87+
type_id,
88+
component_id,
89+
component_name,
90+
});
91+
};
92+
93+
let Some(type_registry) = self.get_resource::<AppTypeRegistry>().map(|atr| atr.read())
94+
else {
95+
return Err(GetComponentReflectError::MissingAppTypeRegistry);
96+
};
97+
98+
let Some(reflect_from_ptr) = type_registry.get_type_data::<ReflectFromPtr>(type_id) else {
99+
return Err(GetComponentReflectError::MissingReflectFromPtrTypeData(
100+
type_id,
101+
));
102+
};
103+
104+
// SAFETY:
105+
// - `comp_ptr` is guaranteed to point to an object of type `type_id`
106+
// - `reflect_from_ptr` was constructed for type `type_id`
107+
// - Assertion that checks this equality is present
108+
unsafe {
109+
assert_eq!(
110+
reflect_from_ptr.type_id(),
111+
type_id,
112+
"Mismatch between Ptr's type_id and ReflectFromPtr's type_id",
113+
);
114+
115+
Ok(reflect_from_ptr.as_reflect(comp_ptr))
116+
}
117+
}
118+
119+
/// Retrieves a mutable reference to the given `entity`'s [`Component`] of the given `type_id` using
120+
/// reflection.
121+
///
122+
/// Requires implementing [`Reflect`] for the [`Component`] (e.g., using [`#[derive(Reflect)`](derive@bevy_reflect::Reflect))
123+
/// and `app.register_type::<TheComponent>()` to have been called.
124+
///
125+
/// This is the mutable version of [`World::get_reflect`], see its docs for more information
126+
/// and an example.
127+
///
128+
/// Just calling this method does not trigger [change detection](crate::change_detection).
129+
///
130+
/// # Errors
131+
///
132+
/// See [`GetComponentReflectError`] for the possible errors and their descriptions.
133+
///
134+
/// # Example
135+
///
136+
/// See the documentation for [`World::get_reflect`].
137+
///
138+
/// # Note
139+
/// Requires the feature `bevy_reflect` (included in the default features).
140+
///
141+
/// [`Reflect`]: bevy_reflect::Reflect
142+
#[inline]
143+
pub fn get_reflect_mut(
144+
&mut self,
145+
entity: Entity,
146+
type_id: TypeId,
147+
) -> Result<Mut<'_, dyn Reflect>, GetComponentReflectError> {
148+
// little clone() + read() dance so we a) don't keep a borrow of `self` and b) don't drop a
149+
// temporary (from read()) too early.
150+
let Some(app_type_registry) = self.get_resource::<AppTypeRegistry>().cloned() else {
151+
return Err(GetComponentReflectError::MissingAppTypeRegistry);
152+
};
153+
let type_registry = app_type_registry.read();
154+
155+
let Some(reflect_from_ptr) = type_registry.get_type_data::<ReflectFromPtr>(type_id) else {
156+
return Err(GetComponentReflectError::MissingReflectFromPtrTypeData(
157+
type_id,
158+
));
159+
};
160+
161+
let Some(component_id) = self.components().get_id(type_id) else {
162+
return Err(GetComponentReflectError::NoCorrespondingComponentId(
163+
type_id,
164+
));
165+
};
166+
167+
// HACK: Only required for the `None`-case/`else`-branch, but it borrows `self`, which will
168+
// already be mutablyy borrowed by `self.get_mut_by_id()`, and I didn't find a way around it.
169+
let component_name = self
170+
.components()
171+
.get_name(component_id)
172+
.map(ToString::to_string);
173+
174+
let Some(comp_mut_untyped) = self.get_mut_by_id(entity, component_id) else {
175+
return Err(GetComponentReflectError::EntityDoesNotHaveComponent {
176+
entity,
177+
type_id,
178+
component_id,
179+
component_name,
180+
});
181+
};
182+
183+
// SAFETY:
184+
// - `comp_mut_untyped` is guaranteed to point to an object of type `type_id`
185+
// - `reflect_from_ptr` was constructed for type `type_id`
186+
// - Assertion that checks this equality is present
187+
let comp_mut_typed = comp_mut_untyped.map_unchanged(|ptr_mut| unsafe {
188+
assert_eq!(
189+
reflect_from_ptr.type_id(),
190+
type_id,
191+
"Mismatch between PtrMut's type_id and ReflectFromPtr's type_id",
192+
);
193+
194+
reflect_from_ptr.as_reflect_mut(ptr_mut)
195+
});
196+
197+
Ok(comp_mut_typed)
198+
}
199+
}
200+
201+
/// The error type returned by [`World::get_reflect`] and [`World::get_reflect_mut`].
202+
#[derive(Error, Debug)]
203+
pub enum GetComponentReflectError {
204+
/// There is no [`ComponentId`] corresponding to the given [`TypeId`].
205+
///
206+
/// This is usually handled by calling [`App::register_type`] for the type corresponding to
207+
/// the given [`TypeId`].
208+
///
209+
/// See the documentation for [`bevy_reflect`] for more information.
210+
///
211+
/// [`App::register_type`]: ../../../bevy_app/struct.App.html#method.register_type
212+
#[error("No `ComponentId` corresponding to {0:?} found (did you call App::register_type()?)")]
213+
NoCorrespondingComponentId(TypeId),
214+
215+
/// The given [`Entity`] does not have a [`Component`] corresponding to the given [`TypeId`].
216+
#[error("The given `Entity` {entity:?} does not have a `{component_name:?}` component ({component_id:?}, which corresponds to {type_id:?})")]
217+
EntityDoesNotHaveComponent {
218+
/// The given [`Entity`].
219+
entity: Entity,
220+
/// The given [`TypeId`].
221+
type_id: TypeId,
222+
/// The [`ComponentId`] corresponding to the given [`TypeId`].
223+
component_id: ComponentId,
224+
/// The name corresponding to the [`Component`] with the given [`TypeId`], or `None`
225+
/// if not available.
226+
component_name: Option<String>,
227+
},
228+
229+
/// The [`World`] was missing the [`AppTypeRegistry`] resource.
230+
#[error("The `World` was missing the `AppTypeRegistry` resource")]
231+
MissingAppTypeRegistry,
232+
233+
/// The [`World`]'s [`TypeRegistry`] did not contain [`TypeData`] for [`ReflectFromPtr`] for the given [`TypeId`].
234+
///
235+
/// This is usually handled by calling [`App::register_type`] for the type corresponding to
236+
/// the given [`TypeId`].
237+
///
238+
/// See the documentation for [`bevy_reflect`] for more information.
239+
///
240+
/// [`TypeData`]: bevy_reflect::TypeData
241+
/// [`TypeRegistry`]: bevy_reflect::TypeRegistry
242+
/// [`ReflectFromPtr`]: bevy_reflect::ReflectFromPtr
243+
/// [`App::register_type`]: ../../../bevy_app/struct.App.html#method.register_type
244+
#[error("The `World`'s `TypeRegistry` did not contain `TypeData` for `ReflectFromPtr` for the given {0:?} (did you call `App::register_type()`?)")]
245+
MissingReflectFromPtrTypeData(TypeId),
246+
}
247+
248+
#[cfg(test)]
249+
mod tests {
250+
use std::any::TypeId;
251+
252+
use bevy_reflect::Reflect;
253+
254+
use crate::{
255+
// For bevy_ecs_macros
256+
self as bevy_ecs,
257+
prelude::{AppTypeRegistry, Component, DetectChanges, World},
258+
};
259+
260+
#[derive(Component, Reflect)]
261+
struct RFoo(i32);
262+
263+
#[derive(Component)]
264+
struct Bar;
265+
266+
#[test]
267+
fn get_component_as_reflect() {
268+
let mut world = World::new();
269+
world.init_resource::<AppTypeRegistry>();
270+
271+
let app_type_registry = world.get_resource_mut::<AppTypeRegistry>().unwrap();
272+
app_type_registry.write().register::<RFoo>();
273+
274+
{
275+
let entity_with_rfoo = world.spawn(RFoo(42)).id();
276+
let comp_reflect = world
277+
.get_reflect(entity_with_rfoo, TypeId::of::<RFoo>())
278+
.expect("Reflection of RFoo-component failed");
279+
280+
assert!(comp_reflect.is::<RFoo>());
281+
}
282+
283+
{
284+
let entity_without_rfoo = world.spawn_empty().id();
285+
let reflect_opt = world.get_reflect(entity_without_rfoo, TypeId::of::<RFoo>());
286+
287+
assert!(reflect_opt.is_err());
288+
}
289+
290+
{
291+
let entity_with_bar = world.spawn(Bar).id();
292+
let reflect_opt = world.get_reflect(entity_with_bar, TypeId::of::<Bar>());
293+
294+
assert!(reflect_opt.is_err());
295+
}
296+
}
297+
298+
#[test]
299+
fn get_component_as_mut_reflect() {
300+
let mut world = World::new();
301+
world.init_resource::<AppTypeRegistry>();
302+
303+
let app_type_registry = world.get_resource_mut::<AppTypeRegistry>().unwrap();
304+
app_type_registry.write().register::<RFoo>();
305+
306+
{
307+
let entity_with_rfoo = world.spawn(RFoo(42)).id();
308+
let mut comp_reflect = world
309+
.get_reflect_mut(entity_with_rfoo, TypeId::of::<RFoo>())
310+
.expect("Mutable reflection of RFoo-component failed");
311+
312+
let comp_rfoo_reflected = comp_reflect
313+
.downcast_mut::<RFoo>()
314+
.expect("Wrong type reflected (expected RFoo)");
315+
assert_eq!(comp_rfoo_reflected.0, 42);
316+
comp_rfoo_reflected.0 = 1337;
317+
318+
let rfoo_ref = world.entity(entity_with_rfoo).get_ref::<RFoo>().unwrap();
319+
assert!(rfoo_ref.is_changed());
320+
assert_eq!(rfoo_ref.0, 1337);
321+
}
322+
323+
{
324+
let entity_without_rfoo = world.spawn_empty().id();
325+
let reflect_opt = world.get_reflect_mut(entity_without_rfoo, TypeId::of::<RFoo>());
326+
327+
assert!(reflect_opt.is_err());
328+
}
329+
330+
{
331+
let entity_with_bar = world.spawn(Bar).id();
332+
let reflect_opt = world.get_reflect_mut(entity_with_bar, TypeId::of::<Bar>());
333+
334+
assert!(reflect_opt.is_err());
335+
}
336+
}
337+
}

0 commit comments

Comments
 (0)