use crate::{
meta::MetaTransform, Asset, AssetId, AssetIndex, AssetIndexAllocator, AssetPath, AssetServer,
ErasedAssetIndex, ReflectHandle, UntypedAssetId,
};
use alloc::sync::Arc;
use bevy_ecs::template::{FromTemplate, SpecializeFromTemplate, Template, TemplateContext};
use bevy_platform::{collections::Equivalent, sync::Mutex};
use bevy_reflect::{enums::Enum, FromReflect, PartialReflect, Reflect, ReflectRef, TypePath};
use core::{
any::TypeId,
hash::{Hash, Hasher},
marker::PhantomData,
};
use crossbeam_channel::{Receiver, Sender};
use disqualified::ShortName;
use thiserror::Error;
use uuid::Uuid;
#[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,
}
#[derive(Debug)]
pub(crate) struct DropEvent {
pub(crate) index: ErasedAssetIndex,
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(index, false, None, None))
}
pub(crate) fn get_handle(
&self,
index: AssetIndex,
asset_server_managed: bool,
path: Option<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
) -> Arc<StrongHandle> {
Arc::new(StrongHandle {
index,
type_id: 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(index, asset_server_managed, path, meta_transform)
}
}
#[derive(TypePath)]
pub struct StrongHandle {
pub(crate) index: AssetIndex,
pub(crate) type_id: TypeId,
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 {
index: ErasedAssetIndex::new(self.index, self.type_id),
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("index", &self.index)
.field("type_id", &self.type_id)
.field("asset_server_managed", &self.asset_server_managed)
.field("path", &self.path)
.field("drop_sender", &self.drop_sender)
.finish()
}
}
#[derive(Reflect)]
#[reflect(Debug, Hash, PartialEq, Clone, Handle, from_reflect = false)]
pub enum Handle<A: Asset> {
Strong(Arc<StrongHandle>),
Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
}
impl<A: Asset> FromReflect for Handle<A>
where
Handle<A>: Send + Sync,
A: TypePath,
{
fn from_reflect(reflect_value: &dyn PartialReflect) -> Option<Self> {
let ReflectRef::Enum(enum_value) = PartialReflect::reflect_ref(reflect_value) else {
return None;
};
match Enum::variant_name(enum_value) {
"Strong" => {
let strong_field = enum_value.field_at(0usize)?;
let strong_handle = Arc::<StrongHandle>::from_reflect(strong_field)?;
if strong_handle.type_id != TypeId::of::<A>() {
return None;
}
Some(Handle::Strong(strong_handle))
}
"Uuid" => {
let uuid_field = enum_value.field_at(0usize)?;
let uuid = Uuid::from_reflect(uuid_field)?;
Some(Handle::Uuid(uuid, Default::default()))
}
_ => None,
}
}
}
impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
match self {
Handle::Strong(handle) => Handle::Strong(handle.clone()),
Handle::Uuid(uuid, ..) => Handle::Uuid(*uuid, PhantomData),
}
}
}
impl<A: Asset> Handle<A> {
#[inline]
pub fn id(&self) -> AssetId<A> {
match self {
Handle::Strong(handle) => AssetId::Index {
index: handle.index,
marker: PhantomData,
},
Handle::Uuid(uuid, ..) => AssetId::Uuid { uuid: *uuid },
}
}
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
match self {
Handle::Strong(handle) => handle.path.as_ref(),
Handle::Uuid(..) => None,
}
}
#[inline]
pub fn is_uuid(&self) -> bool {
matches!(self, Handle::Uuid(..))
}
#[inline]
pub fn is_strong(&self) -> bool {
matches!(self, Handle::Strong(_))
}
#[inline]
pub fn untyped(self) -> UntypedHandle {
self.into()
}
}
impl<A: Asset> Default for Handle<A> {
fn default() -> Self {
Handle::Uuid(AssetId::<A>::DEFAULT_UUID, PhantomData)
}
}
impl<T: Asset> Unpin for Handle<T> where for<'a> [()]: SpecializeFromTemplate {}
impl<T: Asset> FromTemplate for Handle<T> {
type Template = HandleTemplate<T>;
}
#[derive(Reflect)]
pub enum HandleTemplate<T: Asset> {
Path(AssetPath<'static>),
Handle(Handle<T>),
Value(ArcMutexValue<T>),
}
impl<T: Asset> HandleTemplate<T> {
pub fn value(value: impl Into<T>) -> Self {
HandleTemplate::Value(ArcMutexValue(Arc::new(Mutex::new(AssetOrHandle::Value(
Some(value.into()),
)))))
}
}
#[derive(Reflect)]
#[reflect(opaque)]
pub struct ArcMutexValue<T: Asset>(Arc<Mutex<AssetOrHandle<T>>>);
impl<T: Asset> Clone for ArcMutexValue<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
#[derive(Reflect)]
enum AssetOrHandle<T: Asset> {
Value(Option<T>),
Handle(Handle<T>),
}
impl<T: Asset> Default for AssetOrHandle<T> {
fn default() -> Self {
Self::Handle(Default::default())
}
}
impl<T: Asset> Default for HandleTemplate<T> {
fn default() -> Self {
Self::Handle(Default::default())
}
}
impl<I: Into<AssetPath<'static>>, T: Asset> From<I> for HandleTemplate<T> {
fn from(value: I) -> Self {
Self::Path(value.into())
}
}
impl<T: Asset> From<Handle<T>> for HandleTemplate<T> {
fn from(value: Handle<T>) -> Self {
Self::Handle(value)
}
}
impl<T: Asset> Template for HandleTemplate<T> {
type Output = Handle<T>;
fn build_template(&self, context: &mut TemplateContext) -> bevy_ecs::error::Result<Handle<T>> {
Ok(match self {
HandleTemplate::Path(asset_path) => context.resource::<AssetServer>().load(asset_path),
HandleTemplate::Handle(handle) => handle.clone(),
HandleTemplate::Value(value) => {
let mut value_or_handle = value.0.lock().unwrap();
match &mut *value_or_handle {
AssetOrHandle::Value(value) => {
let handle = context.resource::<AssetServer>().add(value.take().unwrap());
*value_or_handle = AssetOrHandle::Handle(handle.clone());
handle
}
AssetOrHandle::Handle(handle) => handle.clone(),
}
}
})
}
fn clone_template(&self) -> Self {
match self {
HandleTemplate::Path(asset_path) => HandleTemplate::Path(asset_path.clone()),
HandleTemplate::Handle(handle) => HandleTemplate::Handle(handle.clone()),
HandleTemplate::Value(value) => HandleTemplate::Value(value.clone()),
}
}
}
pub fn asset_value<I: Into<A>, A: Asset>(asset: I) -> HandleTemplate<A> {
HandleTemplate::value(asset)
}
impl<A: Asset> core::fmt::Debug for Handle<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = ShortName::of::<A>();
match self {
Handle::Strong(handle) => {
write!(
f,
"StrongHandle<{name}>{{ index: {:?}, type_id: {:?}, path: {:?} }}",
handle.index, handle.type_id, handle.path
)
}
Handle::Uuid(uuid, ..) => write!(f, "UuidHandle<{name}>({uuid:?})"),
}
}
}
impl<A: Asset> Hash for Handle<A> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl<T: Asset> Equivalent<Handle<T>> for AssetId<T> {
fn equivalent(&self, key: &Handle<T>) -> bool {
*self == key.id()
}
}
impl<A: Asset> PartialOrd for Handle<A> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<A: Asset> Ord for Handle<A> {
fn cmp(&self, other: &Self) -> core::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 UntypedAssetId {
#[inline]
fn from(value: &Handle<A>) -> Self {
value.id().into()
}
}
impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
#[inline]
fn from(value: &mut Handle<A>) -> Self {
value.id()
}
}
impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
#[inline]
fn from(value: &mut Handle<A>) -> Self {
value.id().into()
}
}
impl<A: Asset> From<Uuid> for Handle<A> {
#[inline]
fn from(uuid: Uuid) -> Self {
Handle::Uuid(uuid, PhantomData)
}
}
#[derive(Clone, Reflect)]
pub enum UntypedHandle {
Strong(Arc<StrongHandle>),
Uuid {
type_id: TypeId,
uuid: Uuid,
},
}
impl UntypedHandle {
pub fn default_for_type(type_id: TypeId) -> Self {
Self::Uuid {
type_id,
uuid: AssetId::<()>::DEFAULT_UUID,
}
}
#[inline]
pub fn id(&self) -> UntypedAssetId {
match self {
UntypedHandle::Strong(handle) => UntypedAssetId::Index {
type_id: handle.type_id,
index: handle.index,
},
UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
uuid: *uuid,
type_id: *type_id,
},
}
}
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
match self {
UntypedHandle::Strong(handle) => handle.path.as_ref(),
UntypedHandle::Uuid { .. } => None,
}
}
#[inline]
pub fn type_id(&self) -> TypeId {
match self {
UntypedHandle::Strong(handle) => handle.type_id,
UntypedHandle::Uuid { type_id, .. } => *type_id,
}
}
#[inline]
pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
match self {
UntypedHandle::Strong(handle) => Handle::Strong(handle),
UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
}
}
#[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"
);
self.typed_unchecked()
}
#[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",
core::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::Uuid { .. } => 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 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.type_id, handle.index, handle.path
)
}
UntypedHandle::Uuid { type_id, uuid } => {
write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
}
}
}
}
impl PartialOrd for UntypedHandle {
fn partial_cmp(&self, other: &Self) -> Option<core::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<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<core::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<core::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::Uuid(uuid, _) => UntypedHandle::Uuid {
type_id: TypeId::of::<A>(),
uuid,
},
}
}
}
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 });
}
Ok(match value {
UntypedHandle::Strong(handle) => Handle::Strong(handle),
UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
})
}
}
#[macro_export]
macro_rules! uuid_handle {
($uuid:expr) => {{
$crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
}};
}
#[deprecated = "Use uuid_handle! instead"]
#[macro_export]
macro_rules! weak_handle {
($uuid:expr) => {
$crate::uuid_handle!($uuid)
};
}
#[derive(Error, Debug, PartialEq, Clone)]
#[non_exhaustive]
pub enum UntypedAssetConversionError {
#[error(
"This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
)]
TypeIdMismatch {
expected: TypeId,
found: TypeId,
},
}
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use bevy_platform::hash::FixedHasher;
use bevy_reflect::PartialReflect;
use core::hash::BuildHasher;
use uuid::Uuid;
use crate::tests::create_app;
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 {
FixedHasher.hash_one(data)
}
#[test]
fn equality() {
let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
let untyped = UntypedHandle::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
assert_eq!(
Ok(typed.clone()),
Handle::<TestAsset>::try_from(untyped.clone())
);
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
assert_eq!(typed, untyped);
}
#[test]
#[expect(
clippy::cmp_owned,
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."
)]
fn ordering() {
assert!(UUID_1 < UUID_2);
let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
let untyped_1 = UntypedHandle::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let untyped_2 = UntypedHandle::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_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 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
let untyped = UntypedHandle::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
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 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
let untyped = UntypedHandle::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
}
#[test]
fn from_uuid() {
let uuid = UUID_1;
let handle: Handle<TestAsset> = uuid.into();
assert!(handle.is_uuid());
assert_eq!(handle.id(), AssetId::Uuid { uuid });
}
#[test]
fn strong_handle_reflect_clone() {
use crate::{AssetApp, Assets, VisitAssetDependencies};
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 = create_app().0;
app.init_asset::<MyAsset>();
let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
let handle: Handle<MyAsset> = 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<dyn Reflect> = reflected.reflect_clone().unwrap();
assert_eq!(
Arc::strong_count(strong),
2,
"Cloning the handle with reflect should increase the strong count to 2"
);
let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
assert_eq!(
Arc::strong_count(strong),
3,
"Converting the handle to a dynamic should increase the strong count to 3"
);
let from_reflect_handle: Handle<MyAsset> =
FromReflect::from_reflect(&*dynamic_handle).unwrap();
assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
assert!(
from_reflect_handle.is_strong(),
"The cloned handle should still be strong"
);
}
_ => panic!("Expected a strong handle"),
}
}
#[test]
fn handle_from_reflect_verifies_type_id() {
use crate::{AssetApp, Assets};
use bevy_reflect::FromReflect;
#[derive(Reflect, Asset)]
struct A;
#[derive(Reflect, Asset)]
struct B;
let mut app = create_app().0;
app.init_asset::<A>().init_asset::<B>();
let mut assets = app.world_mut().resource_mut::<Assets<A>>();
let handle_a = assets.add(A);
let dynamic_handle_a = handle_a.to_dynamic();
let reflected_handle_a = handle_a.as_partial_reflect();
let handle_b_from_reflect_dynamic: Option<Handle<B>> =
FromReflect::from_reflect(&*dynamic_handle_a);
let handle_b_from_reflect: Option<Handle<B>> =
FromReflect::from_reflect(reflected_handle_a);
let handle_a_from_reflect: Option<Handle<A>> =
FromReflect::from_reflect(reflected_handle_a);
assert!(
handle_b_from_reflect.is_none(),
"Handle<B> should not be constructible from reflected Handle<A>"
);
assert!(
handle_b_from_reflect_dynamic.is_none(),
"Handle<B> should not be constructible from dynamic Handle<A>"
);
assert!(
handle_a_from_reflect.is_some(),
"Handle<A> should be constructible from reflected Handle<A>"
);
}
}