Skip to main content

bevy_asset/
reflect.rs

1use alloc::{borrow::Cow, boxed::Box, format};
2use core::any::{Any, TypeId};
3use serde::{de::Error as _, ser::Error as _, Deserialize, Deserializer, Serialize};
4use thiserror::Error;
5use tracing::warn;
6use uuid::Uuid;
7
8use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
9use bevy_reflect::{
10    serde::{ReflectDeserializerProcessor, ReflectSerializerProcessor},
11    FromReflect, FromType, PartialReflect, Reflect, TypeRegistry,
12};
13
14use crate::{
15    Asset, AssetId, AssetPath, AssetServer, Assets, Handle, InvalidGenerationError, LoadContext,
16    UntypedAssetId, UntypedHandle,
17};
18
19/// Type data for the [`TypeRegistry`] used to operate on reflected [`Asset`]s.
20///
21/// This type provides similar methods to [`Assets<T>`] like [`get`](ReflectAsset::get),
22/// [`add`](ReflectAsset::add) and [`remove`](ReflectAsset::remove), but can be used in situations where you don't know which asset type `T` you want
23/// until runtime.
24///
25/// [`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).
26#[derive(Clone)]
27pub struct ReflectAsset {
28    handle_type_id: TypeId,
29    assets_resource_type_id: TypeId,
30
31    get: fn(&World, UntypedAssetId) -> Option<&dyn Reflect>,
32    // SAFETY:
33    // - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets<T>` resource mutably
34    // - may only be used to access **at most one** access at once
35    get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedAssetId) -> Option<&mut dyn Reflect>,
36    add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
37    insert:
38        fn(&mut World, UntypedAssetId, &dyn PartialReflect) -> Result<(), InvalidGenerationError>,
39    len: fn(&World) -> usize,
40    ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
41    remove: fn(&mut World, UntypedAssetId) -> Option<Box<dyn Reflect>>,
42}
43
44impl ReflectAsset {
45    /// The [`TypeId`] of the [`Handle<T>`] for this asset
46    pub fn handle_type_id(&self) -> TypeId {
47        self.handle_type_id
48    }
49
50    /// The [`TypeId`] of the [`Assets<T>`] resource
51    pub fn assets_resource_type_id(&self) -> TypeId {
52        self.assets_resource_type_id
53    }
54
55    /// Equivalent of [`Assets::get`]
56    pub fn get<'w>(
57        &self,
58        world: &'w World,
59        asset_id: impl Into<UntypedAssetId>,
60    ) -> Option<&'w dyn Reflect> {
61        (self.get)(world, asset_id.into())
62    }
63
64    /// Equivalent of [`Assets::get_mut`]
65    pub fn get_mut<'w>(
66        &self,
67        world: &'w mut World,
68        asset_id: impl Into<UntypedAssetId>,
69    ) -> Option<&'w mut dyn Reflect> {
70        #[expect(
71            unsafe_code,
72            reason = "Use of unsafe `Self::get_unchecked_mut()` function."
73        )]
74        // SAFETY: unique world access
75        unsafe {
76            (self.get_unchecked_mut)(world.as_unsafe_world_cell(), asset_id.into())
77        }
78    }
79
80    /// Equivalent of [`Assets::get_mut`], but works with an [`UnsafeWorldCell`].
81    ///
82    /// Only use this method when you have ensured that you are the *only* one with access to the [`Assets`] resource of the asset type.
83    /// Furthermore, this does *not* allow you to have look up two distinct handles,
84    /// you can only have at most one alive at the same time.
85    /// This means that this is *not allowed*:
86    /// ```no_run
87    /// # use bevy_asset::{ReflectAsset, UntypedHandle};
88    /// # use bevy_ecs::prelude::World;
89    /// # let reflect_asset: ReflectAsset = unimplemented!();
90    /// # let mut world: World = unimplemented!();
91    /// # let handle_1: UntypedHandle = unimplemented!();
92    /// # let handle_2: UntypedHandle = unimplemented!();
93    /// let unsafe_world_cell = world.as_unsafe_world_cell();
94    /// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_1).unwrap() };
95    /// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_2).unwrap() };
96    /// // ^ not allowed, two mutable references through the same asset resource, even though the
97    /// // handles are distinct
98    ///
99    /// println!("a = {a:?}, b = {b:?}");
100    /// ```
101    ///
102    /// # Safety
103    /// This method does not prevent you from having two mutable pointers to the same data,
104    /// violating Rust's aliasing rules. To avoid this:
105    /// * Only call this method if you know that the [`UnsafeWorldCell`] may be used to access the corresponding `Assets<T>`
106    /// * Don't call this method more than once in the same scope.
107    #[expect(
108        unsafe_code,
109        reason = "This function calls unsafe code and has safety requirements."
110    )]
111    pub unsafe fn get_unchecked_mut<'w>(
112        &self,
113        world: UnsafeWorldCell<'w>,
114        asset_id: impl Into<UntypedAssetId>,
115    ) -> Option<&'w mut dyn Reflect> {
116        // SAFETY: requirements are deferred to the caller
117        unsafe { (self.get_unchecked_mut)(world, asset_id.into()) }
118    }
119
120    /// Equivalent of [`Assets::add`]
121    pub fn add(&self, world: &mut World, value: &dyn PartialReflect) -> UntypedHandle {
122        (self.add)(world, value)
123    }
124    /// Equivalent of [`Assets::insert`]
125    pub fn insert(
126        &self,
127        world: &mut World,
128        asset_id: impl Into<UntypedAssetId>,
129        value: &dyn PartialReflect,
130    ) -> Result<(), InvalidGenerationError> {
131        (self.insert)(world, asset_id.into(), value)
132    }
133
134    /// Equivalent of [`Assets::remove`]
135    pub fn remove(
136        &self,
137        world: &mut World,
138        asset_id: impl Into<UntypedAssetId>,
139    ) -> Option<Box<dyn Reflect>> {
140        (self.remove)(world, asset_id.into())
141    }
142
143    /// Equivalent of [`Assets::len`]
144    pub fn len(&self, world: &World) -> usize {
145        (self.len)(world)
146    }
147
148    /// Equivalent of [`Assets::is_empty`]
149    pub fn is_empty(&self, world: &World) -> bool {
150        self.len(world) == 0
151    }
152
153    /// Equivalent of [`Assets::ids`]
154    pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
155        (self.ids)(world)
156    }
157}
158
159impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
160    fn from_type() -> Self {
161        ReflectAsset {
162            handle_type_id: TypeId::of::<Handle<A>>(),
163            assets_resource_type_id: TypeId::of::<Assets<A>>(),
164            get: |world, asset_id| {
165                let assets = world.resource::<Assets<A>>();
166                let asset = assets.get(asset_id.typed_debug_checked());
167                asset.map(|asset| asset as &dyn Reflect)
168            },
169            get_unchecked_mut: |world, asset_id| {
170                // SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
171                // and must ensure to only have at most one reference to it live at all times.
172                #[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
173                let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
174                let asset = assets.get_mut(asset_id.typed_debug_checked());
175                asset.map(|asset| asset.into_inner() as &mut dyn Reflect)
176            },
177            add: |world, value| {
178                let mut assets = world.resource_mut::<Assets<A>>();
179                let value: A = FromReflect::from_reflect(value)
180                    .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
181                assets.add(value).untyped()
182            },
183            insert: |world, asset_id, value| {
184                let mut assets = world.resource_mut::<Assets<A>>();
185                let value: A = FromReflect::from_reflect(value)
186                    .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
187                assets.insert(asset_id.typed_debug_checked(), value)
188            },
189            len: |world| {
190                let assets = world.resource::<Assets<A>>();
191                assets.len()
192            },
193            ids: |world| {
194                let assets = world.resource::<Assets<A>>();
195                Box::new(assets.ids().map(AssetId::untyped))
196            },
197            remove: |world, asset_id| {
198                let mut assets = world.resource_mut::<Assets<A>>();
199                let value = assets.remove(asset_id.typed_debug_checked());
200                value.map(|value| Box::new(value) as Box<dyn Reflect>)
201            },
202        }
203    }
204}
205
206/// Reflect type data struct relating a [`Handle<T>`] back to the `T` asset type.
207///
208/// Say you want to look up the asset values of a list of handles when you have access to their `&dyn Reflect` form.
209/// Assets can be looked up in the world using [`ReflectAsset`], but how do you determine which [`ReflectAsset`] to use when
210/// 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
211/// the [`ReflectAsset`] type data on the corresponding `T` asset type:
212///
213///
214/// ```no_run
215/// # use bevy_reflect::{TypeRegistry, prelude::*};
216/// # use bevy_ecs::prelude::*;
217/// use bevy_asset::{ReflectHandle, ReflectAsset};
218///
219/// # let world: &World = unimplemented!();
220/// # let type_registry: TypeRegistry = unimplemented!();
221/// let handles: Vec<&dyn Reflect> = unimplemented!();
222/// for handle in handles {
223///     let reflect_handle = type_registry.get_type_data::<ReflectHandle>(handle.type_id()).unwrap();
224///     let reflect_asset = type_registry.get_type_data::<ReflectAsset>(reflect_handle.asset_type_id()).unwrap();
225///
226///     let handle = reflect_handle.downcast_handle_untyped(handle.as_any()).unwrap();
227///     let value = reflect_asset.get(world, &handle).unwrap();
228///     println!("{value:?}");
229/// }
230/// ```
231#[derive(Clone)]
232pub struct ReflectHandle {
233    asset_type_id: TypeId,
234    downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
235    typed: fn(UntypedHandle) -> Box<dyn Reflect>,
236}
237
238impl ReflectHandle {
239    /// The [`TypeId`] of the asset
240    pub fn asset_type_id(&self) -> TypeId {
241        self.asset_type_id
242    }
243
244    /// A way to go from a [`Handle<T>`] in a `dyn Any` to a [`UntypedHandle`]
245    pub fn downcast_handle_untyped(&self, handle: &dyn Any) -> Option<UntypedHandle> {
246        (self.downcast_handle_untyped)(handle)
247    }
248
249    /// A way to go from a [`UntypedHandle`] to a [`Handle<T>`] in a `Box<dyn Reflect>`.
250    /// Equivalent of [`UntypedHandle::typed`].
251    pub fn typed(&self, handle: UntypedHandle) -> Box<dyn Reflect> {
252        (self.typed)(handle)
253    }
254}
255
256impl<A: Asset> FromType<Handle<A>> for ReflectHandle {
257    fn from_type() -> Self {
258        ReflectHandle {
259            asset_type_id: TypeId::of::<A>(),
260            downcast_handle_untyped: |handle: &dyn Any| {
261                handle
262                    .downcast_ref::<Handle<A>>()
263                    .map(|h| h.clone().untyped())
264            },
265            typed: |handle: UntypedHandle| Box::new(handle.typed_debug_checked::<A>()),
266        }
267    }
268}
269
270/// A [`ReflectSerializerProcessor`] that manually serializes [`Handle`] and [`UntypedHandle`], and
271/// passes through for all other types.
272///
273/// [`Handle`]s cannot be serialized normally since it contains lots of ephemeral information (e.g.,
274/// the [`AssetId`] of the asset being referenced). This processor serializes just the identifying
275/// and stable parts of the handle. This can later be used to deserialize the handle.
276///
277/// Use [`HandleDeserializeProcessor`] to deserialize this data.
278pub struct HandleSerializeProcessor {
279    /// How ephemeral handles are dealt with.
280    pub ephemeral_handle_behavior: EphemeralHandleBehavior,
281}
282
283/// Specifies the action that will be taken when attempting to serialize an ephemeral handle.
284///
285/// Ephemeral handles are handles to assets that were not loaded. Specifically, these are handles to
286/// assets that were manually added.
287#[derive(Clone, Copy, Debug)]
288pub enum EphemeralHandleBehavior {
289    /// Ephemeral handles are entirely ignored and are serialized as the default [`Handle`].
290    Silent,
291    /// A warning is logged, and the handle is serialized as the default [`Handle`].
292    Warn,
293    /// Serializing an ephemeral handle will cause serialization to return an error.
294    Error,
295}
296
297impl ReflectSerializerProcessor for HandleSerializeProcessor {
298    fn try_serialize<S>(
299        &self,
300        value: &dyn PartialReflect,
301        registry: &TypeRegistry,
302        serializer: S,
303    ) -> Result<Result<S::Ok, S>, S::Error>
304    where
305        S: serde::Serializer,
306    {
307        let Some(value_reflect) = value.try_as_reflect() else {
308            // Anything that isn't a concrete type should be serialized by the underlying
309            // serializer, since it must not be a handle!
310            return Ok(Err(serializer));
311        };
312
313        #[derive(Error, Debug)]
314        #[error("Attempted to serialize an ephemeral asset handle {0:?} while `EphemeralHandleBehavior::Error` is set")]
315        struct SerializingEphemeralHandleError(UntypedHandle);
316
317        fn handle_reference_from_handle(
318            handle: &UntypedHandle,
319            ephemeral_handle_behavior: EphemeralHandleBehavior,
320        ) -> Result<HandleReference, SerializingEphemeralHandleError> {
321            Ok(match &handle {
322                UntypedHandle::Strong(inner) => match &inner.path {
323                    None => {
324                        match ephemeral_handle_behavior {
325                            EphemeralHandleBehavior::Silent => {}
326                            EphemeralHandleBehavior::Warn => {
327                                warn!("Serializing ephemeral handle {handle:?}. Ephemeral handles cannot be deserialized. Replacing with Handle::default");
328                            }
329                            EphemeralHandleBehavior::Error => {
330                                return Err(SerializingEphemeralHandleError(handle.clone()))
331                            }
332                        }
333                        HandleReference::Uuid(AssetId::<()>::DEFAULT_UUID)
334                    }
335                    Some(path) => HandleReference::Path(path.clone_owned()),
336                },
337                UntypedHandle::Uuid { uuid, .. } => HandleReference::Uuid(*uuid),
338            })
339        }
340
341        if let Some(untyped_handle) = value_reflect.downcast_ref::<UntypedHandle>() {
342            let Some(asset_registration) = registry.get(untyped_handle.type_id()) else {
343                return Err(S::Error::custom(format!(
344                    "Missing type registration for asset type of handle {:?}. Ensure the asset implements Reflect, includes #[reflect(Asset)], and is registered",
345                    untyped_handle
346                )));
347            };
348            return Ok(Ok(TypedHandleReference {
349                asset_type: asset_registration.type_info().type_path().into(),
350                reference: handle_reference_from_handle(
351                    untyped_handle,
352                    self.ephemeral_handle_behavior,
353                )
354                .map_err(S::Error::custom)?,
355            }
356            .serialize(serializer)?));
357        }
358
359        let Some(handle_registration) = registry.get(value_reflect.type_id()) else {
360            // This is a slow path. Users are unlikely to be intentionally serializing types without
361            // reflection, especially in production apps, so we can afford to be slow and give
362            // better diagnostics.
363            if let Some(type_info) = value_reflect.get_represented_type_info()
364                && type_info.type_path().starts_with("bevy_asset::Handle")
365            {
366                warn!(
367                    "HandleSerializeProcessor attempted to serialize a handle type \"{}\" without type data. This likely means the asset type was not registered.",
368                    type_info.type_path()
369                );
370            }
371            // Otherwise, fall back to the underlying serializer. Let it handle the error.
372            return Ok(Err(serializer));
373        };
374
375        let Some(reflect_handle) = handle_registration.data::<ReflectHandle>() else {
376            // This isn't an `UntypedHandle` and it isn't a `Handle<A>`, so just let the regular
377            // serializer serialize it.
378            return Ok(Err(serializer));
379        };
380
381        let untyped_handle = reflect_handle
382            .downcast_handle_untyped(value_reflect.as_any())
383            .expect("type includes `ReflectHandle` type data, so it must be a handle matching that type");
384
385        let handle_reference =
386            handle_reference_from_handle(&untyped_handle, self.ephemeral_handle_behavior)
387                .map_err(S::Error::custom)?;
388
389        Ok(Ok(handle_reference.serialize(serializer)?))
390    }
391}
392
393/// A trait for loading an asset.
394///
395/// There are several ways to load an asset. This trait allows deserializing in many contexts
396/// depending on how assets can be loaded. Note all these loads are deferred, and must have a
397/// concrete type.
398pub trait LoadFromPath {
399    /// Initiates the load for the given expected type ID, and the path.
400    ///
401    /// See [`LoadBuilder::load_erased`](crate::LoadBuilder::load_erased) for more.
402    fn load_from_path_erased(&mut self, type_id: TypeId, path: AssetPath<'static>)
403        -> UntypedHandle;
404}
405
406impl LoadFromPath for LoadContext<'_> {
407    fn load_from_path_erased(
408        &mut self,
409        type_id: TypeId,
410        path: AssetPath<'static>,
411    ) -> UntypedHandle {
412        self.load_builder().load_erased(type_id, path)
413    }
414}
415
416impl LoadFromPath for AssetServer {
417    fn load_from_path_erased(
418        &mut self,
419        type_id: TypeId,
420        path: AssetPath<'static>,
421    ) -> UntypedHandle {
422        self.load_builder().load_erased(type_id, path)
423    }
424}
425
426impl LoadFromPath for &AssetServer {
427    fn load_from_path_erased(
428        &mut self,
429        type_id: TypeId,
430        path: AssetPath<'static>,
431    ) -> UntypedHandle {
432        self.load_builder().load_erased(type_id, path)
433    }
434}
435
436/// A [`ReflectDeserializerProcessor`] that manually deserializes [`Handle`] and [`UntypedHandle`],
437/// and passes through for all other types.
438///
439/// [`Handle`]s cannot be deserialized normally since it contains lots of ephemeral information
440/// (e.g., the [`AssetId`] of the asset being referenced). This processor deserializes the
441/// identifying and stable parts of the handle (usually serialized by [`HandleSerializeProcessor`]),
442/// and triggers the load of that handle.
443///
444/// Use [`HandleSerializeProcessor`] to serialize data for this processor.
445pub struct HandleDeserializeProcessor<'a> {
446    /// The loader to load asset paths and retrieve their handles.
447    pub load_from_path: &'a mut dyn LoadFromPath,
448}
449
450impl ReflectDeserializerProcessor for HandleDeserializeProcessor<'_> {
451    fn try_deserialize<'de, D>(
452        &mut self,
453        registration: &bevy_reflect::TypeRegistration,
454        registry: &TypeRegistry,
455        deserializer: D,
456    ) -> Result<Result<Box<dyn PartialReflect>, D>, D::Error>
457    where
458        D: Deserializer<'de>,
459    {
460        if registration.type_id() == TypeId::of::<UntypedHandle>() {
461            let typed_handle_reference = TypedHandleReference::deserialize(deserializer)?;
462            let Some(asset_type) = registry.get_with_type_path(&typed_handle_reference.asset_type)
463            else {
464                return Err(D::Error::custom(format!(
465                    "Could not find asset type by name \"{}\" for UntypedHandle",
466                    &typed_handle_reference.asset_type
467                )));
468            };
469            let type_id = asset_type.type_id();
470            return Ok(Ok(Box::new(match typed_handle_reference.reference {
471                HandleReference::Path(path) => {
472                    self.load_from_path.load_from_path_erased(type_id, path)
473                }
474                HandleReference::Uuid(uuid) => UntypedHandle::Uuid { type_id, uuid },
475            })));
476        }
477
478        let Some(reflect_handle) = registration.data::<ReflectHandle>() else {
479            // This type isn't an `UntypedHandle`, and it isn't a `Handle<A>`, so let the regular
480            // serializer deal with it. Note: it's possible the handle just never got its reflect
481            // data serialized, but most types will fall into this category, so we can't give a
482            // warning here.
483            return Ok(Err(deserializer));
484        };
485
486        let handle_reference = HandleReference::deserialize(deserializer)?;
487
488        let type_id = reflect_handle.asset_type_id;
489        Ok(Ok(reflect_handle.typed(match handle_reference {
490            HandleReference::Path(path) => self.load_from_path.load_from_path_erased(type_id, path),
491            HandleReference::Uuid(uuid) => UntypedHandle::Uuid { type_id, uuid },
492        })))
493    }
494}
495
496/// The "stable" data of a handle that can be serialized and deserialized.
497#[derive(Serialize, Deserialize)]
498pub enum HandleReference {
499    /// The handle references an asset path that needs to be loaded.
500    Path(AssetPath<'static>),
501    /// The handle references a constant [`Uuid`].
502    Uuid(Uuid),
503}
504
505/// The "stable" data of a handle whose asset type information is stored internally.
506#[derive(Serialize, Deserialize)]
507pub struct TypedHandleReference {
508    /// The type path of the asset type this handle references.
509    pub asset_type: Cow<'static, str>,
510    /// The reference for this handle.
511    pub reference: HandleReference,
512}
513
514#[cfg(test)]
515mod tests {
516    use alloc::{string::String, vec, vec::Vec};
517    use core::any::TypeId;
518    use ron::ser::PrettyConfig;
519    use serde::de::DeserializeSeed;
520    use std::path::Path;
521    use uuid::Uuid;
522
523    use crate::{
524        tests::{create_app, run_app_until, CoolText, CoolTextLoader, CoolTextRon, SubText},
525        Asset, AssetApp, AssetServer, Assets, DirectAssetAccessExt, EphemeralHandleBehavior,
526        Handle, HandleDeserializeProcessor, HandleSerializeProcessor, LoadedUntypedAsset,
527        ReflectAsset, UntypedHandle,
528    };
529    use bevy_ecs::reflect::AppTypeRegistry;
530    use bevy_reflect::{
531        serde::{TypedReflectDeserializer, TypedReflectSerializer},
532        FromReflect, Reflect, TypePath,
533    };
534
535    #[derive(Asset, Reflect)]
536    struct AssetType {
537        field: String,
538    }
539
540    #[test]
541    fn test_reflect_asset_operations() {
542        let mut app = create_app().0;
543        app.init_asset::<AssetType>()
544            .register_asset_reflect::<AssetType>();
545
546        let reflect_asset = {
547            let type_registry = app.world().resource::<AppTypeRegistry>();
548            let type_registry = type_registry.read();
549
550            type_registry
551                .get_type_data::<ReflectAsset>(TypeId::of::<AssetType>())
552                .unwrap()
553                .clone()
554        };
555
556        let value = AssetType {
557            field: "test".into(),
558        };
559
560        let handle = reflect_asset.add(app.world_mut(), &value);
561        // struct is a reserved keyword, so we can't use it here
562        let strukt = reflect_asset
563            .get_mut(app.world_mut(), &handle)
564            .unwrap()
565            .reflect_mut()
566            .as_struct()
567            .unwrap();
568        strukt
569            .field_mut("field")
570            .unwrap()
571            .apply(&String::from("edited"));
572
573        assert_eq!(reflect_asset.len(app.world()), 1);
574        let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
575        assert_eq!(ids.len(), 1);
576        let id = ids[0];
577
578        let asset = reflect_asset.get(app.world(), id).unwrap();
579        assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
580
581        reflect_asset.remove(app.world_mut(), id).unwrap();
582        assert_eq!(reflect_asset.len(app.world()), 0);
583    }
584
585    fn serialize_as_cool_text(text: &str) -> String {
586        let cool_text_ron = CoolTextRon {
587            text: text.into(),
588            dependencies: vec![],
589            embedded_dependencies: vec![],
590            sub_texts: vec![],
591        };
592        ron::ser::to_string_pretty(&cool_text_ron, PrettyConfig::new().new_line("\n")).unwrap()
593    }
594
595    #[test]
596    fn roundtrip_reflect_serialize_handles() {
597        #[derive(Asset, TypePath)]
598        struct OtherAsset;
599
600        #[derive(Reflect)]
601        struct Stuff {
602            typed: Handle<CoolText>,
603            untyped: UntypedHandle,
604            uuid: Handle<OtherAsset>,
605            ephemeral: Handle<OtherAsset>,
606        }
607
608        let uuid = Uuid::from_u128(123);
609
610        // Initial app to serialize a `Stuff` instance.
611        let ron_data = {
612            let (mut app, dir) = create_app();
613            app.init_asset::<OtherAsset>()
614                .init_asset::<CoolText>()
615                .init_asset::<SubText>()
616                // Normally reflection auto registration would mean we don't need this, but that
617                // feature may not be set for tests, so register the types manually just in case.
618                .register_asset_reflect::<CoolText>()
619                .register_type::<Stuff>()
620                .register_asset_loader(CoolTextLoader);
621
622            dir.insert_asset_text(Path::new("abc.cool.ron"), &serialize_as_cool_text("hello"));
623            dir.insert_asset_text(Path::new("def.cool.ron"), &serialize_as_cool_text("world"));
624
625            let type_registry = app.world().resource::<AppTypeRegistry>().0.clone();
626            let asset_server = app.world().resource::<AssetServer>().clone();
627
628            let untyped = asset_server.load_builder().load_untyped("def.cool.ron");
629            run_app_until(&mut app, |_| asset_server.is_loaded(&untyped).then_some(()));
630            let untyped = app
631                .world()
632                .resource::<Assets<LoadedUntypedAsset>>()
633                .get(&untyped)
634                .unwrap()
635                .handle
636                .clone();
637
638            let ephemeral = app.world_mut().add_asset(OtherAsset);
639
640            let stuff = Stuff {
641                typed: asset_server.load("abc.cool.ron"),
642                untyped,
643                uuid: uuid.into(),
644                ephemeral,
645            };
646
647            let type_registry = type_registry.read();
648            let processor = HandleSerializeProcessor {
649                ephemeral_handle_behavior: EphemeralHandleBehavior::Silent,
650            };
651            let reflect_serializer =
652                TypedReflectSerializer::with_processor(&stuff, &type_registry, &processor);
653
654            ron::to_string(&reflect_serializer).unwrap()
655        };
656
657        // Create a new app to deserialize the serialized data.
658        let (mut app, dir) = create_app();
659        app.init_asset::<OtherAsset>()
660            .init_asset::<CoolText>()
661            .init_asset::<SubText>()
662            // See above for why we register these manually.
663            .register_asset_reflect::<CoolText>()
664            .register_type::<Stuff>()
665            .register_asset_loader(CoolTextLoader);
666
667        dir.insert_asset_text(Path::new("abc.cool.ron"), &serialize_as_cool_text("hello"));
668        dir.insert_asset_text(Path::new("def.cool.ron"), &serialize_as_cool_text("world"));
669
670        let type_registry = app.world().resource::<AppTypeRegistry>().0.clone();
671        let mut asset_server = app.world().resource::<AssetServer>().clone();
672
673        let type_registry = type_registry.read();
674        let mut processor = HandleDeserializeProcessor {
675            load_from_path: &mut asset_server,
676        };
677        let reflect_deserializer = TypedReflectDeserializer::with_processor(
678            type_registry.get(TypeId::of::<Stuff>()).unwrap(),
679            &type_registry,
680            &mut processor,
681        );
682
683        let mut ron_deserializer = ron::Deserializer::from_str(&ron_data).unwrap();
684        let stuff = Stuff::from_reflect(
685            reflect_deserializer
686                .deserialize(&mut ron_deserializer)
687                .unwrap()
688                .as_ref(),
689        )
690        .unwrap();
691
692        // The UUID handle matches.
693        assert_eq!(stuff.uuid, Handle::from(uuid));
694        // The ephemeral handle was replaced by the default handle.
695        assert_eq!(stuff.ephemeral, Handle::default());
696
697        // The deserializer should have caused the handles to start loading.
698        run_app_until(&mut app, |_| {
699            (asset_server.is_loaded(&stuff.typed) && asset_server.is_loaded(&stuff.untyped))
700                .then_some(())
701        });
702
703        // Make sure that the handles actually do end up with the correct assets.
704        assert_eq!(
705            app.world()
706                .resource::<Assets<CoolText>>()
707                .get(&stuff.typed)
708                .unwrap()
709                .text,
710            "hello"
711        );
712        assert_eq!(
713            app.world()
714                .resource::<Assets<CoolText>>()
715                .get(&stuff.untyped.try_typed::<CoolText>().unwrap())
716                .unwrap()
717                .text,
718            "world"
719        );
720    }
721}