bevy_asset/
handle.rs

1use crate::{
2    meta::MetaTransform, Asset, AssetId, AssetIndex, AssetIndexAllocator, AssetPath,
3    ErasedAssetIndex, UntypedAssetId,
4};
5use alloc::sync::Arc;
6use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
7use core::{
8    any::TypeId,
9    hash::{Hash, Hasher},
10    marker::PhantomData,
11};
12use crossbeam_channel::{Receiver, Sender};
13use disqualified::ShortName;
14use thiserror::Error;
15use uuid::Uuid;
16
17/// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_.
18/// This should _only_ be used for one specific asset type.
19#[derive(Clone)]
20pub struct AssetHandleProvider {
21    pub(crate) allocator: Arc<AssetIndexAllocator>,
22    pub(crate) drop_sender: Sender<DropEvent>,
23    pub(crate) drop_receiver: Receiver<DropEvent>,
24    pub(crate) type_id: TypeId,
25}
26
27#[derive(Debug)]
28pub(crate) struct DropEvent {
29    pub(crate) index: ErasedAssetIndex,
30    pub(crate) asset_server_managed: bool,
31}
32
33impl AssetHandleProvider {
34    pub(crate) fn new(type_id: TypeId, allocator: Arc<AssetIndexAllocator>) -> Self {
35        let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
36        Self {
37            type_id,
38            allocator,
39            drop_sender,
40            drop_receiver,
41        }
42    }
43
44    /// Reserves a new strong [`UntypedHandle`] (with a new [`UntypedAssetId`]). The stored [`Asset`] [`TypeId`] in the
45    /// [`UntypedHandle`] will match the [`Asset`] [`TypeId`] assigned to this [`AssetHandleProvider`].
46    pub fn reserve_handle(&self) -> UntypedHandle {
47        let index = self.allocator.reserve();
48        UntypedHandle::Strong(self.get_handle(index, false, None, None))
49    }
50
51    pub(crate) fn get_handle(
52        &self,
53        index: AssetIndex,
54        asset_server_managed: bool,
55        path: Option<AssetPath<'static>>,
56        meta_transform: Option<MetaTransform>,
57    ) -> Arc<StrongHandle> {
58        Arc::new(StrongHandle {
59            index,
60            type_id: self.type_id,
61            drop_sender: self.drop_sender.clone(),
62            meta_transform,
63            path,
64            asset_server_managed,
65        })
66    }
67
68    pub(crate) fn reserve_handle_internal(
69        &self,
70        asset_server_managed: bool,
71        path: Option<AssetPath<'static>>,
72        meta_transform: Option<MetaTransform>,
73    ) -> Arc<StrongHandle> {
74        let index = self.allocator.reserve();
75        self.get_handle(index, asset_server_managed, path, meta_transform)
76    }
77}
78
79/// The internal "strong" [`Asset`] handle storage for [`Handle::Strong`] and [`UntypedHandle::Strong`]. When this is dropped,
80/// the [`Asset`] will be freed. It also stores some asset metadata for easy access from handles.
81#[derive(TypePath)]
82pub struct StrongHandle {
83    pub(crate) index: AssetIndex,
84    pub(crate) type_id: TypeId,
85    pub(crate) asset_server_managed: bool,
86    pub(crate) path: Option<AssetPath<'static>>,
87    /// Modifies asset meta. This is stored on the handle because it is:
88    /// 1. configuration tied to the lifetime of a specific asset load
89    /// 2. configuration that must be repeatable when the asset is hot-reloaded
90    pub(crate) meta_transform: Option<MetaTransform>,
91    pub(crate) drop_sender: Sender<DropEvent>,
92}
93
94impl Drop for StrongHandle {
95    fn drop(&mut self) {
96        let _ = self.drop_sender.send(DropEvent {
97            index: ErasedAssetIndex::new(self.index, self.type_id),
98            asset_server_managed: self.asset_server_managed,
99        });
100    }
101}
102
103impl core::fmt::Debug for StrongHandle {
104    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105        f.debug_struct("StrongHandle")
106            .field("index", &self.index)
107            .field("type_id", &self.type_id)
108            .field("asset_server_managed", &self.asset_server_managed)
109            .field("path", &self.path)
110            .field("drop_sender", &self.drop_sender)
111            .finish()
112    }
113}
114
115/// A handle to a specific [`Asset`] of type `A`. Handles act as abstract "references" to
116/// assets, whose data are stored in the [`Assets<A>`](crate::prelude::Assets) resource,
117/// avoiding the need to store multiple copies of the same data.
118///
119/// If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
120/// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Uuid`], it does not necessarily reference a live [`Asset`],
121/// nor will it keep assets alive.
122///
123/// Modifying a *handle* will change which existing asset is referenced, but modifying the *asset*
124/// (by mutating the [`Assets`](crate::prelude::Assets) resource) will change the asset for all handles referencing it.
125///
126/// [`Handle`] can be cloned. If a [`Handle::Strong`] is cloned, the referenced [`Asset`] will not be freed until _all_ instances
127/// of the [`Handle`] are dropped.
128///
129/// [`Handle::Strong`], via [`StrongHandle`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
130#[derive(Reflect)]
131#[reflect(Default, Debug, Hash, PartialEq, Clone)]
132pub enum Handle<A: Asset> {
133    /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
134    /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
135    Strong(Arc<StrongHandle>),
136    /// A reference to an [`Asset`] using a stable-across-runs / const identifier. Dropping this
137    /// handle will not result in the asset being dropped.
138    Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
139}
140
141impl<T: Asset> Clone for Handle<T> {
142    fn clone(&self) -> Self {
143        match self {
144            Handle::Strong(handle) => Handle::Strong(handle.clone()),
145            Handle::Uuid(uuid, ..) => Handle::Uuid(*uuid, PhantomData),
146        }
147    }
148}
149
150impl<A: Asset> Handle<A> {
151    /// Returns the [`AssetId`] of this [`Asset`].
152    #[inline]
153    pub fn id(&self) -> AssetId<A> {
154        match self {
155            Handle::Strong(handle) => AssetId::Index {
156                index: handle.index,
157                marker: PhantomData,
158            },
159            Handle::Uuid(uuid, ..) => AssetId::Uuid { uuid: *uuid },
160        }
161    }
162
163    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
164    #[inline]
165    pub fn path(&self) -> Option<&AssetPath<'static>> {
166        match self {
167            Handle::Strong(handle) => handle.path.as_ref(),
168            Handle::Uuid(..) => None,
169        }
170    }
171
172    /// Returns `true` if this is a uuid handle.
173    #[inline]
174    pub fn is_uuid(&self) -> bool {
175        matches!(self, Handle::Uuid(..))
176    }
177
178    /// Returns `true` if this is a strong handle.
179    #[inline]
180    pub fn is_strong(&self) -> bool {
181        matches!(self, Handle::Strong(_))
182    }
183
184    /// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
185    /// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Uuid`] for
186    /// [`Handle::Uuid`].
187    #[inline]
188    pub fn untyped(self) -> UntypedHandle {
189        self.into()
190    }
191}
192
193impl<A: Asset> Default for Handle<A> {
194    fn default() -> Self {
195        Handle::Uuid(AssetId::<A>::DEFAULT_UUID, PhantomData)
196    }
197}
198
199impl<A: Asset> core::fmt::Debug for Handle<A> {
200    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
201        let name = ShortName::of::<A>();
202        match self {
203            Handle::Strong(handle) => {
204                write!(
205                    f,
206                    "StrongHandle<{name}>{{ index: {:?}, type_id: {:?}, path: {:?} }}",
207                    handle.index, handle.type_id, handle.path
208                )
209            }
210            Handle::Uuid(uuid, ..) => write!(f, "UuidHandle<{name}>({uuid:?})"),
211        }
212    }
213}
214
215impl<A: Asset> Hash for Handle<A> {
216    #[inline]
217    fn hash<H: Hasher>(&self, state: &mut H) {
218        self.id().hash(state);
219    }
220}
221
222impl<A: Asset> PartialOrd for Handle<A> {
223    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
224        Some(self.cmp(other))
225    }
226}
227
228impl<A: Asset> Ord for Handle<A> {
229    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
230        self.id().cmp(&other.id())
231    }
232}
233
234impl<A: Asset> PartialEq for Handle<A> {
235    #[inline]
236    fn eq(&self, other: &Self) -> bool {
237        self.id() == other.id()
238    }
239}
240
241impl<A: Asset> Eq for Handle<A> {}
242
243impl<A: Asset> From<&Handle<A>> for AssetId<A> {
244    #[inline]
245    fn from(value: &Handle<A>) -> Self {
246        value.id()
247    }
248}
249
250impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
251    #[inline]
252    fn from(value: &Handle<A>) -> Self {
253        value.id().into()
254    }
255}
256
257impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
258    #[inline]
259    fn from(value: &mut Handle<A>) -> Self {
260        value.id()
261    }
262}
263
264impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
265    #[inline]
266    fn from(value: &mut Handle<A>) -> Self {
267        value.id().into()
268    }
269}
270
271impl<A: Asset> From<Uuid> for Handle<A> {
272    #[inline]
273    fn from(uuid: Uuid) -> Self {
274        Handle::Uuid(uuid, PhantomData)
275    }
276}
277
278/// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime
279/// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types
280/// to be stored together and compared.
281///
282/// See [`Handle`] for more information.
283#[derive(Clone, Reflect)]
284pub enum UntypedHandle {
285    /// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped.
286    Strong(Arc<StrongHandle>),
287    /// A UUID handle, which does not keep the referenced [`Asset`] alive.
288    Uuid {
289        /// An identifier that records the underlying asset type.
290        type_id: TypeId,
291        /// The UUID provided during asset registration.
292        uuid: Uuid,
293    },
294}
295
296impl UntypedHandle {
297    /// Returns the [`UntypedAssetId`] for the referenced asset.
298    #[inline]
299    pub fn id(&self) -> UntypedAssetId {
300        match self {
301            UntypedHandle::Strong(handle) => UntypedAssetId::Index {
302                type_id: handle.type_id,
303                index: handle.index,
304            },
305            UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
306                uuid: *uuid,
307                type_id: *type_id,
308            },
309        }
310    }
311
312    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
313    #[inline]
314    pub fn path(&self) -> Option<&AssetPath<'static>> {
315        match self {
316            UntypedHandle::Strong(handle) => handle.path.as_ref(),
317            UntypedHandle::Uuid { .. } => None,
318        }
319    }
320
321    /// Returns the [`TypeId`] of the referenced [`Asset`].
322    #[inline]
323    pub fn type_id(&self) -> TypeId {
324        match self {
325            UntypedHandle::Strong(handle) => handle.type_id,
326            UntypedHandle::Uuid { type_id, .. } => *type_id,
327        }
328    }
329
330    /// Converts to a typed Handle. This _will not check if the target Handle type matches_.
331    #[inline]
332    pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
333        match self {
334            UntypedHandle::Strong(handle) => Handle::Strong(handle),
335            UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
336        }
337    }
338
339    /// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it
340    ///  _will not check if the target Handle type matches in release builds_. Use this as an optimization
341    /// when you want some degree of validation at dev-time, but you are also very certain that the type
342    /// actually matches.
343    #[inline]
344    pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
345        debug_assert_eq!(
346            self.type_id(),
347            TypeId::of::<A>(),
348            "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
349        );
350        self.typed_unchecked()
351    }
352
353    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
354    #[inline]
355    pub fn typed<A: Asset>(self) -> Handle<A> {
356        let Ok(handle) = self.try_typed() else {
357            panic!(
358                "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
359                core::any::type_name::<A>()
360            )
361        };
362
363        handle
364    }
365
366    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
367    #[inline]
368    pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
369        Handle::try_from(self)
370    }
371
372    /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform
373    /// associated with it.
374    #[inline]
375    pub fn meta_transform(&self) -> Option<&MetaTransform> {
376        match self {
377            UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
378            UntypedHandle::Uuid { .. } => None,
379        }
380    }
381}
382
383impl PartialEq for UntypedHandle {
384    #[inline]
385    fn eq(&self, other: &Self) -> bool {
386        self.id() == other.id() && self.type_id() == other.type_id()
387    }
388}
389
390impl Eq for UntypedHandle {}
391
392impl Hash for UntypedHandle {
393    #[inline]
394    fn hash<H: Hasher>(&self, state: &mut H) {
395        self.id().hash(state);
396    }
397}
398
399impl core::fmt::Debug for UntypedHandle {
400    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401        match self {
402            UntypedHandle::Strong(handle) => {
403                write!(
404                    f,
405                    "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
406                    handle.type_id, handle.index, handle.path
407                )
408            }
409            UntypedHandle::Uuid { type_id, uuid } => {
410                write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
411            }
412        }
413    }
414}
415
416impl PartialOrd for UntypedHandle {
417    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
418        if self.type_id() == other.type_id() {
419            self.id().partial_cmp(&other.id())
420        } else {
421            None
422        }
423    }
424}
425
426impl From<&UntypedHandle> for UntypedAssetId {
427    #[inline]
428    fn from(value: &UntypedHandle) -> Self {
429        value.id()
430    }
431}
432
433// Cross Operations
434
435impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
436    #[inline]
437    fn eq(&self, other: &UntypedHandle) -> bool {
438        TypeId::of::<A>() == other.type_id() && self.id() == other.id()
439    }
440}
441
442impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
443    #[inline]
444    fn eq(&self, other: &Handle<A>) -> bool {
445        other.eq(self)
446    }
447}
448
449impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
450    #[inline]
451    fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
452        if TypeId::of::<A>() != other.type_id() {
453            None
454        } else {
455            self.id().partial_cmp(&other.id())
456        }
457    }
458}
459
460impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
461    #[inline]
462    fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
463        Some(other.partial_cmp(self)?.reverse())
464    }
465}
466
467impl<A: Asset> From<Handle<A>> for UntypedHandle {
468    fn from(value: Handle<A>) -> Self {
469        match value {
470            Handle::Strong(handle) => UntypedHandle::Strong(handle),
471            Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
472                type_id: TypeId::of::<A>(),
473                uuid,
474            },
475        }
476    }
477}
478
479impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
480    type Error = UntypedAssetConversionError;
481
482    fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
483        let found = value.type_id();
484        let expected = TypeId::of::<A>();
485
486        if found != expected {
487            return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
488        }
489
490        Ok(match value {
491            UntypedHandle::Strong(handle) => Handle::Strong(handle),
492            UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
493        })
494    }
495}
496
497/// Creates a [`Handle`] from a string literal containing a UUID.
498///
499/// # Examples
500///
501/// ```
502/// # use bevy_asset::{Handle, uuid_handle};
503/// # type Image = ();
504/// const IMAGE: Handle<Image> = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
505/// ```
506#[macro_export]
507macro_rules! uuid_handle {
508    ($uuid:expr) => {{
509        $crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
510    }};
511}
512
513#[deprecated = "Use uuid_handle! instead"]
514#[macro_export]
515macro_rules! weak_handle {
516    ($uuid:expr) => {
517        $crate::uuid_handle!($uuid)
518    };
519}
520
521/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
522#[derive(Error, Debug, PartialEq, Clone)]
523#[non_exhaustive]
524pub enum UntypedAssetConversionError {
525    /// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type.
526    #[error(
527        "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
528    )]
529    TypeIdMismatch {
530        /// The expected [`TypeId`] of the [`Handle`] being converted to.
531        expected: TypeId,
532        /// The [`TypeId`] of the [`UntypedHandle`] being converted from.
533        found: TypeId,
534    },
535}
536
537#[cfg(test)]
538mod tests {
539    use alloc::boxed::Box;
540    use bevy_platform::hash::FixedHasher;
541    use bevy_reflect::PartialReflect;
542    use core::hash::BuildHasher;
543    use uuid::Uuid;
544
545    use super::*;
546
547    type TestAsset = ();
548
549    const UUID_1: Uuid = Uuid::from_u128(123);
550    const UUID_2: Uuid = Uuid::from_u128(456);
551
552    /// Simple utility to directly hash a value using a fixed hasher
553    fn hash<T: Hash>(data: &T) -> u64 {
554        FixedHasher.hash_one(data)
555    }
556
557    /// Typed and Untyped `Handles` should be equivalent to each other and themselves
558    #[test]
559    fn equality() {
560        let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
561        let untyped = UntypedHandle::Uuid {
562            type_id: TypeId::of::<TestAsset>(),
563            uuid: UUID_1,
564        };
565
566        assert_eq!(
567            Ok(typed.clone()),
568            Handle::<TestAsset>::try_from(untyped.clone())
569        );
570        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
571        assert_eq!(typed, untyped);
572    }
573
574    /// Typed and Untyped `Handles` should be orderable amongst each other and themselves
575    #[test]
576    #[expect(
577        clippy::cmp_owned,
578        reason = "This lints on the assertion that a typed handle converted to an untyped handle maintains its ordering compared to an untyped handle. While the conversion would normally be useless, we need to ensure that converted handles maintain their ordering, making the conversion necessary here."
579    )]
580    fn ordering() {
581        assert!(UUID_1 < UUID_2);
582
583        let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
584        let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
585        let untyped_1 = UntypedHandle::Uuid {
586            type_id: TypeId::of::<TestAsset>(),
587            uuid: UUID_1,
588        };
589        let untyped_2 = UntypedHandle::Uuid {
590            type_id: TypeId::of::<TestAsset>(),
591            uuid: UUID_2,
592        };
593
594        assert!(typed_1 < typed_2);
595        assert!(untyped_1 < untyped_2);
596
597        assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
598        assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
599
600        assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
601        assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
602
603        assert!(typed_1 < untyped_2);
604        assert!(untyped_1 < typed_2);
605    }
606
607    /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
608    #[test]
609    fn hashing() {
610        let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
611        let untyped = UntypedHandle::Uuid {
612            type_id: TypeId::of::<TestAsset>(),
613            uuid: UUID_1,
614        };
615
616        assert_eq!(
617            hash(&typed),
618            hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
619        );
620        assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
621        assert_eq!(hash(&typed), hash(&untyped));
622    }
623
624    /// Typed and Untyped `Handles` should be interchangeable
625    #[test]
626    fn conversion() {
627        let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
628        let untyped = UntypedHandle::Uuid {
629            type_id: TypeId::of::<TestAsset>(),
630            uuid: UUID_1,
631        };
632
633        assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
634        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
635    }
636
637    #[test]
638    fn from_uuid() {
639        let uuid = UUID_1;
640        let handle: Handle<TestAsset> = uuid.into();
641
642        assert!(handle.is_uuid());
643        assert_eq!(handle.id(), AssetId::Uuid { uuid });
644    }
645
646    /// `PartialReflect::reflect_clone`/`PartialReflect::to_dynamic` should increase the strong count of a strong handle
647    #[test]
648    fn strong_handle_reflect_clone() {
649        use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
650        use bevy_app::App;
651        use bevy_reflect::FromReflect;
652
653        #[derive(Reflect)]
654        struct MyAsset {
655            value: u32,
656        }
657        impl Asset for MyAsset {}
658        impl VisitAssetDependencies for MyAsset {
659            fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
660        }
661
662        let mut app = App::new();
663        app.add_plugins(AssetPlugin::default())
664            .init_asset::<MyAsset>();
665        let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
666
667        let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
668        match &handle {
669            Handle::Strong(strong) => {
670                assert_eq!(
671                    Arc::strong_count(strong),
672                    1,
673                    "Inserting the asset should result in a strong count of 1"
674                );
675
676                let reflected: &dyn Reflect = &handle;
677                let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
678
679                assert_eq!(
680                    Arc::strong_count(strong),
681                    2,
682                    "Cloning the handle with reflect should increase the strong count to 2"
683                );
684
685                let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
686
687                assert_eq!(
688                    Arc::strong_count(strong),
689                    3,
690                    "Converting the handle to a dynamic should increase the strong count to 3"
691                );
692
693                let from_reflect_handle: Handle<MyAsset> =
694                    FromReflect::from_reflect(&*dynamic_handle).unwrap();
695
696                assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
697                assert!(
698                    from_reflect_handle.is_strong(),
699                    "The cloned handle should still be strong"
700                );
701            }
702            _ => panic!("Expected a strong handle"),
703        }
704    }
705}