bevy_asset/
handle.rs

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