use crate::{ meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId, UntypedAssetId, }; use alloc::sync::Arc; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; use core::{ any::TypeId, hash::{Hash, Hasher}, }; use crossbeam_channel::{Receiver, Sender}; use disqualified::ShortName; use thiserror::Error; use uuid::Uuid; /// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_. /// This should _only_ be used for one specific asset type. #[derive(Clone)] pub struct AssetHandleProvider { pub(crate) allocator: Arc, pub(crate) drop_sender: Sender, pub(crate) drop_receiver: Receiver, pub(crate) type_id: TypeId, } #[derive(Debug)] pub(crate) struct DropEvent { pub(crate) id: InternalAssetId, pub(crate) asset_server_managed: bool, } impl AssetHandleProvider { pub(crate) fn new(type_id: TypeId, allocator: Arc) -> Self { let (drop_sender, drop_receiver) = crossbeam_channel::unbounded(); Self { type_id, allocator, drop_sender, drop_receiver, } } /// Reserves a new strong [`UntypedHandle`] (with a new [`UntypedAssetId`]). The stored [`Asset`] [`TypeId`] in the /// [`UntypedHandle`] will match the [`Asset`] [`TypeId`] assigned to this [`AssetHandleProvider`]. pub fn reserve_handle(&self) -> UntypedHandle { let index = self.allocator.reserve(); UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None, None)) } pub(crate) fn get_handle( &self, id: InternalAssetId, asset_server_managed: bool, path: Option>, meta_transform: Option, ) -> Arc { Arc::new(StrongHandle { id: id.untyped(self.type_id), drop_sender: self.drop_sender.clone(), meta_transform, path, asset_server_managed, }) } pub(crate) fn reserve_handle_internal( &self, asset_server_managed: bool, path: Option>, meta_transform: Option, ) -> Arc { let index = self.allocator.reserve(); self.get_handle( InternalAssetId::Index(index), asset_server_managed, path, meta_transform, ) } } /// The internal "strong" [`Asset`] handle storage for [`Handle::Strong`] and [`UntypedHandle::Strong`]. When this is dropped, /// the [`Asset`] will be freed. It also stores some asset metadata for easy access from handles. #[derive(TypePath)] pub struct StrongHandle { pub(crate) id: UntypedAssetId, pub(crate) asset_server_managed: bool, pub(crate) path: Option>, /// Modifies asset meta. This is stored on the handle because it is: /// 1. configuration tied to the lifetime of a specific asset load /// 2. configuration that must be repeatable when the asset is hot-reloaded pub(crate) meta_transform: Option, pub(crate) drop_sender: Sender, } impl Drop for StrongHandle { fn drop(&mut self) { let _ = self.drop_sender.send(DropEvent { id: self.id.internal(), asset_server_managed: self.asset_server_managed, }); } } impl core::fmt::Debug for StrongHandle { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("StrongHandle") .field("id", &self.id) .field("asset_server_managed", &self.asset_server_managed) .field("path", &self.path) .field("drop_sender", &self.drop_sender) .finish() } } /// A strong or weak handle to a specific [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept /// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`], /// nor will it keep assets alive. /// /// [`Handle`] can be cloned. If a [`Handle::Strong`] is cloned, the referenced [`Asset`] will not be freed until _all_ instances /// of the [`Handle`] are dropped. /// /// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists). #[derive(Reflect)] #[reflect(Default, Debug, Hash, PartialEq)] pub enum Handle { /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata. Strong(Arc), /// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`], /// nor will it keep assets alive. Weak(AssetId), } impl Clone for Handle { fn clone(&self) -> Self { match self { Handle::Strong(handle) => Handle::Strong(handle.clone()), Handle::Weak(id) => Handle::Weak(*id), } } } impl Handle { /// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`]. pub const fn weak_from_u128(value: u128) -> Self { Handle::Weak(AssetId::Uuid { uuid: Uuid::from_u128(value), }) } /// Returns the [`AssetId`] of this [`Asset`]. #[inline] pub fn id(&self) -> AssetId { match self { Handle::Strong(handle) => handle.id.typed_unchecked(), Handle::Weak(id) => *id, } } /// Returns the path if this is (1) a strong handle and (2) the asset has a path #[inline] pub fn path(&self) -> Option<&AssetPath<'static>> { match self { Handle::Strong(handle) => handle.path.as_ref(), Handle::Weak(_) => None, } } /// Returns `true` if this is a weak handle. #[inline] pub fn is_weak(&self) -> bool { matches!(self, Handle::Weak(_)) } /// Returns `true` if this is a strong handle. #[inline] pub fn is_strong(&self) -> bool { matches!(self, Handle::Strong(_)) } /// Creates a [`Handle::Weak`] clone of this [`Handle`], which will not keep the referenced [`Asset`] alive. #[inline] pub fn clone_weak(&self) -> Self { match self { Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::()), Handle::Weak(id) => Handle::Weak(*id), } } /// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information /// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for /// [`Handle::Weak`]. #[inline] pub fn untyped(self) -> UntypedHandle { self.into() } } impl Default for Handle { fn default() -> Self { Handle::Weak(AssetId::default()) } } impl core::fmt::Debug for Handle { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let name = ShortName::of::(); match self { Handle::Strong(handle) => { write!( f, "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}", handle.id.internal(), handle.path ) } Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()), } } } impl Hash for Handle { #[inline] fn hash(&self, state: &mut H) { self.id().hash(state); } } impl PartialOrd for Handle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Handle { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.id().cmp(&other.id()) } } impl PartialEq for Handle { #[inline] fn eq(&self, other: &Self) -> bool { self.id() == other.id() } } impl Eq for Handle {} impl From<&Handle> for AssetId { #[inline] fn from(value: &Handle) -> Self { value.id() } } impl From<&Handle> for UntypedAssetId { #[inline] fn from(value: &Handle) -> Self { value.id().into() } } impl From<&mut Handle> for AssetId { #[inline] fn from(value: &mut Handle) -> Self { value.id() } } impl From<&mut Handle> for UntypedAssetId { #[inline] fn from(value: &mut Handle) -> Self { value.id().into() } } /// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime /// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types /// to be stored together and compared. /// /// See [`Handle`] for more information. #[derive(Clone)] pub enum UntypedHandle { Strong(Arc), Weak(UntypedAssetId), } impl UntypedHandle { /// Returns the [`UntypedAssetId`] for the referenced asset. #[inline] pub fn id(&self) -> UntypedAssetId { match self { UntypedHandle::Strong(handle) => handle.id, UntypedHandle::Weak(id) => *id, } } /// Returns the path if this is (1) a strong handle and (2) the asset has a path #[inline] pub fn path(&self) -> Option<&AssetPath<'static>> { match self { UntypedHandle::Strong(handle) => handle.path.as_ref(), UntypedHandle::Weak(_) => None, } } /// Creates an [`UntypedHandle::Weak`] clone of this [`UntypedHandle`], which will not keep the referenced [`Asset`] alive. #[inline] pub fn clone_weak(&self) -> UntypedHandle { match self { UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id), UntypedHandle::Weak(id) => UntypedHandle::Weak(*id), } } /// Returns the [`TypeId`] of the referenced [`Asset`]. #[inline] pub fn type_id(&self) -> TypeId { match self { UntypedHandle::Strong(handle) => handle.id.type_id(), UntypedHandle::Weak(id) => id.type_id(), } } /// Converts to a typed Handle. This _will not check if the target Handle type matches_. #[inline] pub fn typed_unchecked(self) -> Handle { match self { UntypedHandle::Strong(handle) => Handle::Strong(handle), UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::()), } } /// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it /// _will not check if the target Handle type matches in release builds_. Use this as an optimization /// when you want some degree of validation at dev-time, but you are also very certain that the type /// actually matches. #[inline] pub fn typed_debug_checked(self) -> Handle { debug_assert_eq!( self.type_id(), TypeId::of::(), "The target Handle's TypeId does not match the TypeId of this UntypedHandle" ); match self { UntypedHandle::Strong(handle) => Handle::Strong(handle), UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::()), } } /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A` #[inline] pub fn typed(self) -> Handle { let Ok(handle) = self.try_typed() else { panic!( "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle", core::any::type_name::() ) }; handle } /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A` #[inline] pub fn try_typed(self) -> Result, UntypedAssetConversionError> { Handle::try_from(self) } /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform /// associated with it. #[inline] pub fn meta_transform(&self) -> Option<&MetaTransform> { match self { UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(), UntypedHandle::Weak(_) => None, } } } impl PartialEq for UntypedHandle { #[inline] fn eq(&self, other: &Self) -> bool { self.id() == other.id() && self.type_id() == other.type_id() } } impl Eq for UntypedHandle {} impl Hash for UntypedHandle { #[inline] fn hash(&self, state: &mut H) { self.id().hash(state); } } impl core::fmt::Debug for UntypedHandle { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { UntypedHandle::Strong(handle) => { write!( f, "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}", handle.id.type_id(), handle.id.internal(), handle.path ) } UntypedHandle::Weak(id) => write!( f, "WeakHandle{{ type_id: {:?}, id: {:?} }}", id.type_id(), id.internal() ), } } } impl PartialOrd for UntypedHandle { fn partial_cmp(&self, other: &Self) -> Option { if self.type_id() == other.type_id() { self.id().partial_cmp(&other.id()) } else { None } } } impl From<&UntypedHandle> for UntypedAssetId { #[inline] fn from(value: &UntypedHandle) -> Self { value.id() } } // Cross Operations impl PartialEq for Handle { #[inline] fn eq(&self, other: &UntypedHandle) -> bool { TypeId::of::() == other.type_id() && self.id() == other.id() } } impl PartialEq> for UntypedHandle { #[inline] fn eq(&self, other: &Handle) -> bool { other.eq(self) } } impl PartialOrd for Handle { #[inline] fn partial_cmp(&self, other: &UntypedHandle) -> Option { if TypeId::of::() != other.type_id() { None } else { self.id().partial_cmp(&other.id()) } } } impl PartialOrd> for UntypedHandle { #[inline] fn partial_cmp(&self, other: &Handle) -> Option { Some(other.partial_cmp(self)?.reverse()) } } impl From> for UntypedHandle { fn from(value: Handle) -> Self { match value { Handle::Strong(handle) => UntypedHandle::Strong(handle), Handle::Weak(id) => UntypedHandle::Weak(id.into()), } } } impl TryFrom for Handle { type Error = UntypedAssetConversionError; fn try_from(value: UntypedHandle) -> Result { let found = value.type_id(); let expected = TypeId::of::(); if found != expected { return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found }); } match value { UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)), UntypedHandle::Weak(id) => { let Ok(id) = id.try_into() else { return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found }); }; Ok(Handle::Weak(id)) } } } } /// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`]. #[derive(Error, Debug, PartialEq, Clone)] #[non_exhaustive] pub enum UntypedAssetConversionError { /// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type. #[error( "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>" )] TypeIdMismatch { expected: TypeId, found: TypeId }, } #[cfg(test)] mod tests { use bevy_reflect::PartialReflect; use super::*; type TestAsset = (); const UUID_1: Uuid = Uuid::from_u128(123); const UUID_2: Uuid = Uuid::from_u128(456); /// Simple utility to directly hash a value using a fixed hasher fn hash(data: &T) -> u64 { let mut hasher = bevy_utils::AHasher::default(); data.hash(&mut hasher); hasher.finish() } /// Typed and Untyped `Handles` should be equivalent to each other and themselves #[test] fn equality() { let typed = AssetId::::Uuid { uuid: UUID_1 }; let untyped = UntypedAssetId::Uuid { type_id: TypeId::of::(), uuid: UUID_1, }; let typed = Handle::Weak(typed); let untyped = UntypedHandle::Weak(untyped); assert_eq!( Ok(typed.clone()), Handle::::try_from(untyped.clone()) ); assert_eq!(UntypedHandle::from(typed.clone()), untyped); assert_eq!(typed, untyped); } /// Typed and Untyped `Handles` should be orderable amongst each other and themselves #[allow(clippy::cmp_owned)] #[test] fn ordering() { assert!(UUID_1 < UUID_2); let typed_1 = AssetId::::Uuid { uuid: UUID_1 }; let typed_2 = AssetId::::Uuid { uuid: UUID_2 }; let untyped_1 = UntypedAssetId::Uuid { type_id: TypeId::of::(), uuid: UUID_1, }; let untyped_2 = UntypedAssetId::Uuid { type_id: TypeId::of::(), uuid: UUID_2, }; let typed_1 = Handle::Weak(typed_1); let typed_2 = Handle::Weak(typed_2); let untyped_1 = UntypedHandle::Weak(untyped_1); let untyped_2 = UntypedHandle::Weak(untyped_2); assert!(typed_1 < typed_2); assert!(untyped_1 < untyped_2); assert!(UntypedHandle::from(typed_1.clone()) < untyped_2); assert!(untyped_1 < UntypedHandle::from(typed_2.clone())); assert!(Handle::::try_from(untyped_1.clone()).unwrap() < typed_2); assert!(typed_1 < Handle::::try_from(untyped_2.clone()).unwrap()); assert!(typed_1 < untyped_2); assert!(untyped_1 < typed_2); } /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves #[test] fn hashing() { let typed = AssetId::::Uuid { uuid: UUID_1 }; let untyped = UntypedAssetId::Uuid { type_id: TypeId::of::(), uuid: UUID_1, }; let typed = Handle::Weak(typed); let untyped = UntypedHandle::Weak(untyped); assert_eq!( hash(&typed), hash(&Handle::::try_from(untyped.clone()).unwrap()) ); assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped)); assert_eq!(hash(&typed), hash(&untyped)); } /// Typed and Untyped `Handles` should be interchangeable #[test] fn conversion() { let typed = AssetId::::Uuid { uuid: UUID_1 }; let untyped = UntypedAssetId::Uuid { type_id: TypeId::of::(), uuid: UUID_1, }; let typed = Handle::Weak(typed); let untyped = UntypedHandle::Weak(untyped); assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap()); assert_eq!(UntypedHandle::from(typed.clone()), untyped); } /// `Reflect::clone_value` should increase the strong count of a strong handle #[test] fn strong_handle_reflect_clone() { use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies}; use bevy_app::App; use bevy_reflect::FromReflect; #[derive(Reflect)] struct MyAsset { value: u32, } impl Asset for MyAsset {} impl VisitAssetDependencies for MyAsset { fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {} } let mut app = App::new(); app.add_plugins(AssetPlugin::default()) .init_asset::(); let mut assets = app.world_mut().resource_mut::>(); let handle: Handle = assets.add(MyAsset { value: 1 }); match &handle { Handle::Strong(strong) => { assert_eq!( Arc::strong_count(strong), 1, "Inserting the asset should result in a strong count of 1" ); let reflected: &dyn Reflect = &handle; let cloned_handle: Box = reflected.clone_value(); assert_eq!( Arc::strong_count(strong), 2, "Cloning the handle with reflect should increase the strong count to 2" ); let from_reflect_handle: Handle = FromReflect::from_reflect(&*cloned_handle).unwrap(); assert_eq!(Arc::strong_count(strong), 3, "Converting the reflected value back to a handle should increase the strong count to 3"); assert!( from_reflect_handle.is_strong(), "The cloned handle should still be strong" ); } _ => panic!("Expected a strong handle"), } } }