|
| 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