1use crate::{
2 meta::MetaTransform, Asset, AssetId, AssetIndex, AssetIndexAllocator, AssetPath,
3 ErasedAssetIndex, 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) index: ErasedAssetIndex,
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(index, false, None, None))
49 }
50
51 pub(crate) fn get_handle(
52 &self,
53 index: AssetIndex,
54 asset_server_managed: bool,
55 path: Option<AssetPath<'static>>,
56 meta_transform: Option<MetaTransform>,
57 ) -> Arc<StrongHandle> {
58 Arc::new(StrongHandle {
59 index,
60 type_id: self.type_id,
61 drop_sender: self.drop_sender.clone(),
62 meta_transform,
63 path,
64 asset_server_managed,
65 })
66 }
67
68 pub(crate) fn reserve_handle_internal(
69 &self,
70 asset_server_managed: bool,
71 path: Option<AssetPath<'static>>,
72 meta_transform: Option<MetaTransform>,
73 ) -> Arc<StrongHandle> {
74 let index = self.allocator.reserve();
75 self.get_handle(index, asset_server_managed, path, meta_transform)
76 }
77}
78
79#[derive(TypePath)]
82pub struct StrongHandle {
83 pub(crate) index: AssetIndex,
84 pub(crate) type_id: TypeId,
85 pub(crate) asset_server_managed: bool,
86 pub(crate) path: Option<AssetPath<'static>>,
87 pub(crate) meta_transform: Option<MetaTransform>,
91 pub(crate) drop_sender: Sender<DropEvent>,
92}
93
94impl Drop for StrongHandle {
95 fn drop(&mut self) {
96 let _ = self.drop_sender.send(DropEvent {
97 index: ErasedAssetIndex::new(self.index, self.type_id),
98 asset_server_managed: self.asset_server_managed,
99 });
100 }
101}
102
103impl core::fmt::Debug for StrongHandle {
104 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 f.debug_struct("StrongHandle")
106 .field("index", &self.index)
107 .field("type_id", &self.type_id)
108 .field("asset_server_managed", &self.asset_server_managed)
109 .field("path", &self.path)
110 .field("drop_sender", &self.drop_sender)
111 .finish()
112 }
113}
114
115#[derive(Reflect)]
131#[reflect(Default, Debug, Hash, PartialEq, Clone)]
132pub enum Handle<A: Asset> {
133 Strong(Arc<StrongHandle>),
136 Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
139}
140
141impl<T: Asset> Clone for Handle<T> {
142 fn clone(&self) -> Self {
143 match self {
144 Handle::Strong(handle) => Handle::Strong(handle.clone()),
145 Handle::Uuid(uuid, ..) => Handle::Uuid(*uuid, PhantomData),
146 }
147 }
148}
149
150impl<A: Asset> Handle<A> {
151 #[inline]
153 pub fn id(&self) -> AssetId<A> {
154 match self {
155 Handle::Strong(handle) => AssetId::Index {
156 index: handle.index,
157 marker: PhantomData,
158 },
159 Handle::Uuid(uuid, ..) => AssetId::Uuid { uuid: *uuid },
160 }
161 }
162
163 #[inline]
165 pub fn path(&self) -> Option<&AssetPath<'static>> {
166 match self {
167 Handle::Strong(handle) => handle.path.as_ref(),
168 Handle::Uuid(..) => None,
169 }
170 }
171
172 #[inline]
174 pub fn is_uuid(&self) -> bool {
175 matches!(self, Handle::Uuid(..))
176 }
177
178 #[inline]
180 pub fn is_strong(&self) -> bool {
181 matches!(self, Handle::Strong(_))
182 }
183
184 #[inline]
188 pub fn untyped(self) -> UntypedHandle {
189 self.into()
190 }
191}
192
193impl<A: Asset> Default for Handle<A> {
194 fn default() -> Self {
195 Handle::Uuid(AssetId::<A>::DEFAULT_UUID, PhantomData)
196 }
197}
198
199impl<A: Asset> core::fmt::Debug for Handle<A> {
200 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
201 let name = ShortName::of::<A>();
202 match self {
203 Handle::Strong(handle) => {
204 write!(
205 f,
206 "StrongHandle<{name}>{{ index: {:?}, type_id: {:?}, path: {:?} }}",
207 handle.index, handle.type_id, 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) => UntypedAssetId::Index {
302 type_id: handle.type_id,
303 index: handle.index,
304 },
305 UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
306 uuid: *uuid,
307 type_id: *type_id,
308 },
309 }
310 }
311
312 #[inline]
314 pub fn path(&self) -> Option<&AssetPath<'static>> {
315 match self {
316 UntypedHandle::Strong(handle) => handle.path.as_ref(),
317 UntypedHandle::Uuid { .. } => None,
318 }
319 }
320
321 #[inline]
323 pub fn type_id(&self) -> TypeId {
324 match self {
325 UntypedHandle::Strong(handle) => handle.type_id,
326 UntypedHandle::Uuid { type_id, .. } => *type_id,
327 }
328 }
329
330 #[inline]
332 pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
333 match self {
334 UntypedHandle::Strong(handle) => Handle::Strong(handle),
335 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
336 }
337 }
338
339 #[inline]
344 pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
345 debug_assert_eq!(
346 self.type_id(),
347 TypeId::of::<A>(),
348 "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
349 );
350 self.typed_unchecked()
351 }
352
353 #[inline]
355 pub fn typed<A: Asset>(self) -> Handle<A> {
356 let Ok(handle) = self.try_typed() else {
357 panic!(
358 "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
359 core::any::type_name::<A>()
360 )
361 };
362
363 handle
364 }
365
366 #[inline]
368 pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
369 Handle::try_from(self)
370 }
371
372 #[inline]
375 pub fn meta_transform(&self) -> Option<&MetaTransform> {
376 match self {
377 UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
378 UntypedHandle::Uuid { .. } => None,
379 }
380 }
381}
382
383impl PartialEq for UntypedHandle {
384 #[inline]
385 fn eq(&self, other: &Self) -> bool {
386 self.id() == other.id() && self.type_id() == other.type_id()
387 }
388}
389
390impl Eq for UntypedHandle {}
391
392impl Hash for UntypedHandle {
393 #[inline]
394 fn hash<H: Hasher>(&self, state: &mut H) {
395 self.id().hash(state);
396 }
397}
398
399impl core::fmt::Debug for UntypedHandle {
400 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401 match self {
402 UntypedHandle::Strong(handle) => {
403 write!(
404 f,
405 "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
406 handle.type_id, handle.index, handle.path
407 )
408 }
409 UntypedHandle::Uuid { type_id, uuid } => {
410 write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
411 }
412 }
413 }
414}
415
416impl PartialOrd for UntypedHandle {
417 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
418 if self.type_id() == other.type_id() {
419 self.id().partial_cmp(&other.id())
420 } else {
421 None
422 }
423 }
424}
425
426impl From<&UntypedHandle> for UntypedAssetId {
427 #[inline]
428 fn from(value: &UntypedHandle) -> Self {
429 value.id()
430 }
431}
432
433impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
436 #[inline]
437 fn eq(&self, other: &UntypedHandle) -> bool {
438 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
439 }
440}
441
442impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
443 #[inline]
444 fn eq(&self, other: &Handle<A>) -> bool {
445 other.eq(self)
446 }
447}
448
449impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
450 #[inline]
451 fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
452 if TypeId::of::<A>() != other.type_id() {
453 None
454 } else {
455 self.id().partial_cmp(&other.id())
456 }
457 }
458}
459
460impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
461 #[inline]
462 fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
463 Some(other.partial_cmp(self)?.reverse())
464 }
465}
466
467impl<A: Asset> From<Handle<A>> for UntypedHandle {
468 fn from(value: Handle<A>) -> Self {
469 match value {
470 Handle::Strong(handle) => UntypedHandle::Strong(handle),
471 Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
472 type_id: TypeId::of::<A>(),
473 uuid,
474 },
475 }
476 }
477}
478
479impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
480 type Error = UntypedAssetConversionError;
481
482 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
483 let found = value.type_id();
484 let expected = TypeId::of::<A>();
485
486 if found != expected {
487 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
488 }
489
490 Ok(match value {
491 UntypedHandle::Strong(handle) => Handle::Strong(handle),
492 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
493 })
494 }
495}
496
497#[macro_export]
507macro_rules! uuid_handle {
508 ($uuid:expr) => {{
509 $crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
510 }};
511}
512
513#[deprecated = "Use uuid_handle! instead"]
514#[macro_export]
515macro_rules! weak_handle {
516 ($uuid:expr) => {
517 $crate::uuid_handle!($uuid)
518 };
519}
520
521#[derive(Error, Debug, PartialEq, Clone)]
523#[non_exhaustive]
524pub enum UntypedAssetConversionError {
525 #[error(
527 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
528 )]
529 TypeIdMismatch {
530 expected: TypeId,
532 found: TypeId,
534 },
535}
536
537#[cfg(test)]
538mod tests {
539 use alloc::boxed::Box;
540 use bevy_platform::hash::FixedHasher;
541 use bevy_reflect::PartialReflect;
542 use core::hash::BuildHasher;
543 use uuid::Uuid;
544
545 use super::*;
546
547 type TestAsset = ();
548
549 const UUID_1: Uuid = Uuid::from_u128(123);
550 const UUID_2: Uuid = Uuid::from_u128(456);
551
552 fn hash<T: Hash>(data: &T) -> u64 {
554 FixedHasher.hash_one(data)
555 }
556
557 #[test]
559 fn equality() {
560 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
561 let untyped = UntypedHandle::Uuid {
562 type_id: TypeId::of::<TestAsset>(),
563 uuid: UUID_1,
564 };
565
566 assert_eq!(
567 Ok(typed.clone()),
568 Handle::<TestAsset>::try_from(untyped.clone())
569 );
570 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
571 assert_eq!(typed, untyped);
572 }
573
574 #[test]
576 #[expect(
577 clippy::cmp_owned,
578 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."
579 )]
580 fn ordering() {
581 assert!(UUID_1 < UUID_2);
582
583 let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
584 let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
585 let untyped_1 = UntypedHandle::Uuid {
586 type_id: TypeId::of::<TestAsset>(),
587 uuid: UUID_1,
588 };
589 let untyped_2 = UntypedHandle::Uuid {
590 type_id: TypeId::of::<TestAsset>(),
591 uuid: UUID_2,
592 };
593
594 assert!(typed_1 < typed_2);
595 assert!(untyped_1 < untyped_2);
596
597 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
598 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
599
600 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
601 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
602
603 assert!(typed_1 < untyped_2);
604 assert!(untyped_1 < typed_2);
605 }
606
607 #[test]
609 fn hashing() {
610 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
611 let untyped = UntypedHandle::Uuid {
612 type_id: TypeId::of::<TestAsset>(),
613 uuid: UUID_1,
614 };
615
616 assert_eq!(
617 hash(&typed),
618 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
619 );
620 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
621 assert_eq!(hash(&typed), hash(&untyped));
622 }
623
624 #[test]
626 fn conversion() {
627 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
628 let untyped = UntypedHandle::Uuid {
629 type_id: TypeId::of::<TestAsset>(),
630 uuid: UUID_1,
631 };
632
633 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
634 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
635 }
636
637 #[test]
638 fn from_uuid() {
639 let uuid = UUID_1;
640 let handle: Handle<TestAsset> = uuid.into();
641
642 assert!(handle.is_uuid());
643 assert_eq!(handle.id(), AssetId::Uuid { uuid });
644 }
645
646 #[test]
648 fn strong_handle_reflect_clone() {
649 use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
650 use bevy_app::App;
651 use bevy_reflect::FromReflect;
652
653 #[derive(Reflect)]
654 struct MyAsset {
655 value: u32,
656 }
657 impl Asset for MyAsset {}
658 impl VisitAssetDependencies for MyAsset {
659 fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
660 }
661
662 let mut app = App::new();
663 app.add_plugins(AssetPlugin::default())
664 .init_asset::<MyAsset>();
665 let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
666
667 let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
668 match &handle {
669 Handle::Strong(strong) => {
670 assert_eq!(
671 Arc::strong_count(strong),
672 1,
673 "Inserting the asset should result in a strong count of 1"
674 );
675
676 let reflected: &dyn Reflect = &handle;
677 let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
678
679 assert_eq!(
680 Arc::strong_count(strong),
681 2,
682 "Cloning the handle with reflect should increase the strong count to 2"
683 );
684
685 let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
686
687 assert_eq!(
688 Arc::strong_count(strong),
689 3,
690 "Converting the handle to a dynamic should increase the strong count to 3"
691 );
692
693 let from_reflect_handle: Handle<MyAsset> =
694 FromReflect::from_reflect(&*dynamic_handle).unwrap();
695
696 assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
697 assert!(
698 from_reflect_handle.is_strong(),
699 "The cloned handle should still be strong"
700 );
701 }
702 _ => panic!("Expected a strong handle"),
703 }
704 }
705}