1use crate::{
2 meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId,
3 UntypedAssetId,
4};
5use alloc::sync::Arc;
6use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
7use core::{
8 any::TypeId,
9 hash::{Hash, Hasher},
10 marker::PhantomData,
11};
12use crossbeam_channel::{Receiver, Sender};
13use disqualified::ShortName;
14use thiserror::Error;
15use uuid::Uuid;
16
17#[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 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#[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 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 core::fmt::Debug for StrongHandle {
107 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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#[derive(Reflect)]
133#[reflect(Default, Debug, Hash, PartialEq, Clone)]
134pub enum Handle<A: Asset> {
135 Strong(Arc<StrongHandle>),
138 Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
141}
142
143impl<T: Asset> Clone for Handle<T> {
144 fn clone(&self) -> Self {
145 match self {
146 Handle::Strong(handle) => Handle::Strong(handle.clone()),
147 Handle::Uuid(uuid, ..) => Handle::Uuid(*uuid, PhantomData),
148 }
149 }
150}
151
152impl<A: Asset> Handle<A> {
153 #[inline]
155 pub fn id(&self) -> AssetId<A> {
156 match self {
157 Handle::Strong(handle) => handle.id.typed_unchecked(),
158 Handle::Uuid(uuid, ..) => AssetId::Uuid { uuid: *uuid },
159 }
160 }
161
162 #[inline]
164 pub fn path(&self) -> Option<&AssetPath<'static>> {
165 match self {
166 Handle::Strong(handle) => handle.path.as_ref(),
167 Handle::Uuid(..) => None,
168 }
169 }
170
171 #[inline]
173 pub fn is_uuid(&self) -> bool {
174 matches!(self, Handle::Uuid(..))
175 }
176
177 #[inline]
179 pub fn is_strong(&self) -> bool {
180 matches!(self, Handle::Strong(_))
181 }
182
183 #[inline]
187 pub fn untyped(self) -> UntypedHandle {
188 self.into()
189 }
190}
191
192impl<A: Asset> Default for Handle<A> {
193 fn default() -> Self {
194 Handle::Uuid(AssetId::<A>::DEFAULT_UUID, PhantomData)
195 }
196}
197
198impl<A: Asset> core::fmt::Debug for Handle<A> {
199 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200 let name = ShortName::of::<A>();
201 match self {
202 Handle::Strong(handle) => {
203 write!(
204 f,
205 "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
206 handle.id.internal(),
207 handle.path
208 )
209 }
210 Handle::Uuid(uuid, ..) => write!(f, "UuidHandle<{name}>({uuid:?})"),
211 }
212 }
213}
214
215impl<A: Asset> Hash for Handle<A> {
216 #[inline]
217 fn hash<H: Hasher>(&self, state: &mut H) {
218 self.id().hash(state);
219 }
220}
221
222impl<A: Asset> PartialOrd for Handle<A> {
223 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
224 Some(self.cmp(other))
225 }
226}
227
228impl<A: Asset> Ord for Handle<A> {
229 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
230 self.id().cmp(&other.id())
231 }
232}
233
234impl<A: Asset> PartialEq for Handle<A> {
235 #[inline]
236 fn eq(&self, other: &Self) -> bool {
237 self.id() == other.id()
238 }
239}
240
241impl<A: Asset> Eq for Handle<A> {}
242
243impl<A: Asset> From<&Handle<A>> for AssetId<A> {
244 #[inline]
245 fn from(value: &Handle<A>) -> Self {
246 value.id()
247 }
248}
249
250impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
251 #[inline]
252 fn from(value: &Handle<A>) -> Self {
253 value.id().into()
254 }
255}
256
257impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
258 #[inline]
259 fn from(value: &mut Handle<A>) -> Self {
260 value.id()
261 }
262}
263
264impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
265 #[inline]
266 fn from(value: &mut Handle<A>) -> Self {
267 value.id().into()
268 }
269}
270
271impl<A: Asset> From<Uuid> for Handle<A> {
272 #[inline]
273 fn from(uuid: Uuid) -> Self {
274 Handle::Uuid(uuid, PhantomData)
275 }
276}
277
278#[derive(Clone, Reflect)]
284pub enum UntypedHandle {
285 Strong(Arc<StrongHandle>),
287 Uuid {
289 type_id: TypeId,
291 uuid: Uuid,
293 },
294}
295
296impl UntypedHandle {
297 #[inline]
299 pub fn id(&self) -> UntypedAssetId {
300 match self {
301 UntypedHandle::Strong(handle) => handle.id,
302 UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
303 uuid: *uuid,
304 type_id: *type_id,
305 },
306 }
307 }
308
309 #[inline]
311 pub fn path(&self) -> Option<&AssetPath<'static>> {
312 match self {
313 UntypedHandle::Strong(handle) => handle.path.as_ref(),
314 UntypedHandle::Uuid { .. } => None,
315 }
316 }
317
318 #[inline]
320 pub fn type_id(&self) -> TypeId {
321 match self {
322 UntypedHandle::Strong(handle) => handle.id.type_id(),
323 UntypedHandle::Uuid { type_id, .. } => *type_id,
324 }
325 }
326
327 #[inline]
329 pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
330 match self {
331 UntypedHandle::Strong(handle) => Handle::Strong(handle),
332 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
333 }
334 }
335
336 #[inline]
341 pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
342 debug_assert_eq!(
343 self.type_id(),
344 TypeId::of::<A>(),
345 "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
346 );
347 self.typed_unchecked()
348 }
349
350 #[inline]
352 pub fn typed<A: Asset>(self) -> Handle<A> {
353 let Ok(handle) = self.try_typed() else {
354 panic!(
355 "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
356 core::any::type_name::<A>()
357 )
358 };
359
360 handle
361 }
362
363 #[inline]
365 pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
366 Handle::try_from(self)
367 }
368
369 #[inline]
372 pub fn meta_transform(&self) -> Option<&MetaTransform> {
373 match self {
374 UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
375 UntypedHandle::Uuid { .. } => None,
376 }
377 }
378}
379
380impl PartialEq for UntypedHandle {
381 #[inline]
382 fn eq(&self, other: &Self) -> bool {
383 self.id() == other.id() && self.type_id() == other.type_id()
384 }
385}
386
387impl Eq for UntypedHandle {}
388
389impl Hash for UntypedHandle {
390 #[inline]
391 fn hash<H: Hasher>(&self, state: &mut H) {
392 self.id().hash(state);
393 }
394}
395
396impl core::fmt::Debug for UntypedHandle {
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 match self {
399 UntypedHandle::Strong(handle) => {
400 write!(
401 f,
402 "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
403 handle.id.type_id(),
404 handle.id.internal(),
405 handle.path
406 )
407 }
408 UntypedHandle::Uuid { type_id, uuid } => {
409 write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
410 }
411 }
412 }
413}
414
415impl PartialOrd for UntypedHandle {
416 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
417 if self.type_id() == other.type_id() {
418 self.id().partial_cmp(&other.id())
419 } else {
420 None
421 }
422 }
423}
424
425impl From<&UntypedHandle> for UntypedAssetId {
426 #[inline]
427 fn from(value: &UntypedHandle) -> Self {
428 value.id()
429 }
430}
431
432impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
435 #[inline]
436 fn eq(&self, other: &UntypedHandle) -> bool {
437 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
438 }
439}
440
441impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
442 #[inline]
443 fn eq(&self, other: &Handle<A>) -> bool {
444 other.eq(self)
445 }
446}
447
448impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
449 #[inline]
450 fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
451 if TypeId::of::<A>() != other.type_id() {
452 None
453 } else {
454 self.id().partial_cmp(&other.id())
455 }
456 }
457}
458
459impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
460 #[inline]
461 fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
462 Some(other.partial_cmp(self)?.reverse())
463 }
464}
465
466impl<A: Asset> From<Handle<A>> for UntypedHandle {
467 fn from(value: Handle<A>) -> Self {
468 match value {
469 Handle::Strong(handle) => UntypedHandle::Strong(handle),
470 Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
471 type_id: TypeId::of::<A>(),
472 uuid,
473 },
474 }
475 }
476}
477
478impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
479 type Error = UntypedAssetConversionError;
480
481 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
482 let found = value.type_id();
483 let expected = TypeId::of::<A>();
484
485 if found != expected {
486 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
487 }
488
489 Ok(match value {
490 UntypedHandle::Strong(handle) => Handle::Strong(handle),
491 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
492 })
493 }
494}
495
496#[macro_export]
506macro_rules! uuid_handle {
507 ($uuid:expr) => {{
508 $crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
509 }};
510}
511
512#[deprecated = "Use uuid_handle! instead"]
513#[macro_export]
514macro_rules! weak_handle {
515 ($uuid:expr) => {
516 $crate::uuid_handle!($uuid)
517 };
518}
519
520#[derive(Error, Debug, PartialEq, Clone)]
522#[non_exhaustive]
523pub enum UntypedAssetConversionError {
524 #[error(
526 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
527 )]
528 TypeIdMismatch {
529 expected: TypeId,
531 found: TypeId,
533 },
534}
535
536#[cfg(test)]
537mod tests {
538 use alloc::boxed::Box;
539 use bevy_platform::hash::FixedHasher;
540 use bevy_reflect::PartialReflect;
541 use core::hash::BuildHasher;
542 use uuid::Uuid;
543
544 use super::*;
545
546 type TestAsset = ();
547
548 const UUID_1: Uuid = Uuid::from_u128(123);
549 const UUID_2: Uuid = Uuid::from_u128(456);
550
551 fn hash<T: Hash>(data: &T) -> u64 {
553 FixedHasher.hash_one(data)
554 }
555
556 #[test]
558 fn equality() {
559 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
560 let untyped = UntypedHandle::Uuid {
561 type_id: TypeId::of::<TestAsset>(),
562 uuid: UUID_1,
563 };
564
565 assert_eq!(
566 Ok(typed.clone()),
567 Handle::<TestAsset>::try_from(untyped.clone())
568 );
569 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
570 assert_eq!(typed, untyped);
571 }
572
573 #[test]
575 #[expect(
576 clippy::cmp_owned,
577 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."
578 )]
579 fn ordering() {
580 assert!(UUID_1 < UUID_2);
581
582 let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
583 let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
584 let untyped_1 = UntypedHandle::Uuid {
585 type_id: TypeId::of::<TestAsset>(),
586 uuid: UUID_1,
587 };
588 let untyped_2 = UntypedHandle::Uuid {
589 type_id: TypeId::of::<TestAsset>(),
590 uuid: UUID_2,
591 };
592
593 assert!(typed_1 < typed_2);
594 assert!(untyped_1 < untyped_2);
595
596 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
597 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
598
599 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
600 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
601
602 assert!(typed_1 < untyped_2);
603 assert!(untyped_1 < typed_2);
604 }
605
606 #[test]
608 fn hashing() {
609 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
610 let untyped = UntypedHandle::Uuid {
611 type_id: TypeId::of::<TestAsset>(),
612 uuid: UUID_1,
613 };
614
615 assert_eq!(
616 hash(&typed),
617 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
618 );
619 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
620 assert_eq!(hash(&typed), hash(&untyped));
621 }
622
623 #[test]
625 fn conversion() {
626 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
627 let untyped = UntypedHandle::Uuid {
628 type_id: TypeId::of::<TestAsset>(),
629 uuid: UUID_1,
630 };
631
632 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
633 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
634 }
635
636 #[test]
637 fn from_uuid() {
638 let uuid = UUID_1;
639 let handle: Handle<TestAsset> = uuid.into();
640
641 assert!(handle.is_uuid());
642 assert_eq!(handle.id(), AssetId::Uuid { uuid });
643 }
644
645 #[test]
647 fn strong_handle_reflect_clone() {
648 use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
649 use bevy_app::App;
650 use bevy_reflect::FromReflect;
651
652 #[derive(Reflect)]
653 struct MyAsset {
654 value: u32,
655 }
656 impl Asset for MyAsset {}
657 impl VisitAssetDependencies for MyAsset {
658 fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
659 }
660
661 let mut app = App::new();
662 app.add_plugins(AssetPlugin::default())
663 .init_asset::<MyAsset>();
664 let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
665
666 let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
667 match &handle {
668 Handle::Strong(strong) => {
669 assert_eq!(
670 Arc::strong_count(strong),
671 1,
672 "Inserting the asset should result in a strong count of 1"
673 );
674
675 let reflected: &dyn Reflect = &handle;
676 let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
677
678 assert_eq!(
679 Arc::strong_count(strong),
680 2,
681 "Cloning the handle with reflect should increase the strong count to 2"
682 );
683
684 let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
685
686 assert_eq!(
687 Arc::strong_count(strong),
688 3,
689 "Converting the handle to a dynamic should increase the strong count to 3"
690 );
691
692 let from_reflect_handle: Handle<MyAsset> =
693 FromReflect::from_reflect(&*dynamic_handle).unwrap();
694
695 assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
696 assert!(
697 from_reflect_handle.is_strong(),
698 "The cloned handle should still be strong"
699 );
700 }
701 _ => panic!("Expected a strong handle"),
702 }
703 }
704}