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};
11use crossbeam_channel::{Receiver, Sender};
12use disqualified::ShortName;
13use thiserror::Error;
14use uuid::Uuid;
15
16#[derive(Clone)]
19pub struct AssetHandleProvider {
20 pub(crate) allocator: Arc<AssetIndexAllocator>,
21 pub(crate) drop_sender: Sender<DropEvent>,
22 pub(crate) drop_receiver: Receiver<DropEvent>,
23 pub(crate) type_id: TypeId,
24}
25
26#[derive(Debug)]
27pub(crate) struct DropEvent {
28 pub(crate) id: InternalAssetId,
29 pub(crate) asset_server_managed: bool,
30}
31
32impl AssetHandleProvider {
33 pub(crate) fn new(type_id: TypeId, allocator: Arc<AssetIndexAllocator>) -> Self {
34 let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
35 Self {
36 type_id,
37 allocator,
38 drop_sender,
39 drop_receiver,
40 }
41 }
42
43 pub fn reserve_handle(&self) -> UntypedHandle {
46 let index = self.allocator.reserve();
47 UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None, None))
48 }
49
50 pub(crate) fn get_handle(
51 &self,
52 id: InternalAssetId,
53 asset_server_managed: bool,
54 path: Option<AssetPath<'static>>,
55 meta_transform: Option<MetaTransform>,
56 ) -> Arc<StrongHandle> {
57 Arc::new(StrongHandle {
58 id: id.untyped(self.type_id),
59 drop_sender: self.drop_sender.clone(),
60 meta_transform,
61 path,
62 asset_server_managed,
63 })
64 }
65
66 pub(crate) fn reserve_handle_internal(
67 &self,
68 asset_server_managed: bool,
69 path: Option<AssetPath<'static>>,
70 meta_transform: Option<MetaTransform>,
71 ) -> Arc<StrongHandle> {
72 let index = self.allocator.reserve();
73 self.get_handle(
74 InternalAssetId::Index(index),
75 asset_server_managed,
76 path,
77 meta_transform,
78 )
79 }
80}
81
82#[derive(TypePath)]
85pub struct StrongHandle {
86 pub(crate) id: UntypedAssetId,
87 pub(crate) asset_server_managed: bool,
88 pub(crate) path: Option<AssetPath<'static>>,
89 pub(crate) meta_transform: Option<MetaTransform>,
93 pub(crate) drop_sender: Sender<DropEvent>,
94}
95
96impl Drop for StrongHandle {
97 fn drop(&mut self) {
98 let _ = self.drop_sender.send(DropEvent {
99 id: self.id.internal(),
100 asset_server_managed: self.asset_server_managed,
101 });
102 }
103}
104
105impl core::fmt::Debug for StrongHandle {
106 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107 f.debug_struct("StrongHandle")
108 .field("id", &self.id)
109 .field("asset_server_managed", &self.asset_server_managed)
110 .field("path", &self.path)
111 .field("drop_sender", &self.drop_sender)
112 .finish()
113 }
114}
115
116#[derive(Reflect)]
132#[reflect(Default, Debug, Hash, PartialEq, Clone)]
133pub enum Handle<A: Asset> {
134 Strong(Arc<StrongHandle>),
137 Weak(AssetId<A>),
140}
141
142impl<T: Asset> Clone for Handle<T> {
143 fn clone(&self) -> Self {
144 match self {
145 Handle::Strong(handle) => Handle::Strong(handle.clone()),
146 Handle::Weak(id) => Handle::Weak(*id),
147 }
148 }
149}
150
151impl<A: Asset> Handle<A> {
152 #[deprecated(
154 since = "0.16.0",
155 note = "use the `weak_handle!` macro with a UUID string instead"
156 )]
157 pub const fn weak_from_u128(value: u128) -> Self {
158 Handle::Weak(AssetId::Uuid {
159 uuid: Uuid::from_u128(value),
160 })
161 }
162
163 #[inline]
165 pub fn id(&self) -> AssetId<A> {
166 match self {
167 Handle::Strong(handle) => handle.id.typed_unchecked(),
168 Handle::Weak(id) => *id,
169 }
170 }
171
172 #[inline]
174 pub fn path(&self) -> Option<&AssetPath<'static>> {
175 match self {
176 Handle::Strong(handle) => handle.path.as_ref(),
177 Handle::Weak(_) => None,
178 }
179 }
180
181 #[inline]
183 pub fn is_weak(&self) -> bool {
184 matches!(self, Handle::Weak(_))
185 }
186
187 #[inline]
189 pub fn is_strong(&self) -> bool {
190 matches!(self, Handle::Strong(_))
191 }
192
193 #[inline]
195 pub fn clone_weak(&self) -> Self {
196 match self {
197 Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
198 Handle::Weak(id) => Handle::Weak(*id),
199 }
200 }
201
202 #[inline]
206 pub fn untyped(self) -> UntypedHandle {
207 self.into()
208 }
209}
210
211impl<A: Asset> Default for Handle<A> {
212 fn default() -> Self {
213 Handle::Weak(AssetId::default())
214 }
215}
216
217impl<A: Asset> core::fmt::Debug for Handle<A> {
218 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219 let name = ShortName::of::<A>();
220 match self {
221 Handle::Strong(handle) => {
222 write!(
223 f,
224 "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
225 handle.id.internal(),
226 handle.path
227 )
228 }
229 Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
230 }
231 }
232}
233
234impl<A: Asset> Hash for Handle<A> {
235 #[inline]
236 fn hash<H: Hasher>(&self, state: &mut H) {
237 self.id().hash(state);
238 }
239}
240
241impl<A: Asset> PartialOrd for Handle<A> {
242 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
243 Some(self.cmp(other))
244 }
245}
246
247impl<A: Asset> Ord for Handle<A> {
248 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
249 self.id().cmp(&other.id())
250 }
251}
252
253impl<A: Asset> PartialEq for Handle<A> {
254 #[inline]
255 fn eq(&self, other: &Self) -> bool {
256 self.id() == other.id()
257 }
258}
259
260impl<A: Asset> Eq for Handle<A> {}
261
262impl<A: Asset> From<&Handle<A>> for AssetId<A> {
263 #[inline]
264 fn from(value: &Handle<A>) -> Self {
265 value.id()
266 }
267}
268
269impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
270 #[inline]
271 fn from(value: &Handle<A>) -> Self {
272 value.id().into()
273 }
274}
275
276impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
277 #[inline]
278 fn from(value: &mut Handle<A>) -> Self {
279 value.id()
280 }
281}
282
283impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
284 #[inline]
285 fn from(value: &mut Handle<A>) -> Self {
286 value.id().into()
287 }
288}
289
290#[derive(Clone)]
296pub enum UntypedHandle {
297 Strong(Arc<StrongHandle>),
299 Weak(UntypedAssetId),
301}
302
303impl UntypedHandle {
304 #[inline]
306 pub fn id(&self) -> UntypedAssetId {
307 match self {
308 UntypedHandle::Strong(handle) => handle.id,
309 UntypedHandle::Weak(id) => *id,
310 }
311 }
312
313 #[inline]
315 pub fn path(&self) -> Option<&AssetPath<'static>> {
316 match self {
317 UntypedHandle::Strong(handle) => handle.path.as_ref(),
318 UntypedHandle::Weak(_) => None,
319 }
320 }
321
322 #[inline]
324 pub fn clone_weak(&self) -> UntypedHandle {
325 match self {
326 UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
327 UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
328 }
329 }
330
331 #[inline]
333 pub fn type_id(&self) -> TypeId {
334 match self {
335 UntypedHandle::Strong(handle) => handle.id.type_id(),
336 UntypedHandle::Weak(id) => id.type_id(),
337 }
338 }
339
340 #[inline]
342 pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
343 match self {
344 UntypedHandle::Strong(handle) => Handle::Strong(handle),
345 UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
346 }
347 }
348
349 #[inline]
354 pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
355 debug_assert_eq!(
356 self.type_id(),
357 TypeId::of::<A>(),
358 "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
359 );
360 match self {
361 UntypedHandle::Strong(handle) => Handle::Strong(handle),
362 UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
363 }
364 }
365
366 #[inline]
368 pub fn typed<A: Asset>(self) -> Handle<A> {
369 let Ok(handle) = self.try_typed() else {
370 panic!(
371 "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
372 core::any::type_name::<A>()
373 )
374 };
375
376 handle
377 }
378
379 #[inline]
381 pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
382 Handle::try_from(self)
383 }
384
385 #[inline]
388 pub fn meta_transform(&self) -> Option<&MetaTransform> {
389 match self {
390 UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
391 UntypedHandle::Weak(_) => None,
392 }
393 }
394}
395
396impl PartialEq for UntypedHandle {
397 #[inline]
398 fn eq(&self, other: &Self) -> bool {
399 self.id() == other.id() && self.type_id() == other.type_id()
400 }
401}
402
403impl Eq for UntypedHandle {}
404
405impl Hash for UntypedHandle {
406 #[inline]
407 fn hash<H: Hasher>(&self, state: &mut H) {
408 self.id().hash(state);
409 }
410}
411
412impl core::fmt::Debug for UntypedHandle {
413 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
414 match self {
415 UntypedHandle::Strong(handle) => {
416 write!(
417 f,
418 "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
419 handle.id.type_id(),
420 handle.id.internal(),
421 handle.path
422 )
423 }
424 UntypedHandle::Weak(id) => write!(
425 f,
426 "WeakHandle{{ type_id: {:?}, id: {:?} }}",
427 id.type_id(),
428 id.internal()
429 ),
430 }
431 }
432}
433
434impl PartialOrd for UntypedHandle {
435 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
436 if self.type_id() == other.type_id() {
437 self.id().partial_cmp(&other.id())
438 } else {
439 None
440 }
441 }
442}
443
444impl From<&UntypedHandle> for UntypedAssetId {
445 #[inline]
446 fn from(value: &UntypedHandle) -> Self {
447 value.id()
448 }
449}
450
451impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
454 #[inline]
455 fn eq(&self, other: &UntypedHandle) -> bool {
456 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
457 }
458}
459
460impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
461 #[inline]
462 fn eq(&self, other: &Handle<A>) -> bool {
463 other.eq(self)
464 }
465}
466
467impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
468 #[inline]
469 fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
470 if TypeId::of::<A>() != other.type_id() {
471 None
472 } else {
473 self.id().partial_cmp(&other.id())
474 }
475 }
476}
477
478impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
479 #[inline]
480 fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
481 Some(other.partial_cmp(self)?.reverse())
482 }
483}
484
485impl<A: Asset> From<Handle<A>> for UntypedHandle {
486 fn from(value: Handle<A>) -> Self {
487 match value {
488 Handle::Strong(handle) => UntypedHandle::Strong(handle),
489 Handle::Weak(id) => UntypedHandle::Weak(id.into()),
490 }
491 }
492}
493
494impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
495 type Error = UntypedAssetConversionError;
496
497 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
498 let found = value.type_id();
499 let expected = TypeId::of::<A>();
500
501 if found != expected {
502 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
503 }
504
505 match value {
506 UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
507 UntypedHandle::Weak(id) => {
508 let Ok(id) = id.try_into() else {
509 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
510 };
511 Ok(Handle::Weak(id))
512 }
513 }
514 }
515}
516
517#[macro_export]
527macro_rules! weak_handle {
528 ($uuid:expr) => {{
529 $crate::Handle::Weak($crate::AssetId::Uuid {
530 uuid: $crate::uuid::uuid!($uuid),
531 })
532 }};
533}
534
535#[derive(Error, Debug, PartialEq, Clone)]
537#[non_exhaustive]
538pub enum UntypedAssetConversionError {
539 #[error(
541 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
542 )]
543 TypeIdMismatch {
544 expected: TypeId,
546 found: TypeId,
548 },
549}
550
551#[cfg(test)]
552mod tests {
553 use alloc::boxed::Box;
554 use bevy_platform::hash::FixedHasher;
555 use bevy_reflect::PartialReflect;
556 use core::hash::BuildHasher;
557
558 use super::*;
559
560 type TestAsset = ();
561
562 const UUID_1: Uuid = Uuid::from_u128(123);
563 const UUID_2: Uuid = Uuid::from_u128(456);
564
565 fn hash<T: Hash>(data: &T) -> u64 {
567 FixedHasher.hash_one(data)
568 }
569
570 #[test]
572 fn equality() {
573 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
574 let untyped = UntypedAssetId::Uuid {
575 type_id: TypeId::of::<TestAsset>(),
576 uuid: UUID_1,
577 };
578
579 let typed = Handle::Weak(typed);
580 let untyped = UntypedHandle::Weak(untyped);
581
582 assert_eq!(
583 Ok(typed.clone()),
584 Handle::<TestAsset>::try_from(untyped.clone())
585 );
586 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
587 assert_eq!(typed, untyped);
588 }
589
590 #[test]
592 #[expect(
593 clippy::cmp_owned,
594 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."
595 )]
596 fn ordering() {
597 assert!(UUID_1 < UUID_2);
598
599 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
600 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
601 let untyped_1 = UntypedAssetId::Uuid {
602 type_id: TypeId::of::<TestAsset>(),
603 uuid: UUID_1,
604 };
605 let untyped_2 = UntypedAssetId::Uuid {
606 type_id: TypeId::of::<TestAsset>(),
607 uuid: UUID_2,
608 };
609
610 let typed_1 = Handle::Weak(typed_1);
611 let typed_2 = Handle::Weak(typed_2);
612 let untyped_1 = UntypedHandle::Weak(untyped_1);
613 let untyped_2 = UntypedHandle::Weak(untyped_2);
614
615 assert!(typed_1 < typed_2);
616 assert!(untyped_1 < untyped_2);
617
618 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
619 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
620
621 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
622 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
623
624 assert!(typed_1 < untyped_2);
625 assert!(untyped_1 < typed_2);
626 }
627
628 #[test]
630 fn hashing() {
631 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
632 let untyped = UntypedAssetId::Uuid {
633 type_id: TypeId::of::<TestAsset>(),
634 uuid: UUID_1,
635 };
636
637 let typed = Handle::Weak(typed);
638 let untyped = UntypedHandle::Weak(untyped);
639
640 assert_eq!(
641 hash(&typed),
642 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
643 );
644 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
645 assert_eq!(hash(&typed), hash(&untyped));
646 }
647
648 #[test]
650 fn conversion() {
651 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
652 let untyped = UntypedAssetId::Uuid {
653 type_id: TypeId::of::<TestAsset>(),
654 uuid: UUID_1,
655 };
656
657 let typed = Handle::Weak(typed);
658 let untyped = UntypedHandle::Weak(untyped);
659
660 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
661 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
662 }
663
664 #[test]
666 fn strong_handle_reflect_clone() {
667 use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
668 use bevy_app::App;
669 use bevy_reflect::FromReflect;
670
671 #[derive(Reflect)]
672 struct MyAsset {
673 value: u32,
674 }
675 impl Asset for MyAsset {}
676 impl VisitAssetDependencies for MyAsset {
677 fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
678 }
679
680 let mut app = App::new();
681 app.add_plugins(AssetPlugin::default())
682 .init_asset::<MyAsset>();
683 let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
684
685 let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
686 match &handle {
687 Handle::Strong(strong) => {
688 assert_eq!(
689 Arc::strong_count(strong),
690 1,
691 "Inserting the asset should result in a strong count of 1"
692 );
693
694 let reflected: &dyn Reflect = &handle;
695 let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
696
697 assert_eq!(
698 Arc::strong_count(strong),
699 2,
700 "Cloning the handle with reflect should increase the strong count to 2"
701 );
702
703 let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
704
705 assert_eq!(
706 Arc::strong_count(strong),
707 3,
708 "Converting the handle to a dynamic should increase the strong count to 3"
709 );
710
711 let from_reflect_handle: Handle<MyAsset> =
712 FromReflect::from_reflect(&*dynamic_handle).unwrap();
713
714 assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
715 assert!(
716 from_reflect_handle.is_strong(),
717 "The cloned handle should still be strong"
718 );
719 }
720 _ => panic!("Expected a strong handle"),
721 }
722 }
723}