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