bevy_asset/
reflect.rs

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