bevy_asset/
reflect.rs

1use std::any::{Any, TypeId};
2
3use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
4use bevy_reflect::{FromReflect, FromType, Reflect};
5
6use crate::{Asset, Assets, Handle, UntypedAssetId, UntypedHandle};
7
8/// Type data for the [`TypeRegistry`](bevy_reflect::TypeRegistry) used to operate on reflected [`Asset`]s.
9///
10/// This type provides similar methods to [`Assets<T>`] like [`get`](ReflectAsset::get),
11/// [`add`](ReflectAsset::add) and [`remove`](ReflectAsset::remove), but can be used in situations where you don't know which asset type `T` you want
12/// until runtime.
13///
14/// [`ReflectAsset`] can be obtained via [`TypeRegistration::data`](bevy_reflect::TypeRegistration::data) if the asset was registered using [`register_asset_reflect`](crate::AssetApp::register_asset_reflect).
15#[derive(Clone)]
16pub struct ReflectAsset {
17    handle_type_id: TypeId,
18    assets_resource_type_id: TypeId,
19
20    get: fn(&World, UntypedHandle) -> Option<&dyn Reflect>,
21    // SAFETY:
22    // - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets<T>` resource mutably
23    // - may only be used to access **at most one** access at once
24    get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedHandle) -> Option<&mut dyn Reflect>,
25    add: fn(&mut World, &dyn Reflect) -> UntypedHandle,
26    insert: fn(&mut World, UntypedHandle, &dyn Reflect),
27    len: fn(&World) -> usize,
28    ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
29    remove: fn(&mut World, UntypedHandle) -> Option<Box<dyn Reflect>>,
30}
31
32impl ReflectAsset {
33    /// The [`TypeId`] of the [`Handle<T>`] for this asset
34    pub fn handle_type_id(&self) -> TypeId {
35        self.handle_type_id
36    }
37
38    /// The [`TypeId`] of the [`Assets<T>`] resource
39    pub fn assets_resource_type_id(&self) -> TypeId {
40        self.assets_resource_type_id
41    }
42
43    /// Equivalent of [`Assets::get`]
44    pub fn get<'w>(&self, world: &'w World, handle: UntypedHandle) -> Option<&'w dyn Reflect> {
45        (self.get)(world, handle)
46    }
47
48    /// Equivalent of [`Assets::get_mut`]
49    #[allow(unsafe_code)]
50    pub fn get_mut<'w>(
51        &self,
52        world: &'w mut World,
53        handle: UntypedHandle,
54    ) -> Option<&'w mut dyn Reflect> {
55        // SAFETY: unique world access
56        unsafe { (self.get_unchecked_mut)(world.as_unsafe_world_cell(), handle) }
57    }
58
59    /// Equivalent of [`Assets::get_mut`], but works with an [`UnsafeWorldCell`].
60    ///
61    /// Only use this method when you have ensured that you are the *only* one with access to the [`Assets`] resource of the asset type.
62    /// Furthermore, this does *not* allow you to have look up two distinct handles,
63    /// you can only have at most one alive at the same time.
64    /// This means that this is *not allowed*:
65    /// ```no_run
66    /// # use bevy_asset::{ReflectAsset, UntypedHandle};
67    /// # use bevy_ecs::prelude::World;
68    /// # let reflect_asset: ReflectAsset = unimplemented!();
69    /// # let mut world: World = unimplemented!();
70    /// # let handle_1: UntypedHandle = unimplemented!();
71    /// # let handle_2: UntypedHandle = unimplemented!();
72    /// let unsafe_world_cell = world.as_unsafe_world_cell();
73    /// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_1).unwrap() };
74    /// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_2).unwrap() };
75    /// // ^ not allowed, two mutable references through the same asset resource, even though the
76    /// // handles are distinct
77    ///
78    /// println!("a = {a:?}, b = {b:?}");
79    /// ```
80    ///
81    /// # Safety
82    /// This method does not prevent you from having two mutable pointers to the same data,
83    /// violating Rust's aliasing rules. To avoid this:
84    /// * Only call this method if you know that the [`UnsafeWorldCell`] may be used to access the corresponding `Assets<T>`
85    /// * Don't call this method more than once in the same scope.
86    #[allow(unsafe_code)]
87    pub unsafe fn get_unchecked_mut<'w>(
88        &self,
89        world: UnsafeWorldCell<'w>,
90        handle: UntypedHandle,
91    ) -> Option<&'w mut dyn Reflect> {
92        // SAFETY: requirements are deferred to the caller
93        unsafe { (self.get_unchecked_mut)(world, handle) }
94    }
95
96    /// Equivalent of [`Assets::add`]
97    pub fn add(&self, world: &mut World, value: &dyn Reflect) -> UntypedHandle {
98        (self.add)(world, value)
99    }
100    /// Equivalent of [`Assets::insert`]
101    pub fn insert(&self, world: &mut World, handle: UntypedHandle, value: &dyn Reflect) {
102        (self.insert)(world, handle, value);
103    }
104
105    /// Equivalent of [`Assets::remove`]
106    pub fn remove(&self, world: &mut World, handle: UntypedHandle) -> Option<Box<dyn Reflect>> {
107        (self.remove)(world, handle)
108    }
109
110    /// Equivalent of [`Assets::len`]
111    #[allow(clippy::len_without_is_empty)] // clippy expects the `is_empty` method to have the signature `(&self) -> bool`
112    pub fn len(&self, world: &World) -> usize {
113        (self.len)(world)
114    }
115
116    /// Equivalent of [`Assets::is_empty`]
117    pub fn is_empty(&self, world: &World) -> bool {
118        self.len(world) == 0
119    }
120
121    /// Equivalent of [`Assets::ids`]
122    pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
123        (self.ids)(world)
124    }
125}
126
127impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
128    fn from_type() -> Self {
129        ReflectAsset {
130            handle_type_id: TypeId::of::<Handle<A>>(),
131            assets_resource_type_id: TypeId::of::<Assets<A>>(),
132            get: |world, handle| {
133                let assets = world.resource::<Assets<A>>();
134                let asset = assets.get(&handle.typed_debug_checked());
135                asset.map(|asset| asset as &dyn Reflect)
136            },
137            get_unchecked_mut: |world, handle| {
138                // SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
139                // and must ensure to only have at most one reference to it live at all times.
140                #[allow(unsafe_code)]
141                let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
142                let asset = assets.get_mut(&handle.typed_debug_checked());
143                asset.map(|asset| asset as &mut dyn Reflect)
144            },
145            add: |world, value| {
146                let mut assets = world.resource_mut::<Assets<A>>();
147                let value: A = FromReflect::from_reflect(value)
148                    .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
149                assets.add(value).untyped()
150            },
151            insert: |world, handle, value| {
152                let mut assets = world.resource_mut::<Assets<A>>();
153                let value: A = FromReflect::from_reflect(value)
154                    .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
155                assets.insert(&handle.typed_debug_checked(), value);
156            },
157            len: |world| {
158                let assets = world.resource::<Assets<A>>();
159                assets.len()
160            },
161            ids: |world| {
162                let assets = world.resource::<Assets<A>>();
163                Box::new(assets.ids().map(|i| i.untyped()))
164            },
165            remove: |world, handle| {
166                let mut assets = world.resource_mut::<Assets<A>>();
167                let value = assets.remove(&handle.typed_debug_checked());
168                value.map(|value| Box::new(value) as Box<dyn Reflect>)
169            },
170        }
171    }
172}
173
174/// Reflect type data struct relating a [`Handle<T>`] back to the `T` asset type.
175///
176/// Say you want to look up the asset values of a list of handles when you have access to their `&dyn Reflect` form.
177/// Assets can be looked up in the world using [`ReflectAsset`], but how do you determine which [`ReflectAsset`] to use when
178/// only looking at the handle? [`ReflectHandle`] is stored in the type registry on each `Handle<T>` type, so you can use [`ReflectHandle::asset_type_id`] to look up
179/// the [`ReflectAsset`] type data on the corresponding `T` asset type:
180///
181///
182/// ```no_run
183/// # use bevy_reflect::{TypeRegistry, prelude::*};
184/// # use bevy_ecs::prelude::*;
185/// use bevy_asset::{ReflectHandle, ReflectAsset};
186///
187/// # let world: &World = unimplemented!();
188/// # let type_registry: TypeRegistry = unimplemented!();
189/// let handles: Vec<&dyn Reflect> = unimplemented!();
190/// for handle in handles {
191///     let reflect_handle = type_registry.get_type_data::<ReflectHandle>(handle.type_id()).unwrap();
192///     let reflect_asset = type_registry.get_type_data::<ReflectAsset>(reflect_handle.asset_type_id()).unwrap();
193///
194///     let handle = reflect_handle.downcast_handle_untyped(handle.as_any()).unwrap();
195///     let value = reflect_asset.get(world, handle).unwrap();
196///     println!("{value:?}");
197/// }
198/// ```
199#[derive(Clone)]
200pub struct ReflectHandle {
201    asset_type_id: TypeId,
202    downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
203    typed: fn(UntypedHandle) -> Box<dyn Reflect>,
204}
205impl ReflectHandle {
206    /// The [`TypeId`] of the asset
207    pub fn asset_type_id(&self) -> TypeId {
208        self.asset_type_id
209    }
210
211    /// A way to go from a [`Handle<T>`] in a `dyn Any` to a [`UntypedHandle`]
212    pub fn downcast_handle_untyped(&self, handle: &dyn Any) -> Option<UntypedHandle> {
213        (self.downcast_handle_untyped)(handle)
214    }
215
216    /// A way to go from a [`UntypedHandle`] to a [`Handle<T>`] in a `Box<dyn Reflect>`.
217    /// Equivalent of [`UntypedHandle::typed`].
218    pub fn typed(&self, handle: UntypedHandle) -> Box<dyn Reflect> {
219        (self.typed)(handle)
220    }
221}
222
223impl<A: Asset> FromType<Handle<A>> for ReflectHandle {
224    fn from_type() -> Self {
225        ReflectHandle {
226            asset_type_id: TypeId::of::<A>(),
227            downcast_handle_untyped: |handle: &dyn Any| {
228                handle
229                    .downcast_ref::<Handle<A>>()
230                    .map(|h| h.clone().untyped())
231            },
232            typed: |handle: UntypedHandle| Box::new(handle.typed_debug_checked::<A>()),
233        }
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use std::any::TypeId;
240
241    use crate as bevy_asset;
242    use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset, UntypedHandle};
243    use bevy_app::App;
244    use bevy_ecs::reflect::AppTypeRegistry;
245    use bevy_reflect::{Reflect, ReflectMut};
246
247    #[derive(Asset, Reflect)]
248    struct AssetType {
249        field: String,
250    }
251
252    #[test]
253    fn test_reflect_asset_operations() {
254        let mut app = App::new();
255        app.add_plugins(AssetPlugin::default())
256            .init_asset::<AssetType>()
257            .register_asset_reflect::<AssetType>();
258
259        let reflect_asset = {
260            let type_registry = app.world().resource::<AppTypeRegistry>();
261            let type_registry = type_registry.read();
262
263            type_registry
264                .get_type_data::<ReflectAsset>(TypeId::of::<AssetType>())
265                .unwrap()
266                .clone()
267        };
268
269        let value = AssetType {
270            field: "test".into(),
271        };
272
273        let handle = reflect_asset.add(app.world_mut(), &value);
274        let ReflectMut::Struct(strukt) = reflect_asset
275            .get_mut(app.world_mut(), handle)
276            .unwrap()
277            .reflect_mut()
278        else {
279            unreachable!();
280        };
281        strukt
282            .field_mut("field")
283            .unwrap()
284            .apply(&String::from("edited"));
285
286        assert_eq!(reflect_asset.len(app.world()), 1);
287        let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
288        assert_eq!(ids.len(), 1);
289
290        let fetched_handle = UntypedHandle::Weak(ids[0]);
291        let asset = reflect_asset
292            .get(app.world(), fetched_handle.clone_weak())
293            .unwrap();
294        assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
295
296        reflect_asset
297            .remove(app.world_mut(), fetched_handle)
298            .unwrap();
299        assert_eq!(reflect_asset.len(app.world()), 0);
300    }
301}