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