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
271#[derive(Clone, Reflect)]
277pub enum UntypedHandle {
278 Strong(Arc<StrongHandle>),
280 Uuid {
282 type_id: TypeId,
284 uuid: Uuid,
286 },
287}
288
289impl UntypedHandle {
290 #[inline]
292 pub fn id(&self) -> UntypedAssetId {
293 match self {
294 UntypedHandle::Strong(handle) => handle.id,
295 UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
296 uuid: *uuid,
297 type_id: *type_id,
298 },
299 }
300 }
301
302 #[inline]
304 pub fn path(&self) -> Option<&AssetPath<'static>> {
305 match self {
306 UntypedHandle::Strong(handle) => handle.path.as_ref(),
307 UntypedHandle::Uuid { .. } => None,
308 }
309 }
310
311 #[inline]
313 pub fn type_id(&self) -> TypeId {
314 match self {
315 UntypedHandle::Strong(handle) => handle.id.type_id(),
316 UntypedHandle::Uuid { type_id, .. } => *type_id,
317 }
318 }
319
320 #[inline]
322 pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
323 match self {
324 UntypedHandle::Strong(handle) => Handle::Strong(handle),
325 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
326 }
327 }
328
329 #[inline]
334 pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
335 debug_assert_eq!(
336 self.type_id(),
337 TypeId::of::<A>(),
338 "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
339 );
340 self.typed_unchecked()
341 }
342
343 #[inline]
345 pub fn typed<A: Asset>(self) -> Handle<A> {
346 let Ok(handle) = self.try_typed() else {
347 panic!(
348 "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
349 core::any::type_name::<A>()
350 )
351 };
352
353 handle
354 }
355
356 #[inline]
358 pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
359 Handle::try_from(self)
360 }
361
362 #[inline]
365 pub fn meta_transform(&self) -> Option<&MetaTransform> {
366 match self {
367 UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
368 UntypedHandle::Uuid { .. } => None,
369 }
370 }
371}
372
373impl PartialEq for UntypedHandle {
374 #[inline]
375 fn eq(&self, other: &Self) -> bool {
376 self.id() == other.id() && self.type_id() == other.type_id()
377 }
378}
379
380impl Eq for UntypedHandle {}
381
382impl Hash for UntypedHandle {
383 #[inline]
384 fn hash<H: Hasher>(&self, state: &mut H) {
385 self.id().hash(state);
386 }
387}
388
389impl core::fmt::Debug for UntypedHandle {
390 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
391 match self {
392 UntypedHandle::Strong(handle) => {
393 write!(
394 f,
395 "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
396 handle.id.type_id(),
397 handle.id.internal(),
398 handle.path
399 )
400 }
401 UntypedHandle::Uuid { type_id, uuid } => {
402 write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
403 }
404 }
405 }
406}
407
408impl PartialOrd for UntypedHandle {
409 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
410 if self.type_id() == other.type_id() {
411 self.id().partial_cmp(&other.id())
412 } else {
413 None
414 }
415 }
416}
417
418impl From<&UntypedHandle> for UntypedAssetId {
419 #[inline]
420 fn from(value: &UntypedHandle) -> Self {
421 value.id()
422 }
423}
424
425impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
428 #[inline]
429 fn eq(&self, other: &UntypedHandle) -> bool {
430 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
431 }
432}
433
434impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
435 #[inline]
436 fn eq(&self, other: &Handle<A>) -> bool {
437 other.eq(self)
438 }
439}
440
441impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
442 #[inline]
443 fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
444 if TypeId::of::<A>() != other.type_id() {
445 None
446 } else {
447 self.id().partial_cmp(&other.id())
448 }
449 }
450}
451
452impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
453 #[inline]
454 fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
455 Some(other.partial_cmp(self)?.reverse())
456 }
457}
458
459impl<A: Asset> From<Handle<A>> for UntypedHandle {
460 fn from(value: Handle<A>) -> Self {
461 match value {
462 Handle::Strong(handle) => UntypedHandle::Strong(handle),
463 Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
464 type_id: TypeId::of::<A>(),
465 uuid,
466 },
467 }
468 }
469}
470
471impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
472 type Error = UntypedAssetConversionError;
473
474 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
475 let found = value.type_id();
476 let expected = TypeId::of::<A>();
477
478 if found != expected {
479 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
480 }
481
482 Ok(match value {
483 UntypedHandle::Strong(handle) => Handle::Strong(handle),
484 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
485 })
486 }
487}
488
489#[macro_export]
499macro_rules! uuid_handle {
500 ($uuid:expr) => {{
501 $crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
502 }};
503}
504
505#[deprecated = "Use uuid_handle! instead"]
506#[macro_export]
507macro_rules! weak_handle {
508 ($uuid:expr) => {
509 $crate::uuid_handle!($uuid)
510 };
511}
512
513#[derive(Error, Debug, PartialEq, Clone)]
515#[non_exhaustive]
516pub enum UntypedAssetConversionError {
517 #[error(
519 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
520 )]
521 TypeIdMismatch {
522 expected: TypeId,
524 found: TypeId,
526 },
527}
528
529#[cfg(test)]
530mod tests {
531 use alloc::boxed::Box;
532 use bevy_platform::hash::FixedHasher;
533 use bevy_reflect::PartialReflect;
534 use core::hash::BuildHasher;
535 use uuid::Uuid;
536
537 use super::*;
538
539 type TestAsset = ();
540
541 const UUID_1: Uuid = Uuid::from_u128(123);
542 const UUID_2: Uuid = Uuid::from_u128(456);
543
544 fn hash<T: Hash>(data: &T) -> u64 {
546 FixedHasher.hash_one(data)
547 }
548
549 #[test]
551 fn equality() {
552 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
553 let untyped = UntypedHandle::Uuid {
554 type_id: TypeId::of::<TestAsset>(),
555 uuid: UUID_1,
556 };
557
558 assert_eq!(
559 Ok(typed.clone()),
560 Handle::<TestAsset>::try_from(untyped.clone())
561 );
562 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
563 assert_eq!(typed, untyped);
564 }
565
566 #[test]
568 #[expect(
569 clippy::cmp_owned,
570 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."
571 )]
572 fn ordering() {
573 assert!(UUID_1 < UUID_2);
574
575 let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
576 let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
577 let untyped_1 = UntypedHandle::Uuid {
578 type_id: TypeId::of::<TestAsset>(),
579 uuid: UUID_1,
580 };
581 let untyped_2 = UntypedHandle::Uuid {
582 type_id: TypeId::of::<TestAsset>(),
583 uuid: UUID_2,
584 };
585
586 assert!(typed_1 < typed_2);
587 assert!(untyped_1 < untyped_2);
588
589 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
590 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
591
592 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
593 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
594
595 assert!(typed_1 < untyped_2);
596 assert!(untyped_1 < typed_2);
597 }
598
599 #[test]
601 fn hashing() {
602 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
603 let untyped = UntypedHandle::Uuid {
604 type_id: TypeId::of::<TestAsset>(),
605 uuid: UUID_1,
606 };
607
608 assert_eq!(
609 hash(&typed),
610 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
611 );
612 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
613 assert_eq!(hash(&typed), hash(&untyped));
614 }
615
616 #[test]
618 fn conversion() {
619 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
620 let untyped = UntypedHandle::Uuid {
621 type_id: TypeId::of::<TestAsset>(),
622 uuid: UUID_1,
623 };
624
625 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
626 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
627 }
628
629 #[test]
631 fn strong_handle_reflect_clone() {
632 use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
633 use bevy_app::App;
634 use bevy_reflect::FromReflect;
635
636 #[derive(Reflect)]
637 struct MyAsset {
638 value: u32,
639 }
640 impl Asset for MyAsset {}
641 impl VisitAssetDependencies for MyAsset {
642 fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
643 }
644
645 let mut app = App::new();
646 app.add_plugins(AssetPlugin::default())
647 .init_asset::<MyAsset>();
648 let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
649
650 let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
651 match &handle {
652 Handle::Strong(strong) => {
653 assert_eq!(
654 Arc::strong_count(strong),
655 1,
656 "Inserting the asset should result in a strong count of 1"
657 );
658
659 let reflected: &dyn Reflect = &handle;
660 let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
661
662 assert_eq!(
663 Arc::strong_count(strong),
664 2,
665 "Cloning the handle with reflect should increase the strong count to 2"
666 );
667
668 let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
669
670 assert_eq!(
671 Arc::strong_count(strong),
672 3,
673 "Converting the handle to a dynamic should increase the strong count to 3"
674 );
675
676 let from_reflect_handle: Handle<MyAsset> =
677 FromReflect::from_reflect(&*dynamic_handle).unwrap();
678
679 assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
680 assert!(
681 from_reflect_handle.is_strong(),
682 "The cloned handle should still be strong"
683 );
684 }
685 _ => panic!("Expected a strong handle"),
686 }
687 }
688}