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 derive_more::derive::{Display, Error};
13use disqualified::ShortName;
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)]
125#[reflect(Default, Debug, Hash, PartialEq)]
126pub enum Handle<A: Asset> {
127 Strong(Arc<StrongHandle>),
130 Weak(AssetId<A>),
133}
134
135impl<T: Asset> Clone for Handle<T> {
136 fn clone(&self) -> Self {
137 match self {
138 Handle::Strong(handle) => Handle::Strong(handle.clone()),
139 Handle::Weak(id) => Handle::Weak(*id),
140 }
141 }
142}
143
144impl<A: Asset> Handle<A> {
145 pub const fn weak_from_u128(value: u128) -> Self {
147 Handle::Weak(AssetId::Uuid {
148 uuid: Uuid::from_u128(value),
149 })
150 }
151
152 #[inline]
154 pub fn id(&self) -> AssetId<A> {
155 match self {
156 Handle::Strong(handle) => handle.id.typed_unchecked(),
157 Handle::Weak(id) => *id,
158 }
159 }
160
161 #[inline]
163 pub fn path(&self) -> Option<&AssetPath<'static>> {
164 match self {
165 Handle::Strong(handle) => handle.path.as_ref(),
166 Handle::Weak(_) => None,
167 }
168 }
169
170 #[inline]
172 pub fn is_weak(&self) -> bool {
173 matches!(self, Handle::Weak(_))
174 }
175
176 #[inline]
178 pub fn is_strong(&self) -> bool {
179 matches!(self, Handle::Strong(_))
180 }
181
182 #[inline]
184 pub fn clone_weak(&self) -> Self {
185 match self {
186 Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
187 Handle::Weak(id) => Handle::Weak(*id),
188 }
189 }
190
191 #[inline]
195 pub fn untyped(self) -> UntypedHandle {
196 self.into()
197 }
198}
199
200impl<A: Asset> Default for Handle<A> {
201 fn default() -> Self {
202 Handle::Weak(AssetId::default())
203 }
204}
205
206impl<A: Asset> core::fmt::Debug for Handle<A> {
207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208 let name = ShortName::of::<A>();
209 match self {
210 Handle::Strong(handle) => {
211 write!(
212 f,
213 "StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
214 handle.id.internal(),
215 handle.path
216 )
217 }
218 Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
219 }
220 }
221}
222
223impl<A: Asset> Hash for Handle<A> {
224 #[inline]
225 fn hash<H: Hasher>(&self, state: &mut H) {
226 self.id().hash(state);
227 }
228}
229
230impl<A: Asset> PartialOrd for Handle<A> {
231 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
232 Some(self.cmp(other))
233 }
234}
235
236impl<A: Asset> Ord for Handle<A> {
237 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
238 self.id().cmp(&other.id())
239 }
240}
241
242impl<A: Asset> PartialEq for Handle<A> {
243 #[inline]
244 fn eq(&self, other: &Self) -> bool {
245 self.id() == other.id()
246 }
247}
248
249impl<A: Asset> Eq for Handle<A> {}
250
251impl<A: Asset> From<&Handle<A>> for AssetId<A> {
252 #[inline]
253 fn from(value: &Handle<A>) -> Self {
254 value.id()
255 }
256}
257
258impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
259 #[inline]
260 fn from(value: &Handle<A>) -> Self {
261 value.id().into()
262 }
263}
264
265impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
266 #[inline]
267 fn from(value: &mut Handle<A>) -> Self {
268 value.id()
269 }
270}
271
272impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
273 #[inline]
274 fn from(value: &mut Handle<A>) -> Self {
275 value.id().into()
276 }
277}
278
279#[derive(Clone)]
285pub enum UntypedHandle {
286 Strong(Arc<StrongHandle>),
287 Weak(UntypedAssetId),
288}
289
290impl UntypedHandle {
291 #[inline]
293 pub fn id(&self) -> UntypedAssetId {
294 match self {
295 UntypedHandle::Strong(handle) => handle.id,
296 UntypedHandle::Weak(id) => *id,
297 }
298 }
299
300 #[inline]
302 pub fn path(&self) -> Option<&AssetPath<'static>> {
303 match self {
304 UntypedHandle::Strong(handle) => handle.path.as_ref(),
305 UntypedHandle::Weak(_) => None,
306 }
307 }
308
309 #[inline]
311 pub fn clone_weak(&self) -> UntypedHandle {
312 match self {
313 UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
314 UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
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::Weak(id) => 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::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
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 match self {
348 UntypedHandle::Strong(handle) => Handle::Strong(handle),
349 UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
350 }
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::Weak(_) => 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.id.type_id(),
407 handle.id.internal(),
408 handle.path
409 )
410 }
411 UntypedHandle::Weak(id) => write!(
412 f,
413 "WeakHandle{{ type_id: {:?}, id: {:?} }}",
414 id.type_id(),
415 id.internal()
416 ),
417 }
418 }
419}
420
421impl PartialOrd for UntypedHandle {
422 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
423 if self.type_id() == other.type_id() {
424 self.id().partial_cmp(&other.id())
425 } else {
426 None
427 }
428 }
429}
430
431impl From<&UntypedHandle> for UntypedAssetId {
432 #[inline]
433 fn from(value: &UntypedHandle) -> Self {
434 value.id()
435 }
436}
437
438impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
441 #[inline]
442 fn eq(&self, other: &UntypedHandle) -> bool {
443 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
444 }
445}
446
447impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
448 #[inline]
449 fn eq(&self, other: &Handle<A>) -> bool {
450 other.eq(self)
451 }
452}
453
454impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
455 #[inline]
456 fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
457 if TypeId::of::<A>() != other.type_id() {
458 None
459 } else {
460 self.id().partial_cmp(&other.id())
461 }
462 }
463}
464
465impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
466 #[inline]
467 fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
468 Some(other.partial_cmp(self)?.reverse())
469 }
470}
471
472impl<A: Asset> From<Handle<A>> for UntypedHandle {
473 fn from(value: Handle<A>) -> Self {
474 match value {
475 Handle::Strong(handle) => UntypedHandle::Strong(handle),
476 Handle::Weak(id) => UntypedHandle::Weak(id.into()),
477 }
478 }
479}
480
481impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
482 type Error = UntypedAssetConversionError;
483
484 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
485 let found = value.type_id();
486 let expected = TypeId::of::<A>();
487
488 if found != expected {
489 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
490 }
491
492 match value {
493 UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
494 UntypedHandle::Weak(id) => {
495 let Ok(id) = id.try_into() else {
496 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
497 };
498 Ok(Handle::Weak(id))
499 }
500 }
501 }
502}
503
504#[derive(Error, Display, Debug, PartialEq, Clone)]
506#[non_exhaustive]
507pub enum UntypedAssetConversionError {
508 #[display(
510 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
511 )]
512 TypeIdMismatch { expected: TypeId, found: TypeId },
513}
514
515#[cfg(test)]
516mod tests {
517 use bevy_reflect::PartialReflect;
518
519 use super::*;
520
521 type TestAsset = ();
522
523 const UUID_1: Uuid = Uuid::from_u128(123);
524 const UUID_2: Uuid = Uuid::from_u128(456);
525
526 fn hash<T: Hash>(data: &T) -> u64 {
528 let mut hasher = bevy_utils::AHasher::default();
529 data.hash(&mut hasher);
530 hasher.finish()
531 }
532
533 #[test]
535 fn equality() {
536 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
537 let untyped = UntypedAssetId::Uuid {
538 type_id: TypeId::of::<TestAsset>(),
539 uuid: UUID_1,
540 };
541
542 let typed = Handle::Weak(typed);
543 let untyped = UntypedHandle::Weak(untyped);
544
545 assert_eq!(
546 Ok(typed.clone()),
547 Handle::<TestAsset>::try_from(untyped.clone())
548 );
549 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
550 assert_eq!(typed, untyped);
551 }
552
553 #[allow(clippy::cmp_owned)]
555 #[test]
556 fn ordering() {
557 assert!(UUID_1 < UUID_2);
558
559 let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
560 let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
561 let untyped_1 = UntypedAssetId::Uuid {
562 type_id: TypeId::of::<TestAsset>(),
563 uuid: UUID_1,
564 };
565 let untyped_2 = UntypedAssetId::Uuid {
566 type_id: TypeId::of::<TestAsset>(),
567 uuid: UUID_2,
568 };
569
570 let typed_1 = Handle::Weak(typed_1);
571 let typed_2 = Handle::Weak(typed_2);
572 let untyped_1 = UntypedHandle::Weak(untyped_1);
573 let untyped_2 = UntypedHandle::Weak(untyped_2);
574
575 assert!(typed_1 < typed_2);
576 assert!(untyped_1 < untyped_2);
577
578 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
579 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
580
581 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
582 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
583
584 assert!(typed_1 < untyped_2);
585 assert!(untyped_1 < typed_2);
586 }
587
588 #[test]
590 fn hashing() {
591 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
592 let untyped = UntypedAssetId::Uuid {
593 type_id: TypeId::of::<TestAsset>(),
594 uuid: UUID_1,
595 };
596
597 let typed = Handle::Weak(typed);
598 let untyped = UntypedHandle::Weak(untyped);
599
600 assert_eq!(
601 hash(&typed),
602 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
603 );
604 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
605 assert_eq!(hash(&typed), hash(&untyped));
606 }
607
608 #[test]
610 fn conversion() {
611 let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
612 let untyped = UntypedAssetId::Uuid {
613 type_id: TypeId::of::<TestAsset>(),
614 uuid: UUID_1,
615 };
616
617 let typed = Handle::Weak(typed);
618 let untyped = UntypedHandle::Weak(untyped);
619
620 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
621 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
622 }
623
624 #[test]
626 fn strong_handle_reflect_clone() {
627 use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
628 use bevy_app::App;
629 use bevy_reflect::FromReflect;
630
631 #[derive(Reflect)]
632 struct MyAsset {
633 value: u32,
634 }
635 impl Asset for MyAsset {}
636 impl VisitAssetDependencies for MyAsset {
637 fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
638 }
639
640 let mut app = App::new();
641 app.add_plugins(AssetPlugin::default())
642 .init_asset::<MyAsset>();
643 let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
644
645 let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
646 match &handle {
647 Handle::Strong(strong) => {
648 assert_eq!(
649 Arc::strong_count(strong),
650 1,
651 "Inserting the asset should result in a strong count of 1"
652 );
653
654 let reflected: &dyn Reflect = &handle;
655 let cloned_handle: Box<dyn PartialReflect> = reflected.clone_value();
656
657 assert_eq!(
658 Arc::strong_count(strong),
659 2,
660 "Cloning the handle with reflect should increase the strong count to 2"
661 );
662
663 let from_reflect_handle: Handle<MyAsset> =
664 FromReflect::from_reflect(&*cloned_handle).unwrap();
665
666 assert_eq!(Arc::strong_count(strong), 3, "Converting the reflected value back to a handle should increase the strong count to 3");
667 assert!(
668 from_reflect_handle.is_strong(),
669 "The cloned handle should still be strong"
670 );
671 }
672 _ => panic!("Expected a strong handle"),
673 }
674 }
675}