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(
154        since = "0.16.0",
155        note = "use the `weak_handle!` macro with a UUID string instead"
156    )]
157    pub const fn weak_from_u128(value: u128) -> Self {
158        Handle::Weak(AssetId::Uuid {
159            uuid: Uuid::from_u128(value),
160        })
161    }
162
163    /// Returns the [`AssetId`] of this [`Asset`].
164    #[inline]
165    pub fn id(&self) -> AssetId<A> {
166        match self {
167            Handle::Strong(handle) => handle.id.typed_unchecked(),
168            Handle::Weak(id) => *id,
169        }
170    }
171
172    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
173    #[inline]
174    pub fn path(&self) -> Option<&AssetPath<'static>> {
175        match self {
176            Handle::Strong(handle) => handle.path.as_ref(),
177            Handle::Weak(_) => None,
178        }
179    }
180
181    /// Returns `true` if this is a weak handle.
182    #[inline]
183    pub fn is_weak(&self) -> bool {
184        matches!(self, Handle::Weak(_))
185    }
186
187    /// Returns `true` if this is a strong handle.
188    #[inline]
189    pub fn is_strong(&self) -> bool {
190        matches!(self, Handle::Strong(_))
191    }
192
193    /// Creates a [`Handle::Weak`] clone of this [`Handle`], which will not keep the referenced [`Asset`] alive.
194    #[inline]
195    pub fn clone_weak(&self) -> Self {
196        match self {
197            Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
198            Handle::Weak(id) => Handle::Weak(*id),
199        }
200    }
201
202    /// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
203    /// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for
204    /// [`Handle::Weak`].
205    #[inline]
206    pub fn untyped(self) -> UntypedHandle {
207        self.into()
208    }
209}
210
211impl<A: Asset> Default for Handle<A> {
212    fn default() -> Self {
213        Handle::Weak(AssetId::default())
214    }
215}
216
217impl<A: Asset> core::fmt::Debug for Handle<A> {
218    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219        let name = ShortName::of::<A>();
220        match self {
221            Handle::Strong(handle) => {
222                write!(
223                    f,
224                    "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
225                    handle.id.internal(),
226                    handle.path
227                )
228            }
229            Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
230        }
231    }
232}
233
234impl<A: Asset> Hash for Handle<A> {
235    #[inline]
236    fn hash<H: Hasher>(&self, state: &mut H) {
237        self.id().hash(state);
238    }
239}
240
241impl<A: Asset> PartialOrd for Handle<A> {
242    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
243        Some(self.cmp(other))
244    }
245}
246
247impl<A: Asset> Ord for Handle<A> {
248    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
249        self.id().cmp(&other.id())
250    }
251}
252
253impl<A: Asset> PartialEq for Handle<A> {
254    #[inline]
255    fn eq(&self, other: &Self) -> bool {
256        self.id() == other.id()
257    }
258}
259
260impl<A: Asset> Eq for Handle<A> {}
261
262impl<A: Asset> From<&Handle<A>> for AssetId<A> {
263    #[inline]
264    fn from(value: &Handle<A>) -> Self {
265        value.id()
266    }
267}
268
269impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
270    #[inline]
271    fn from(value: &Handle<A>) -> Self {
272        value.id().into()
273    }
274}
275
276impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
277    #[inline]
278    fn from(value: &mut Handle<A>) -> Self {
279        value.id()
280    }
281}
282
283impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
284    #[inline]
285    fn from(value: &mut Handle<A>) -> Self {
286        value.id().into()
287    }
288}
289
290/// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime
291/// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types
292/// to be stored together and compared.
293///
294/// See [`Handle`] for more information.
295#[derive(Clone)]
296pub enum UntypedHandle {
297    /// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped.
298    Strong(Arc<StrongHandle>),
299    /// A weak handle, which does not keep the referenced [`Asset`] alive.
300    Weak(UntypedAssetId),
301}
302
303impl UntypedHandle {
304    /// Returns the [`UntypedAssetId`] for the referenced asset.
305    #[inline]
306    pub fn id(&self) -> UntypedAssetId {
307        match self {
308            UntypedHandle::Strong(handle) => handle.id,
309            UntypedHandle::Weak(id) => *id,
310        }
311    }
312
313    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
314    #[inline]
315    pub fn path(&self) -> Option<&AssetPath<'static>> {
316        match self {
317            UntypedHandle::Strong(handle) => handle.path.as_ref(),
318            UntypedHandle::Weak(_) => None,
319        }
320    }
321
322    /// Creates an [`UntypedHandle::Weak`] clone of this [`UntypedHandle`], which will not keep the referenced [`Asset`] alive.
323    #[inline]
324    pub fn clone_weak(&self) -> UntypedHandle {
325        match self {
326            UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
327            UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
328        }
329    }
330
331    /// Returns the [`TypeId`] of the referenced [`Asset`].
332    #[inline]
333    pub fn type_id(&self) -> TypeId {
334        match self {
335            UntypedHandle::Strong(handle) => handle.id.type_id(),
336            UntypedHandle::Weak(id) => id.type_id(),
337        }
338    }
339
340    /// Converts to a typed Handle. This _will not check if the target Handle type matches_.
341    #[inline]
342    pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
343        match self {
344            UntypedHandle::Strong(handle) => Handle::Strong(handle),
345            UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
346        }
347    }
348
349    /// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it
350    ///  _will not check if the target Handle type matches in release builds_. Use this as an optimization
351    /// when you want some degree of validation at dev-time, but you are also very certain that the type
352    /// actually matches.
353    #[inline]
354    pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
355        debug_assert_eq!(
356            self.type_id(),
357            TypeId::of::<A>(),
358            "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
359        );
360        match self {
361            UntypedHandle::Strong(handle) => Handle::Strong(handle),
362            UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
363        }
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 typed<A: Asset>(self) -> Handle<A> {
369        let Ok(handle) = self.try_typed() else {
370            panic!(
371                "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
372                core::any::type_name::<A>()
373            )
374        };
375
376        handle
377    }
378
379    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
380    #[inline]
381    pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
382        Handle::try_from(self)
383    }
384
385    /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform
386    /// associated with it.
387    #[inline]
388    pub fn meta_transform(&self) -> Option<&MetaTransform> {
389        match self {
390            UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
391            UntypedHandle::Weak(_) => None,
392        }
393    }
394}
395
396impl PartialEq for UntypedHandle {
397    #[inline]
398    fn eq(&self, other: &Self) -> bool {
399        self.id() == other.id() && self.type_id() == other.type_id()
400    }
401}
402
403impl Eq for UntypedHandle {}
404
405impl Hash for UntypedHandle {
406    #[inline]
407    fn hash<H: Hasher>(&self, state: &mut H) {
408        self.id().hash(state);
409    }
410}
411
412impl core::fmt::Debug for UntypedHandle {
413    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
414        match self {
415            UntypedHandle::Strong(handle) => {
416                write!(
417                    f,
418                    "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
419                    handle.id.type_id(),
420                    handle.id.internal(),
421                    handle.path
422                )
423            }
424            UntypedHandle::Weak(id) => write!(
425                f,
426                "WeakHandle{{ type_id: {:?}, id: {:?} }}",
427                id.type_id(),
428                id.internal()
429            ),
430        }
431    }
432}
433
434impl PartialOrd for UntypedHandle {
435    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
436        if self.type_id() == other.type_id() {
437            self.id().partial_cmp(&other.id())
438        } else {
439            None
440        }
441    }
442}
443
444impl From<&UntypedHandle> for UntypedAssetId {
445    #[inline]
446    fn from(value: &UntypedHandle) -> Self {
447        value.id()
448    }
449}
450
451// Cross Operations
452
453impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
454    #[inline]
455    fn eq(&self, other: &UntypedHandle) -> bool {
456        TypeId::of::<A>() == other.type_id() && self.id() == other.id()
457    }
458}
459
460impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
461    #[inline]
462    fn eq(&self, other: &Handle<A>) -> bool {
463        other.eq(self)
464    }
465}
466
467impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
468    #[inline]
469    fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
470        if TypeId::of::<A>() != other.type_id() {
471            None
472        } else {
473            self.id().partial_cmp(&other.id())
474        }
475    }
476}
477
478impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
479    #[inline]
480    fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
481        Some(other.partial_cmp(self)?.reverse())
482    }
483}
484
485impl<A: Asset> From<Handle<A>> for UntypedHandle {
486    fn from(value: Handle<A>) -> Self {
487        match value {
488            Handle::Strong(handle) => UntypedHandle::Strong(handle),
489            Handle::Weak(id) => UntypedHandle::Weak(id.into()),
490        }
491    }
492}
493
494impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
495    type Error = UntypedAssetConversionError;
496
497    fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
498        let found = value.type_id();
499        let expected = TypeId::of::<A>();
500
501        if found != expected {
502            return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
503        }
504
505        match value {
506            UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
507            UntypedHandle::Weak(id) => {
508                let Ok(id) = id.try_into() else {
509                    return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
510                };
511                Ok(Handle::Weak(id))
512            }
513        }
514    }
515}
516
517/// Creates a weak [`Handle`] from a string literal containing a UUID.
518///
519/// # Examples
520///
521/// ```
522/// # use bevy_asset::{Handle, weak_handle};
523/// # type Shader = ();
524/// const SHADER: Handle<Shader> = weak_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
525/// ```
526#[macro_export]
527macro_rules! weak_handle {
528    ($uuid:expr) => {{
529        $crate::Handle::Weak($crate::AssetId::Uuid {
530            uuid: $crate::uuid::uuid!($uuid),
531        })
532    }};
533}
534
535/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
536#[derive(Error, Debug, PartialEq, Clone)]
537#[non_exhaustive]
538pub enum UntypedAssetConversionError {
539    /// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type.
540    #[error(
541        "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
542    )]
543    TypeIdMismatch {
544        /// The expected [`TypeId`] of the [`Handle`] being converted to.
545        expected: TypeId,
546        /// The [`TypeId`] of the [`UntypedHandle`] being converted from.
547        found: TypeId,
548    },
549}
550
551#[cfg(test)]
552mod tests {
553    use alloc::boxed::Box;
554    use bevy_platform::hash::FixedHasher;
555    use bevy_reflect::PartialReflect;
556    use core::hash::BuildHasher;
557
558    use super::*;
559
560    type TestAsset = ();
561
562    const UUID_1: Uuid = Uuid::from_u128(123);
563    const UUID_2: Uuid = Uuid::from_u128(456);
564
565    /// Simple utility to directly hash a value using a fixed hasher
566    fn hash<T: Hash>(data: &T) -> u64 {
567        FixedHasher.hash_one(data)
568    }
569
570    /// Typed and Untyped `Handles` should be equivalent to each other and themselves
571    #[test]
572    fn equality() {
573        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
574        let untyped = UntypedAssetId::Uuid {
575            type_id: TypeId::of::<TestAsset>(),
576            uuid: UUID_1,
577        };
578
579        let typed = Handle::Weak(typed);
580        let untyped = UntypedHandle::Weak(untyped);
581
582        assert_eq!(
583            Ok(typed.clone()),
584            Handle::<TestAsset>::try_from(untyped.clone())
585        );
586        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
587        assert_eq!(typed, untyped);
588    }
589
590    /// Typed and Untyped `Handles` should be orderable amongst each other and themselves
591    #[test]
592    #[expect(
593        clippy::cmp_owned,
594        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."
595    )]
596    fn ordering() {
597        assert!(UUID_1 < UUID_2);
598
599        let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
600        let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
601        let untyped_1 = UntypedAssetId::Uuid {
602            type_id: TypeId::of::<TestAsset>(),
603            uuid: UUID_1,
604        };
605        let untyped_2 = UntypedAssetId::Uuid {
606            type_id: TypeId::of::<TestAsset>(),
607            uuid: UUID_2,
608        };
609
610        let typed_1 = Handle::Weak(typed_1);
611        let typed_2 = Handle::Weak(typed_2);
612        let untyped_1 = UntypedHandle::Weak(untyped_1);
613        let untyped_2 = UntypedHandle::Weak(untyped_2);
614
615        assert!(typed_1 < typed_2);
616        assert!(untyped_1 < untyped_2);
617
618        assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
619        assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
620
621        assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
622        assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
623
624        assert!(typed_1 < untyped_2);
625        assert!(untyped_1 < typed_2);
626    }
627
628    /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
629    #[test]
630    fn hashing() {
631        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
632        let untyped = UntypedAssetId::Uuid {
633            type_id: TypeId::of::<TestAsset>(),
634            uuid: UUID_1,
635        };
636
637        let typed = Handle::Weak(typed);
638        let untyped = UntypedHandle::Weak(untyped);
639
640        assert_eq!(
641            hash(&typed),
642            hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
643        );
644        assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
645        assert_eq!(hash(&typed), hash(&untyped));
646    }
647
648    /// Typed and Untyped `Handles` should be interchangeable
649    #[test]
650    fn conversion() {
651        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
652        let untyped = UntypedAssetId::Uuid {
653            type_id: TypeId::of::<TestAsset>(),
654            uuid: UUID_1,
655        };
656
657        let typed = Handle::Weak(typed);
658        let untyped = UntypedHandle::Weak(untyped);
659
660        assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
661        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
662    }
663
664    /// `PartialReflect::reflect_clone`/`PartialReflect::to_dynamic` should increase the strong count of a strong handle
665    #[test]
666    fn strong_handle_reflect_clone() {
667        use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
668        use bevy_app::App;
669        use bevy_reflect::FromReflect;
670
671        #[derive(Reflect)]
672        struct MyAsset {
673            value: u32,
674        }
675        impl Asset for MyAsset {}
676        impl VisitAssetDependencies for MyAsset {
677            fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
678        }
679
680        let mut app = App::new();
681        app.add_plugins(AssetPlugin::default())
682            .init_asset::<MyAsset>();
683        let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
684
685        let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
686        match &handle {
687            Handle::Strong(strong) => {
688                assert_eq!(
689                    Arc::strong_count(strong),
690                    1,
691                    "Inserting the asset should result in a strong count of 1"
692                );
693
694                let reflected: &dyn Reflect = &handle;
695                let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
696
697                assert_eq!(
698                    Arc::strong_count(strong),
699                    2,
700                    "Cloning the handle with reflect should increase the strong count to 2"
701                );
702
703                let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
704
705                assert_eq!(
706                    Arc::strong_count(strong),
707                    3,
708                    "Converting the handle to a dynamic should increase the strong count to 3"
709                );
710
711                let from_reflect_handle: Handle<MyAsset> =
712                    FromReflect::from_reflect(&*dynamic_handle).unwrap();
713
714                assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
715                assert!(
716                    from_reflect_handle.is_strong(),
717                    "The cloned handle should still be strong"
718                );
719            }
720            _ => panic!("Expected a strong handle"),
721        }
722    }
723}