use crate::{
meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId,
UntypedAssetId,
};
use bevy_ecs::prelude::*;
use bevy_reflect::{Reflect, TypePath};
use bevy_utils::{get_short_name, Uuid};
use crossbeam_channel::{Receiver, Sender};
use std::{
any::TypeId,
hash::{Hash, Hasher},
sync::Arc,
};
use thiserror::Error;
#[derive(Clone)]
pub struct AssetHandleProvider {
pub(crate) allocator: Arc<AssetIndexAllocator>,
pub(crate) drop_sender: Sender<DropEvent>,
pub(crate) drop_receiver: Receiver<DropEvent>,
pub(crate) type_id: TypeId,
}
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<AssetIndexAllocator>) -> Self {
let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
Self {
type_id,
allocator,
drop_sender,
drop_receiver,
}
}
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<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
) -> Arc<StrongHandle> {
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<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
) -> Arc<StrongHandle> {
let index = self.allocator.reserve();
self.get_handle(
InternalAssetId::Index(index),
asset_server_managed,
path,
meta_transform,
)
}
}
#[derive(TypePath)]
pub struct StrongHandle {
pub(crate) id: UntypedAssetId,
pub(crate) asset_server_managed: bool,
pub(crate) path: Option<AssetPath<'static>>,
pub(crate) meta_transform: Option<MetaTransform>,
pub(crate) drop_sender: Sender<DropEvent>,
}
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 std::fmt::Debug for StrongHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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()
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
pub enum Handle<A: Asset> {
Strong(Arc<StrongHandle>),
Weak(AssetId<A>),
}
impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
match self {
Handle::Strong(handle) => Handle::Strong(handle.clone()),
Handle::Weak(id) => Handle::Weak(*id),
}
}
}
impl<A: Asset> Handle<A> {
pub const fn weak_from_u128(value: u128) -> Self {
Handle::Weak(AssetId::Uuid {
uuid: Uuid::from_u128(value),
})
}
#[inline]
pub fn id(&self) -> AssetId<A> {
match self {
Handle::Strong(handle) => handle.id.typed_unchecked(),
Handle::Weak(id) => *id,
}
}
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
match self {
Handle::Strong(handle) => handle.path.as_ref(),
Handle::Weak(_) => None,
}
}
#[inline]
pub fn is_weak(&self) -> bool {
matches!(self, Handle::Weak(_))
}
#[inline]
pub fn is_strong(&self) -> bool {
matches!(self, Handle::Strong(_))
}
#[inline]
pub fn clone_weak(&self) -> Self {
match self {
Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
Handle::Weak(id) => Handle::Weak(*id),
}
}
#[inline]
pub fn untyped(self) -> UntypedHandle {
self.into()
}
}
impl<A: Asset> Default for Handle<A> {
fn default() -> Self {
Handle::Weak(AssetId::default())
}
}
impl<A: Asset> std::fmt::Debug for Handle<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = get_short_name(std::any::type_name::<A>());
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<A: Asset> Hash for Handle<A> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl<A: Asset> PartialOrd for Handle<A> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<A: Asset> Ord for Handle<A> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id().cmp(&other.id())
}
}
impl<A: Asset> PartialEq for Handle<A> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl<A: Asset> Eq for Handle<A> {}
impl<A: Asset> From<Handle<A>> for AssetId<A> {
#[inline]
fn from(value: Handle<A>) -> Self {
value.id()
}
}
impl<A: Asset> From<&Handle<A>> for AssetId<A> {
#[inline]
fn from(value: &Handle<A>) -> Self {
value.id()
}
}
impl<A: Asset> From<Handle<A>> for UntypedAssetId {
#[inline]
fn from(value: Handle<A>) -> Self {
value.id().into()
}
}
impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
#[inline]
fn from(value: &Handle<A>) -> Self {
value.id().into()
}
}
#[derive(Clone)]
pub enum UntypedHandle {
Strong(Arc<StrongHandle>),
Weak(UntypedAssetId),
}
impl UntypedHandle {
#[inline]
pub fn id(&self) -> UntypedAssetId {
match self {
UntypedHandle::Strong(handle) => handle.id,
UntypedHandle::Weak(id) => *id,
}
}
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
match self {
UntypedHandle::Strong(handle) => handle.path.as_ref(),
UntypedHandle::Weak(_) => None,
}
}
#[inline]
pub fn clone_weak(&self) -> UntypedHandle {
match self {
UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
}
}
#[inline]
pub fn type_id(&self) -> TypeId {
match self {
UntypedHandle::Strong(handle) => handle.id.type_id(),
UntypedHandle::Weak(id) => id.type_id(),
}
}
#[inline]
pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
match self {
UntypedHandle::Strong(handle) => Handle::Strong(handle),
UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
}
}
#[inline]
pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
debug_assert_eq!(
self.type_id(),
TypeId::of::<A>(),
"The target Handle<A>'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::<A>()),
}
}
#[inline]
pub fn typed<A: Asset>(self) -> Handle<A> {
let Ok(handle) = self.try_typed() else {
panic!(
"The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
std::any::type_name::<A>()
)
};
handle
}
#[inline]
pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
Handle::try_from(self)
}
#[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<H: Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl std::fmt::Debug for UntypedHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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<std::cmp::Ordering> {
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()
}
}
impl From<&UntypedHandle> for UntypedAssetId {
#[inline]
fn from(value: &UntypedHandle) -> Self {
value.id()
}
}
impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
#[inline]
fn eq(&self, other: &UntypedHandle) -> bool {
TypeId::of::<A>() == other.type_id() && self.id() == other.id()
}
}
impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
#[inline]
fn eq(&self, other: &Handle<A>) -> bool {
other.eq(self)
}
}
impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
#[inline]
fn partial_cmp(&self, other: &UntypedHandle) -> Option<std::cmp::Ordering> {
if TypeId::of::<A>() != other.type_id() {
None
} else {
self.id().partial_cmp(&other.id())
}
}
}
impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
#[inline]
fn partial_cmp(&self, other: &Handle<A>) -> Option<std::cmp::Ordering> {
Some(other.partial_cmp(self)?.reverse())
}
}
impl<A: Asset> From<Handle<A>> for UntypedHandle {
fn from(value: Handle<A>) -> Self {
match value {
Handle::Strong(handle) => UntypedHandle::Strong(handle),
Handle::Weak(id) => UntypedHandle::Weak(id.into()),
}
}
}
impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
type Error = UntypedAssetConversionError;
fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
let found = value.type_id();
let expected = TypeId::of::<A>();
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))
}
}
}
}
#[derive(Error, Debug, PartialEq, Clone)]
#[non_exhaustive]
pub enum UntypedAssetConversionError {
#[error(
"This UntypedHandle is for {found:?} and cannot be converted into an Handle<{expected:?}>"
)]
TypeIdMismatch { expected: TypeId, found: TypeId },
}
#[cfg(test)]
mod tests {
use super::*;
type TestAsset = ();
const UUID_1: Uuid = Uuid::from_u128(123);
const UUID_2: Uuid = Uuid::from_u128(456);
fn hash<T: Hash>(data: &T) -> u64 {
let mut hasher = bevy_utils::AHasher::default();
data.hash(&mut hasher);
hasher.finish()
}
#[test]
fn equality() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let typed = Handle::Weak(typed);
let untyped = UntypedHandle::Weak(untyped);
assert_eq!(
Ok(typed.clone()),
Handle::<TestAsset>::try_from(untyped.clone())
);
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
assert_eq!(typed, untyped);
}
#[allow(clippy::cmp_owned)]
#[test]
fn ordering() {
assert!(UUID_1 < UUID_2);
let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
let untyped_1 = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let untyped_2 = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
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::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
assert!(typed_1 < untyped_2);
assert!(untyped_1 < typed_2);
}
#[test]
fn hashing() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let typed = Handle::Weak(typed);
let untyped = UntypedHandle::Weak(untyped);
assert_eq!(
hash(&typed),
hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
);
assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
assert_eq!(hash(&typed), hash(&untyped));
}
#[test]
fn conversion() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
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);
}
}