bevy_asset/
handle.rs

1use crate::{
2    meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId,
3    UntypedAssetId,
4};
5use bevy_ecs::prelude::*;
6use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
7use bevy_utils::get_short_name;
8use crossbeam_channel::{Receiver, Sender};
9use std::{
10    any::TypeId,
11    hash::{Hash, Hasher},
12    sync::Arc,
13};
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 std::fmt::Debug for StrongHandle {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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 strong or weak handle to a specific [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
118/// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
119/// nor will it keep assets alive.
120///
121/// [`Handle`] can be cloned. If a [`Handle::Strong`] is cloned, the referenced [`Asset`] will not be freed until _all_ instances
122/// of the [`Handle`] are dropped.
123///
124/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
125#[derive(Component, Reflect)]
126#[reflect(Default, Component, Debug, Hash, PartialEq)]
127pub enum Handle<A: Asset> {
128    /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
129    /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
130    Strong(Arc<StrongHandle>),
131    /// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
132    /// nor will it keep assets alive.
133    Weak(AssetId<A>),
134}
135
136impl<T: Asset> Clone for Handle<T> {
137    fn clone(&self) -> Self {
138        match self {
139            Handle::Strong(handle) => Handle::Strong(handle.clone()),
140            Handle::Weak(id) => Handle::Weak(*id),
141        }
142    }
143}
144
145impl<A: Asset> Handle<A> {
146    /// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`].
147    pub const fn weak_from_u128(value: u128) -> Self {
148        Handle::Weak(AssetId::Uuid {
149            uuid: Uuid::from_u128(value),
150        })
151    }
152
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::Weak(id) => *id,
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::Weak(_) => None,
168        }
169    }
170
171    /// Returns `true` if this is a weak handle.
172    #[inline]
173    pub fn is_weak(&self) -> bool {
174        matches!(self, Handle::Weak(_))
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    /// Creates a [`Handle::Weak`] clone of this [`Handle`], which will not keep the referenced [`Asset`] alive.
184    #[inline]
185    pub fn clone_weak(&self) -> Self {
186        match self {
187            Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
188            Handle::Weak(id) => Handle::Weak(*id),
189        }
190    }
191
192    /// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
193    /// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for
194    /// [`Handle::Weak`].
195    #[inline]
196    pub fn untyped(self) -> UntypedHandle {
197        self.into()
198    }
199}
200
201impl<A: Asset> Default for Handle<A> {
202    fn default() -> Self {
203        Handle::Weak(AssetId::default())
204    }
205}
206
207impl<A: Asset> std::fmt::Debug for Handle<A> {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        let name = get_short_name(std::any::type_name::<A>());
210        match self {
211            Handle::Strong(handle) => {
212                write!(
213                    f,
214                    "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
215                    handle.id.internal(),
216                    handle.path
217                )
218            }
219            Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
220        }
221    }
222}
223
224impl<A: Asset> Hash for Handle<A> {
225    #[inline]
226    fn hash<H: Hasher>(&self, state: &mut H) {
227        self.id().hash(state);
228    }
229}
230
231impl<A: Asset> PartialOrd for Handle<A> {
232    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
233        Some(self.cmp(other))
234    }
235}
236
237impl<A: Asset> Ord for Handle<A> {
238    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
239        self.id().cmp(&other.id())
240    }
241}
242
243impl<A: Asset> PartialEq for Handle<A> {
244    #[inline]
245    fn eq(&self, other: &Self) -> bool {
246        self.id() == other.id()
247    }
248}
249
250impl<A: Asset> Eq for Handle<A> {}
251
252impl<A: Asset> From<&Handle<A>> for AssetId<A> {
253    #[inline]
254    fn from(value: &Handle<A>) -> Self {
255        value.id()
256    }
257}
258
259impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
260    #[inline]
261    fn from(value: &Handle<A>) -> Self {
262        value.id().into()
263    }
264}
265
266impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
267    #[inline]
268    fn from(value: &mut Handle<A>) -> Self {
269        value.id()
270    }
271}
272
273impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
274    #[inline]
275    fn from(value: &mut Handle<A>) -> Self {
276        value.id().into()
277    }
278}
279
280/// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime
281/// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types
282/// to be stored together and compared.
283///
284/// See [`Handle`] for more information.
285#[derive(Clone)]
286pub enum UntypedHandle {
287    Strong(Arc<StrongHandle>),
288    Weak(UntypedAssetId),
289}
290
291impl UntypedHandle {
292    /// Returns the [`UntypedAssetId`] for the referenced asset.
293    #[inline]
294    pub fn id(&self) -> UntypedAssetId {
295        match self {
296            UntypedHandle::Strong(handle) => handle.id,
297            UntypedHandle::Weak(id) => *id,
298        }
299    }
300
301    /// Returns the path if this is (1) a strong handle and (2) the asset has a path
302    #[inline]
303    pub fn path(&self) -> Option<&AssetPath<'static>> {
304        match self {
305            UntypedHandle::Strong(handle) => handle.path.as_ref(),
306            UntypedHandle::Weak(_) => None,
307        }
308    }
309
310    /// Creates an [`UntypedHandle::Weak`] clone of this [`UntypedHandle`], which will not keep the referenced [`Asset`] alive.
311    #[inline]
312    pub fn clone_weak(&self) -> UntypedHandle {
313        match self {
314            UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
315            UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
316        }
317    }
318
319    /// Returns the [`TypeId`] of the referenced [`Asset`].
320    #[inline]
321    pub fn type_id(&self) -> TypeId {
322        match self {
323            UntypedHandle::Strong(handle) => handle.id.type_id(),
324            UntypedHandle::Weak(id) => id.type_id(),
325        }
326    }
327
328    /// Converts to a typed Handle. This _will not check if the target Handle type matches_.
329    #[inline]
330    pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
331        match self {
332            UntypedHandle::Strong(handle) => Handle::Strong(handle),
333            UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
334        }
335    }
336
337    /// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it
338    ///  _will not check if the target Handle type matches in release builds_. Use this as an optimization
339    /// when you want some degree of validation at dev-time, but you are also very certain that the type
340    /// actually matches.
341    #[inline]
342    pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
343        debug_assert_eq!(
344            self.type_id(),
345            TypeId::of::<A>(),
346            "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
347        );
348        match self {
349            UntypedHandle::Strong(handle) => Handle::Strong(handle),
350            UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
351        }
352    }
353
354    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
355    #[inline]
356    pub fn typed<A: Asset>(self) -> Handle<A> {
357        let Ok(handle) = self.try_typed() else {
358            panic!(
359                "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
360                std::any::type_name::<A>()
361            )
362        };
363
364        handle
365    }
366
367    /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
368    #[inline]
369    pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
370        Handle::try_from(self)
371    }
372
373    /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform
374    /// associated with it.
375    #[inline]
376    pub fn meta_transform(&self) -> Option<&MetaTransform> {
377        match self {
378            UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
379            UntypedHandle::Weak(_) => None,
380        }
381    }
382}
383
384impl PartialEq for UntypedHandle {
385    #[inline]
386    fn eq(&self, other: &Self) -> bool {
387        self.id() == other.id() && self.type_id() == other.type_id()
388    }
389}
390
391impl Eq for UntypedHandle {}
392
393impl Hash for UntypedHandle {
394    #[inline]
395    fn hash<H: Hasher>(&self, state: &mut H) {
396        self.id().hash(state);
397    }
398}
399
400impl std::fmt::Debug for UntypedHandle {
401    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402        match self {
403            UntypedHandle::Strong(handle) => {
404                write!(
405                    f,
406                    "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
407                    handle.id.type_id(),
408                    handle.id.internal(),
409                    handle.path
410                )
411            }
412            UntypedHandle::Weak(id) => write!(
413                f,
414                "WeakHandle{{ type_id: {:?}, id: {:?} }}",
415                id.type_id(),
416                id.internal()
417            ),
418        }
419    }
420}
421
422impl PartialOrd for UntypedHandle {
423    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
424        if self.type_id() == other.type_id() {
425            self.id().partial_cmp(&other.id())
426        } else {
427            None
428        }
429    }
430}
431
432impl From<&UntypedHandle> for UntypedAssetId {
433    #[inline]
434    fn from(value: &UntypedHandle) -> Self {
435        value.id()
436    }
437}
438
439// Cross Operations
440
441impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
442    #[inline]
443    fn eq(&self, other: &UntypedHandle) -> bool {
444        TypeId::of::<A>() == other.type_id() && self.id() == other.id()
445    }
446}
447
448impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
449    #[inline]
450    fn eq(&self, other: &Handle<A>) -> bool {
451        other.eq(self)
452    }
453}
454
455impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
456    #[inline]
457    fn partial_cmp(&self, other: &UntypedHandle) -> Option<std::cmp::Ordering> {
458        if TypeId::of::<A>() != other.type_id() {
459            None
460        } else {
461            self.id().partial_cmp(&other.id())
462        }
463    }
464}
465
466impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
467    #[inline]
468    fn partial_cmp(&self, other: &Handle<A>) -> Option<std::cmp::Ordering> {
469        Some(other.partial_cmp(self)?.reverse())
470    }
471}
472
473impl<A: Asset> From<Handle<A>> for UntypedHandle {
474    fn from(value: Handle<A>) -> Self {
475        match value {
476            Handle::Strong(handle) => UntypedHandle::Strong(handle),
477            Handle::Weak(id) => UntypedHandle::Weak(id.into()),
478        }
479    }
480}
481
482impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
483    type Error = UntypedAssetConversionError;
484
485    fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
486        let found = value.type_id();
487        let expected = TypeId::of::<A>();
488
489        if found != expected {
490            return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
491        }
492
493        match value {
494            UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
495            UntypedHandle::Weak(id) => {
496                let Ok(id) = id.try_into() else {
497                    return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
498                };
499                Ok(Handle::Weak(id))
500            }
501        }
502    }
503}
504
505/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
506#[derive(Error, Debug, PartialEq, Clone)]
507#[non_exhaustive]
508pub enum UntypedAssetConversionError {
509    /// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type.
510    #[error(
511        "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
512    )]
513    TypeIdMismatch { expected: TypeId, found: TypeId },
514}
515
516#[cfg(test)]
517mod tests {
518    use super::*;
519
520    type TestAsset = ();
521
522    const UUID_1: Uuid = Uuid::from_u128(123);
523    const UUID_2: Uuid = Uuid::from_u128(456);
524
525    /// Simple utility to directly hash a value using a fixed hasher
526    fn hash<T: Hash>(data: &T) -> u64 {
527        let mut hasher = bevy_utils::AHasher::default();
528        data.hash(&mut hasher);
529        hasher.finish()
530    }
531
532    /// Typed and Untyped `Handles` should be equivalent to each other and themselves
533    #[test]
534    fn equality() {
535        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
536        let untyped = UntypedAssetId::Uuid {
537            type_id: TypeId::of::<TestAsset>(),
538            uuid: UUID_1,
539        };
540
541        let typed = Handle::Weak(typed);
542        let untyped = UntypedHandle::Weak(untyped);
543
544        assert_eq!(
545            Ok(typed.clone()),
546            Handle::<TestAsset>::try_from(untyped.clone())
547        );
548        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
549        assert_eq!(typed, untyped);
550    }
551
552    /// Typed and Untyped `Handles` should be orderable amongst each other and themselves
553    #[allow(clippy::cmp_owned)]
554    #[test]
555    fn ordering() {
556        assert!(UUID_1 < UUID_2);
557
558        let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
559        let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
560        let untyped_1 = UntypedAssetId::Uuid {
561            type_id: TypeId::of::<TestAsset>(),
562            uuid: UUID_1,
563        };
564        let untyped_2 = UntypedAssetId::Uuid {
565            type_id: TypeId::of::<TestAsset>(),
566            uuid: UUID_2,
567        };
568
569        let typed_1 = Handle::Weak(typed_1);
570        let typed_2 = Handle::Weak(typed_2);
571        let untyped_1 = UntypedHandle::Weak(untyped_1);
572        let untyped_2 = UntypedHandle::Weak(untyped_2);
573
574        assert!(typed_1 < typed_2);
575        assert!(untyped_1 < untyped_2);
576
577        assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
578        assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
579
580        assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
581        assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
582
583        assert!(typed_1 < untyped_2);
584        assert!(untyped_1 < typed_2);
585    }
586
587    /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
588    #[test]
589    fn hashing() {
590        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
591        let untyped = UntypedAssetId::Uuid {
592            type_id: TypeId::of::<TestAsset>(),
593            uuid: UUID_1,
594        };
595
596        let typed = Handle::Weak(typed);
597        let untyped = UntypedHandle::Weak(untyped);
598
599        assert_eq!(
600            hash(&typed),
601            hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
602        );
603        assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
604        assert_eq!(hash(&typed), hash(&untyped));
605    }
606
607    /// Typed and Untyped `Handles` should be interchangeable
608    #[test]
609    fn conversion() {
610        let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
611        let untyped = UntypedAssetId::Uuid {
612            type_id: TypeId::of::<TestAsset>(),
613            uuid: UUID_1,
614        };
615
616        let typed = Handle::Weak(typed);
617        let untyped = UntypedHandle::Weak(untyped);
618
619        assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
620        assert_eq!(UntypedHandle::from(typed.clone()), untyped);
621    }
622}