use crate::{Asset, AssetIndex, Handle, UntypedHandle}; use bevy_reflect::{Reflect, Uuid}; use std::{ any::TypeId, fmt::{Debug, Display}, hash::Hash, marker::PhantomData, }; /// A unique runtime-only identifier for an [`Asset`]. This is cheap to [`Copy`]/[`Clone`] and is not directly tied to the /// lifetime of the Asset. This means it _can_ point to an [`Asset`] that no longer exists. /// /// For an identifier tied to the lifetime of an asset, see [`Handle`]. /// /// For an "untyped" / "generic-less" id, see [`UntypedAssetId`]. #[derive(Reflect)] pub enum AssetId { /// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is /// the "default" identifier used for assets. The alternative(s) (ex: [`AssetId::Uuid`]) will only be used if assets are /// explicitly registered that way. /// /// [`Assets`]: crate::Assets Index { index: AssetIndex, #[reflect(ignore)] marker: PhantomData A>, }, /// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`] /// with one. /// /// [`Assets`]: crate::Assets Uuid { uuid: Uuid }, } impl AssetId { /// The uuid for the default [`AssetId`]. It is valid to assign a value to this in [`Assets`](crate::Assets) /// and by convention (where appropriate) assets should support this pattern. pub const DEFAULT_UUID: Uuid = Uuid::from_u128(200809721996911295814598172825939264631); /// This asset id _should_ never be valid. Assigning a value to this in [`Assets`](crate::Assets) will /// produce undefined behavior, so don't do it! pub const INVALID_UUID: Uuid = Uuid::from_u128(108428345662029828789348721013522787528); /// Returns an [`AssetId`] with [`Self::INVALID_UUID`], which _should_ never be assigned to. #[inline] pub const fn invalid() -> Self { Self::Uuid { uuid: Self::INVALID_UUID, } } /// Converts this to an "untyped" / "generic-less" [`Asset`] identifier that stores the type information /// _inside_ the [`UntypedAssetId`]. #[inline] pub fn untyped(self) -> UntypedAssetId { match self { AssetId::Index { index, .. } => UntypedAssetId::Index { index, type_id: TypeId::of::(), }, AssetId::Uuid { uuid } => UntypedAssetId::Uuid { uuid, type_id: TypeId::of::(), }, } } #[inline] pub(crate) fn internal(self) -> InternalAssetId { match self { AssetId::Index { index, .. } => InternalAssetId::Index(index), AssetId::Uuid { uuid } => InternalAssetId::Uuid(uuid), } } } impl Default for AssetId { fn default() -> Self { AssetId::Uuid { uuid: Self::DEFAULT_UUID, } } } impl Clone for AssetId { fn clone(&self) -> Self { *self } } impl Copy for AssetId {} impl Display for AssetId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Debug::fmt(self, f) } } impl Debug for AssetId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AssetId::Index { index, .. } => { write!( f, "AssetId<{}>{{ index: {}, generation: {}}}", std::any::type_name::(), index.index, index.generation ) } AssetId::Uuid { uuid } => { write!( f, "AssetId<{}>{{uuid: {}}}", std::any::type_name::(), uuid ) } } } } impl Hash for AssetId { #[inline] fn hash(&self, state: &mut H) { self.internal().hash(state); } } impl PartialEq for AssetId { #[inline] fn eq(&self, other: &Self) -> bool { self.internal().eq(&other.internal()) } } impl Eq for AssetId {} impl PartialOrd for AssetId { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for AssetId { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.internal().cmp(&other.internal()) } } impl From for AssetId { #[inline] fn from(value: AssetIndex) -> Self { Self::Index { index: value, marker: PhantomData, } } } impl From for AssetId { #[inline] fn from(value: Uuid) -> Self { Self::Uuid { uuid: value } } } impl From> for AssetId { #[inline] fn from(value: Handle) -> Self { value.id() } } impl From<&Handle> for AssetId { #[inline] fn from(value: &Handle) -> Self { value.id() } } impl From for AssetId { #[inline] fn from(value: UntypedHandle) -> Self { value.id().typed() } } impl From<&UntypedHandle> for AssetId { #[inline] fn from(value: &UntypedHandle) -> Self { value.id().typed() } } impl From for AssetId { #[inline] fn from(value: UntypedAssetId) -> Self { value.typed() } } impl From<&UntypedAssetId> for AssetId { #[inline] fn from(value: &UntypedAssetId) -> Self { value.typed() } } /// An "untyped" / "generic-less" [`Asset`] identifier that behaves much like [`AssetId`], but stores the [`Asset`] type /// information at runtime instead of compile-time. This increases the size of the type, but it enables storing asset ids /// across asset types together and enables comparisons between them. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum UntypedAssetId { /// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is /// the "default" identifier used for assets. The alternative(s) (ex: [`UntypedAssetId::Uuid`]) will only be used if assets are /// explicitly registered that way. /// /// [`Assets`]: crate::Assets Index { type_id: TypeId, index: AssetIndex }, /// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`] /// with one. /// /// [`Assets`]: crate::Assets Uuid { type_id: TypeId, uuid: Uuid }, } impl UntypedAssetId { /// Converts this to a "typed" [`AssetId`] without checking the stored type to see if it matches the target `A` [`Asset`] type. /// This should only be called if you are _absolutely certain_ the asset type matches the stored type. And even then, you should /// consider using [`UntypedAssetId::typed_debug_checked`] instead. #[inline] pub fn typed_unchecked(self) -> AssetId { match self { UntypedAssetId::Index { index, .. } => AssetId::Index { index, marker: PhantomData, }, UntypedAssetId::Uuid { uuid, .. } => AssetId::Uuid { uuid }, } } /// Converts this to a "typed" [`AssetId`]. When compiled in debug-mode it will check to see if the stored type /// matches the target `A` [`Asset`] type. When compiled in release-mode, this check will be skipped. /// /// # Panics /// /// Panics if compiled in debug mode and the [`TypeId`] of `A` does not match the stored [`TypeId`]. #[inline] pub fn typed_debug_checked(self) -> AssetId { debug_assert_eq!( self.type_id(), TypeId::of::(), "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId", std::any::type_name::() ); self.typed_unchecked() } /// Converts this to a "typed" [`AssetId`]. /// /// # Panics /// /// Panics if the [`TypeId`] of `A` does not match the stored type id. #[inline] pub fn typed(self) -> AssetId { assert_eq!( self.type_id(), TypeId::of::(), "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId", std::any::type_name::() ); self.typed_unchecked() } /// Returns the stored [`TypeId`] of the referenced [`Asset`]. #[inline] pub fn type_id(&self) -> TypeId { match self { UntypedAssetId::Index { type_id, .. } | UntypedAssetId::Uuid { type_id, .. } => { *type_id } } } #[inline] pub(crate) fn internal(self) -> InternalAssetId { match self { UntypedAssetId::Index { index, .. } => InternalAssetId::Index(index), UntypedAssetId::Uuid { uuid, .. } => InternalAssetId::Uuid(uuid), } } } impl Display for UntypedAssetId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut writer = f.debug_struct("UntypedAssetId"); match self { UntypedAssetId::Index { index, type_id } => { writer .field("type_id", type_id) .field("index", &index.index) .field("generation", &index.generation); } UntypedAssetId::Uuid { uuid, type_id } => { writer.field("type_id", type_id).field("uuid", uuid); } } writer.finish() } } impl From> for UntypedAssetId { #[inline] fn from(value: AssetId) -> Self { value.untyped() } } impl From> for UntypedAssetId { #[inline] fn from(value: Handle) -> Self { value.id().untyped() } } impl From<&Handle> for UntypedAssetId { #[inline] fn from(value: &Handle) -> Self { value.id().untyped() } } /// An asset id without static or dynamic types associated with it. /// This exist to support efficient type erased id drop tracking. We /// could use [`UntypedAssetId`] for this, but the [`TypeId`] is unnecessary. /// /// Do not _ever_ use this across asset types for comparison. /// [`InternalAssetId`] contains no type information and will happily collide /// with indices across types. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] pub(crate) enum InternalAssetId { Index(AssetIndex), Uuid(Uuid), } impl InternalAssetId { #[inline] pub(crate) fn typed(self) -> AssetId { match self { InternalAssetId::Index(index) => AssetId::Index { index, marker: PhantomData, }, InternalAssetId::Uuid(uuid) => AssetId::Uuid { uuid }, } } #[inline] pub(crate) fn untyped(self, type_id: TypeId) -> UntypedAssetId { match self { InternalAssetId::Index(index) => UntypedAssetId::Index { index, type_id }, InternalAssetId::Uuid(uuid) => UntypedAssetId::Uuid { uuid, type_id }, } } } impl From for InternalAssetId { fn from(value: AssetIndex) -> Self { Self::Index(value) } } impl From for InternalAssetId { fn from(value: Uuid) -> Self { Self::Uuid(value) } }