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
271/// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime
272/// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types
273/// to be stored together and compared.
274///
275/// See [`Handle`] for more information.
276#[derive(Clone, Reflect)]
277pub enum UntypedHandle {
278    /// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped.
279    Strong(Arc<StrongHandle>),
280    /// A UUID handle, which does not keep the referenced [`Asset`] alive.
281    Uuid {
282        /// An identifier that records the underlying asset type.
283        type_id: TypeId,
284        /// The UUID provided during asset registration.
285        uuid: Uuid,
286    },
287}
288
289impl UntypedHandle {
290    /// Returns the [`UntypedAssetId`] for the referenced asset.
291    #[inline]
292    pub fn id(&self) -> UntypedAssetId {
293        match self {
294            UntypedHandle::Strong(handle) => handle.id,
295            UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
296                uuid: *uuid,
297                type_id: *type_id,
298            },
299        }
300    }
301
302    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
303    #[inline]
304    pub fn path(&self) -> Option<&AssetPath<'static>> {
305        match self {
306            UntypedHandle::Strong(handle) => handle.path.as_ref(),
307            UntypedHandle::Uuid { .. } => None,
308        }
309    }
310
311    /// Returns the [`TypeId`] of the referenced [`Asset`].
312    #[inline]
313    pub fn type_id(&self) -> TypeId {
314        match self {
315            UntypedHandle::Strong(handle) => handle.id.type_id(),
316            UntypedHandle::Uuid { type_id, .. } => *type_id,
317        }
318    }
319
320    /// Converts to a typed Handle. This _will not check if the target Handle type matches_.
321    #[inline]
322    pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
323        match self {
324            UntypedHandle::Strong(handle) => Handle::Strong(handle),
325            UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
326        }
327    }
328
329    /// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it
330    ///  _will not check if the target Handle type matches in release builds_. Use this as an optimization
331    /// when you want some degree of validation at dev-time, but you are also very certain that the type
332    /// actually matches.
333    #[inline]
334    pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
335        debug_assert_eq!(
336            self.type_id(),
337            TypeId::of::<A>(),
338            "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
339        );
340        self.typed_unchecked()
341    }
342
343    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
344    #[inline]
345    pub fn typed<A: Asset>(self) -> Handle<A> {
346        let Ok(handle) = self.try_typed() else {
347            panic!(
348                "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
349                core::any::type_name::<A>()
350            )
351        };
352
353        handle
354    }
355
356    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
357    #[inline]
358    pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
359        Handle::try_from(self)
360    }
361
362    /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform
363    /// associated with it.
364    #[inline]
365    pub fn meta_transform(&self) -> Option<&MetaTransform> {
366        match self {
367            UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
368            UntypedHandle::Uuid { .. } => None,
369        }
370    }
371}
372
373impl PartialEq for UntypedHandle {
374    #[inline]
375    fn eq(&self, other: &Self) -> bool {
376        self.id() == other.id() && self.type_id() == other.type_id()
377    }
378}
379
380impl Eq for UntypedHandle {}
381
382impl Hash for UntypedHandle {
383    #[inline]
384    fn hash<H: Hasher>(&self, state: &mut H) {
385        self.id().hash(state);
386    }
387}
388
389impl core::fmt::Debug for UntypedHandle {
390    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
391        match self {
392            UntypedHandle::Strong(handle) => {
393                write!(
394                    f,
395                    "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
396                    handle.id.type_id(),
397                    handle.id.internal(),
398                    handle.path
399                )
400            }
401            UntypedHandle::Uuid { type_id, uuid } => {
402                write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
403            }
404        }
405    }
406}
407
408impl PartialOrd for UntypedHandle {
409    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
410        if self.type_id() == other.type_id() {
411            self.id().partial_cmp(&other.id())
412        } else {
413            None
414        }
415    }
416}
417
418impl From<&UntypedHandle> for UntypedAssetId {
419    #[inline]
420    fn from(value: &UntypedHandle) -> Self {
421        value.id()
422    }
423}
424
425// Cross Operations
426
427impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
428    #[inline]
429    fn eq(&self, other: &UntypedHandle) -> bool {
430        TypeId::of::<A>() == other.type_id() && self.id() == other.id()
431    }
432}
433
434impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
435    #[inline]
436    fn eq(&self, other: &Handle<A>) -> bool {
437        other.eq(self)
438    }
439}
440
441impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
442    #[inline]
443    fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
444        if TypeId::of::<A>() != other.type_id() {
445            None
446        } else {
447            self.id().partial_cmp(&other.id())
448        }
449    }
450}
451
452impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
453    #[inline]
454    fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
455        Some(other.partial_cmp(self)?.reverse())
456    }
457}
458
459impl<A: Asset> From<Handle<A>> for UntypedHandle {
460    fn from(value: Handle<A>) -> Self {
461        match value {
462            Handle::Strong(handle) => UntypedHandle::Strong(handle),
463            Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
464                type_id: TypeId::of::<A>(),
465                uuid,
466            },
467        }
468    }
469}
470
471impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
472    type Error = UntypedAssetConversionError;
473
474    fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
475        let found = value.type_id();
476        let expected = TypeId::of::<A>();
477
478        if found != expected {
479            return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
480        }
481
482        Ok(match value {
483            UntypedHandle::Strong(handle) => Handle::Strong(handle),
484            UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
485        })
486    }
487}
488
489/// Creates a [`Handle`] from a string literal containing a UUID.
490///
491/// # Examples
492///
493/// ```
494/// # use bevy_asset::{Handle, uuid_handle};
495/// # type Image = ();
496/// const IMAGE: Handle<Image> = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
497/// ```
498#[macro_export]
499macro_rules! uuid_handle {
500    ($uuid:expr) => {{
501        $crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
502    }};
503}
504
505#[deprecated = "Use uuid_handle! instead"]
506#[macro_export]
507macro_rules! weak_handle {
508    ($uuid:expr) => {
509        $crate::uuid_handle!($uuid)
510    };
511}
512
513/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
514#[derive(Error, Debug, PartialEq, Clone)]
515#[non_exhaustive]
516pub enum UntypedAssetConversionError {
517    /// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type.
518    #[error(
519        "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
520    )]
521    TypeIdMismatch {
522        /// The expected [`TypeId`] of the [`Handle`] being converted to.
523        expected: TypeId,
524        /// The [`TypeId`] of the [`UntypedHandle`] being converted from.
525        found: TypeId,
526    },
527}
528
529#[cfg(test)]
530mod tests {
531    use alloc::boxed::Box;
532    use bevy_platform::hash::FixedHasher;
533    use bevy_reflect::PartialReflect;
534    use core::hash::BuildHasher;
535    use uuid::Uuid;
536
537    use super::*;
538
539    type TestAsset = ();
540
541    const UUID_1: Uuid = Uuid::from_u128(123);
542    const UUID_2: Uuid = Uuid::from_u128(456);
543
544    /// Simple utility to directly hash a value using a fixed hasher
545    fn hash<T: Hash>(data: &T) -> u64 {
546        FixedHasher.hash_one(data)
547    }
548
549    /// Typed and Untyped `Handles` should be equivalent to each other and themselves
550    #[test]
551    fn equality() {
552        let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
553        let untyped = UntypedHandle::Uuid {
554            type_id: TypeId::of::<TestAsset>(),
555            uuid: UUID_1,
556        };
557
558        assert_eq!(
559            Ok(typed.clone()),
560            Handle::<TestAsset>::try_from(untyped.clone())
561        );
562        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
563        assert_eq!(typed, untyped);
564    }
565
566    /// Typed and Untyped `Handles` should be orderable amongst each other and themselves
567    #[test]
568    #[expect(
569        clippy::cmp_owned,
570        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."
571    )]
572    fn ordering() {
573        assert!(UUID_1 < UUID_2);
574
575        let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
576        let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
577        let untyped_1 = UntypedHandle::Uuid {
578            type_id: TypeId::of::<TestAsset>(),
579            uuid: UUID_1,
580        };
581        let untyped_2 = UntypedHandle::Uuid {
582            type_id: TypeId::of::<TestAsset>(),
583            uuid: UUID_2,
584        };
585
586        assert!(typed_1 < typed_2);
587        assert!(untyped_1 < untyped_2);
588
589        assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
590        assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
591
592        assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
593        assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
594
595        assert!(typed_1 < untyped_2);
596        assert!(untyped_1 < typed_2);
597    }
598
599    /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
600    #[test]
601    fn hashing() {
602        let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
603        let untyped = UntypedHandle::Uuid {
604            type_id: TypeId::of::<TestAsset>(),
605            uuid: UUID_1,
606        };
607
608        assert_eq!(
609            hash(&typed),
610            hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
611        );
612        assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
613        assert_eq!(hash(&typed), hash(&untyped));
614    }
615
616    /// Typed and Untyped `Handles` should be interchangeable
617    #[test]
618    fn conversion() {
619        let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
620        let untyped = UntypedHandle::Uuid {
621            type_id: TypeId::of::<TestAsset>(),
622            uuid: UUID_1,
623        };
624
625        assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
626        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
627    }
628
629    /// `PartialReflect::reflect_clone`/`PartialReflect::to_dynamic` should increase the strong count of a strong handle
630    #[test]
631    fn strong_handle_reflect_clone() {
632        use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
633        use bevy_app::App;
634        use bevy_reflect::FromReflect;
635
636        #[derive(Reflect)]
637        struct MyAsset {
638            value: u32,
639        }
640        impl Asset for MyAsset {}
641        impl VisitAssetDependencies for MyAsset {
642            fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
643        }
644
645        let mut app = App::new();
646        app.add_plugins(AssetPlugin::default())
647            .init_asset::<MyAsset>();
648        let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
649
650        let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
651        match &handle {
652            Handle::Strong(strong) => {
653                assert_eq!(
654                    Arc::strong_count(strong),
655                    1,
656                    "Inserting the asset should result in a strong count of 1"
657                );
658
659                let reflected: &dyn Reflect = &handle;
660                let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
661
662                assert_eq!(
663                    Arc::strong_count(strong),
664                    2,
665                    "Cloning the handle with reflect should increase the strong count to 2"
666                );
667
668                let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
669
670                assert_eq!(
671                    Arc::strong_count(strong),
672                    3,
673                    "Converting the handle to a dynamic should increase the strong count to 3"
674                );
675
676                let from_reflect_handle: Handle<MyAsset> =
677                    FromReflect::from_reflect(&*dynamic_handle).unwrap();
678
679                assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
680                assert!(
681                    from_reflect_handle.is_strong(),
682                    "The cloned handle should still be strong"
683                );
684            }
685            _ => panic!("Expected a strong handle"),
686        }
687    }
688}