1use crate::{
2 Asset, AssetId, AssetIndex, AssetIndexAllocator, AssetPath, AssetServer, Assets,
3 ErasedAssetIndex, ReflectHandle, UntypedAssetId,
4};
5use alloc::sync::Arc;
6use bevy_ecs::template::{FromTemplate, SpecializeFromTemplate, Template, TemplateContext};
7use bevy_platform::{collections::Equivalent, sync::Mutex};
8use bevy_reflect::{enums::Enum, FromReflect, PartialReflect, Reflect, ReflectRef, TypePath};
9use core::{
10 any::TypeId,
11 hash::{Hash, Hasher},
12 marker::PhantomData,
13};
14use crossbeam_channel::{Receiver, Sender};
15use disqualified::ShortName;
16use thiserror::Error;
17use uuid::Uuid;
18
19#[derive(Clone)]
22pub struct AssetHandleProvider {
23 pub(crate) allocator: Arc<AssetIndexAllocator>,
24 pub(crate) drop_sender: Sender<DropEvent>,
25 pub(crate) drop_receiver: Receiver<DropEvent>,
26 pub(crate) type_id: TypeId,
27}
28
29#[derive(Debug)]
30pub(crate) struct DropEvent {
31 pub(crate) index: ErasedAssetIndex,
32 pub(crate) asset_server_managed: bool,
33}
34
35impl AssetHandleProvider {
36 pub(crate) fn new(type_id: TypeId, allocator: Arc<AssetIndexAllocator>) -> Self {
37 let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
38 Self {
39 type_id,
40 allocator,
41 drop_sender,
42 drop_receiver,
43 }
44 }
45
46 pub fn reserve_handle(&self) -> UntypedHandle {
49 let index = self.allocator.reserve();
50 UntypedHandle::Strong(self.get_handle(index, false, None))
51 }
52
53 pub(crate) fn get_handle(
54 &self,
55 index: AssetIndex,
56 asset_server_managed: bool,
57 path: Option<AssetPath<'static>>,
58 ) -> Arc<StrongHandle> {
59 Arc::new(StrongHandle {
60 index,
61 type_id: self.type_id,
62 drop_sender: self.drop_sender.clone(),
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 ) -> Arc<StrongHandle> {
73 let index = self.allocator.reserve();
74 self.get_handle(index, asset_server_managed, path)
75 }
76}
77
78#[derive(TypePath, Debug)]
81pub struct StrongHandle {
82 pub(crate) index: AssetIndex,
83 pub(crate) type_id: TypeId,
84 pub(crate) asset_server_managed: bool,
85 pub(crate) path: Option<AssetPath<'static>>,
86 pub(crate) drop_sender: Sender<DropEvent>,
87}
88
89impl Drop for StrongHandle {
90 fn drop(&mut self) {
91 let _ = self.drop_sender.send(DropEvent {
92 index: ErasedAssetIndex::new(self.index, self.type_id),
93 asset_server_managed: self.asset_server_managed,
94 });
95 }
96}
97
98#[derive(Reflect)]
114#[reflect(Debug, Hash, PartialEq, Clone, Handle, from_reflect = false)]
115pub enum Handle<A: Asset> {
116 Strong(Arc<StrongHandle>),
119 Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
122}
123
124impl<A: Asset> FromReflect for Handle<A>
127where
128 Handle<A>: Send + Sync,
129 A: TypePath,
130{
131 fn from_reflect(reflect_value: &dyn PartialReflect) -> Option<Self> {
132 let ReflectRef::Enum(enum_value) = PartialReflect::reflect_ref(reflect_value) else {
133 return None;
134 };
135
136 match Enum::variant_name(enum_value) {
137 "Strong" => {
138 let strong_field = enum_value.field_at(0usize)?;
139 let strong_handle = Arc::<StrongHandle>::from_reflect(strong_field)?;
140
141 if strong_handle.type_id != TypeId::of::<A>() {
143 return None;
144 }
145
146 Some(Handle::Strong(strong_handle))
147 }
148 "Uuid" => {
149 let uuid_field = enum_value.field_at(0usize)?;
150 let uuid = Uuid::from_reflect(uuid_field)?;
151
152 Some(Handle::Uuid(uuid, Default::default()))
153 }
154 _ => None,
155 }
156 }
157}
158
159impl<T: Asset> Clone for Handle<T> {
160 fn clone(&self) -> Self {
161 match self {
162 Handle::Strong(handle) => Handle::Strong(handle.clone()),
163 Handle::Uuid(uuid, ..) => Handle::Uuid(*uuid, PhantomData),
164 }
165 }
166}
167
168impl<A: Asset> Handle<A> {
169 #[inline]
171 pub fn id(&self) -> AssetId<A> {
172 match self {
173 Handle::Strong(handle) => AssetId::Index {
174 index: handle.index,
175 marker: PhantomData,
176 },
177 Handle::Uuid(uuid, ..) => AssetId::Uuid { uuid: *uuid },
178 }
179 }
180
181 #[inline]
183 pub fn path(&self) -> Option<&AssetPath<'static>> {
184 match self {
185 Handle::Strong(handle) => handle.path.as_ref(),
186 Handle::Uuid(..) => None,
187 }
188 }
189
190 #[inline]
192 pub fn is_uuid(&self) -> bool {
193 matches!(self, Handle::Uuid(..))
194 }
195
196 #[inline]
198 pub fn is_strong(&self) -> bool {
199 matches!(self, Handle::Strong(_))
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::Uuid(AssetId::<A>::DEFAULT_UUID, PhantomData)
214 }
215}
216
217impl<T: Asset> Unpin for Handle<T> where for<'a> [()]: SpecializeFromTemplate {}
222
223impl<T: Asset> FromTemplate for Handle<T> {
224 type Template = HandleTemplate<T>;
225}
226
227#[derive(Reflect)]
252pub enum HandleTemplate<T: Asset> {
253 Path(AssetPath<'static>),
255 Handle(Handle<T>),
257 Value(ArcMutexValue<T>),
262}
263
264impl<T: Asset> HandleTemplate<T> {
265 pub fn value(value: impl Into<T>) -> Self {
271 HandleTemplate::Value(ArcMutexValue(Arc::new(Mutex::new(AssetOrHandle::Value(
272 Some(value.into()),
273 )))))
274 }
275}
276
277#[derive(Reflect)]
281#[reflect(opaque)]
282pub struct ArcMutexValue<T: Asset>(Arc<Mutex<AssetOrHandle<T>>>);
283
284impl<T: Asset> Clone for ArcMutexValue<T> {
285 fn clone(&self) -> Self {
286 Self(self.0.clone())
287 }
288}
289
290#[derive(Reflect)]
291enum AssetOrHandle<T: Asset> {
292 Value(Option<T>),
293 Handle(Handle<T>),
294}
295
296impl<T: Asset> Default for AssetOrHandle<T> {
297 fn default() -> Self {
298 Self::Handle(Default::default())
299 }
300}
301
302impl<T: Asset> Default for HandleTemplate<T> {
303 fn default() -> Self {
304 Self::Handle(Default::default())
305 }
306}
307
308impl<I: Into<AssetPath<'static>>, T: Asset> From<I> for HandleTemplate<T> {
309 fn from(value: I) -> Self {
310 Self::Path(value.into())
311 }
312}
313
314impl<T: Asset> From<Handle<T>> for HandleTemplate<T> {
315 fn from(value: Handle<T>) -> Self {
316 Self::Handle(value)
317 }
318}
319
320impl<T: Asset> Template for HandleTemplate<T> {
321 type Output = Handle<T>;
322 fn build_template(&self, context: &mut TemplateContext) -> bevy_ecs::error::Result<Handle<T>> {
323 Ok(match self {
324 HandleTemplate::Path(asset_path) => context.resource::<AssetServer>().load(asset_path),
325 HandleTemplate::Handle(handle) => handle.clone(),
326 HandleTemplate::Value(value) => {
327 let mut value_or_handle = value.0.lock().unwrap();
330 match &mut *value_or_handle {
331 AssetOrHandle::Value(value) => {
332 let handle = context
335 .resource_mut::<Assets<T>>()
336 .add(value.take().unwrap());
337 *value_or_handle = AssetOrHandle::Handle(handle.clone());
338 handle
339 }
340 AssetOrHandle::Handle(handle) => handle.clone(),
341 }
342 }
343 })
344 }
345
346 fn clone_template(&self) -> Self {
347 match self {
348 HandleTemplate::Path(asset_path) => HandleTemplate::Path(asset_path.clone()),
349 HandleTemplate::Handle(handle) => HandleTemplate::Handle(handle.clone()),
350 HandleTemplate::Value(value) => HandleTemplate::Value(value.clone()),
351 }
352 }
353}
354
355pub fn asset_value<I: Into<A>, A: Asset>(asset: I) -> HandleTemplate<A> {
361 HandleTemplate::value(asset)
362}
363
364impl<A: Asset> core::fmt::Debug for Handle<A> {
365 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
366 let name = ShortName::of::<A>();
367 match self {
368 Handle::Strong(handle) => {
369 write!(
370 f,
371 "StrongHandle<{name}>{{ index: {:?}, type_id: {:?}, path: {:?} }}",
372 handle.index, handle.type_id, handle.path
373 )
374 }
375 Handle::Uuid(uuid, ..) => write!(f, "UuidHandle<{name}>({uuid:?})"),
376 }
377 }
378}
379
380impl<A: Asset> Hash for Handle<A> {
381 #[inline]
382 fn hash<H: Hasher>(&self, state: &mut H) {
383 self.id().hash(state);
384 }
385}
386
387impl<T: Asset> Equivalent<Handle<T>> for AssetId<T> {
389 fn equivalent(&self, key: &Handle<T>) -> bool {
390 *self == key.id()
391 }
392}
393
394impl<A: Asset> PartialOrd for Handle<A> {
395 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
396 Some(self.cmp(other))
397 }
398}
399
400impl<A: Asset> Ord for Handle<A> {
401 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
402 self.id().cmp(&other.id())
403 }
404}
405
406impl<A: Asset> PartialEq for Handle<A> {
407 #[inline]
408 fn eq(&self, other: &Self) -> bool {
409 self.id() == other.id()
410 }
411}
412
413impl<A: Asset> Eq for Handle<A> {}
414
415impl<A: Asset> From<&Handle<A>> for AssetId<A> {
416 #[inline]
417 fn from(value: &Handle<A>) -> Self {
418 value.id()
419 }
420}
421
422impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
423 #[inline]
424 fn from(value: &Handle<A>) -> Self {
425 value.id().into()
426 }
427}
428
429impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
430 #[inline]
431 fn from(value: &mut Handle<A>) -> Self {
432 value.id()
433 }
434}
435
436impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
437 #[inline]
438 fn from(value: &mut Handle<A>) -> Self {
439 value.id().into()
440 }
441}
442
443impl<A: Asset> From<Uuid> for Handle<A> {
444 #[inline]
445 fn from(uuid: Uuid) -> Self {
446 Handle::Uuid(uuid, PhantomData)
447 }
448}
449
450#[derive(Clone, Reflect)]
456pub enum UntypedHandle {
457 Strong(Arc<StrongHandle>),
459 Uuid {
461 type_id: TypeId,
463 uuid: Uuid,
465 },
466}
467
468impl UntypedHandle {
469 pub fn default_for_type(type_id: TypeId) -> Self {
471 Self::Uuid {
472 type_id,
473 uuid: AssetId::<()>::DEFAULT_UUID,
474 }
475 }
476
477 #[inline]
479 pub fn id(&self) -> UntypedAssetId {
480 match self {
481 UntypedHandle::Strong(handle) => UntypedAssetId::Index {
482 type_id: handle.type_id,
483 index: handle.index,
484 },
485 UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
486 uuid: *uuid,
487 type_id: *type_id,
488 },
489 }
490 }
491
492 #[inline]
494 pub fn path(&self) -> Option<&AssetPath<'static>> {
495 match self {
496 UntypedHandle::Strong(handle) => handle.path.as_ref(),
497 UntypedHandle::Uuid { .. } => None,
498 }
499 }
500
501 #[inline]
503 pub fn type_id(&self) -> TypeId {
504 match self {
505 UntypedHandle::Strong(handle) => handle.type_id,
506 UntypedHandle::Uuid { type_id, .. } => *type_id,
507 }
508 }
509
510 #[inline]
512 pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
513 match self {
514 UntypedHandle::Strong(handle) => Handle::Strong(handle),
515 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
516 }
517 }
518
519 #[inline]
524 pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
525 debug_assert_eq!(
526 self.type_id(),
527 TypeId::of::<A>(),
528 "The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
529 );
530 self.typed_unchecked()
531 }
532
533 #[inline]
535 pub fn typed<A: Asset>(self) -> Handle<A> {
536 let Ok(handle) = self.try_typed() else {
537 panic!(
538 "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
539 core::any::type_name::<A>()
540 )
541 };
542
543 handle
544 }
545
546 #[inline]
548 pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
549 Handle::try_from(self)
550 }
551}
552
553impl PartialEq for UntypedHandle {
554 #[inline]
555 fn eq(&self, other: &Self) -> bool {
556 self.id() == other.id() && self.type_id() == other.type_id()
557 }
558}
559
560impl Eq for UntypedHandle {}
561
562impl Hash for UntypedHandle {
563 #[inline]
564 fn hash<H: Hasher>(&self, state: &mut H) {
565 self.id().hash(state);
566 }
567}
568
569impl core::fmt::Debug for UntypedHandle {
570 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
571 match self {
572 UntypedHandle::Strong(handle) => {
573 write!(
574 f,
575 "StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
576 handle.type_id, handle.index, handle.path
577 )
578 }
579 UntypedHandle::Uuid { type_id, uuid } => {
580 write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
581 }
582 }
583 }
584}
585
586impl PartialOrd for UntypedHandle {
587 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
588 if self.type_id() == other.type_id() {
589 self.id().partial_cmp(&other.id())
590 } else {
591 None
592 }
593 }
594}
595
596impl From<&UntypedHandle> for UntypedAssetId {
597 #[inline]
598 fn from(value: &UntypedHandle) -> Self {
599 value.id()
600 }
601}
602
603impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
606 #[inline]
607 fn eq(&self, other: &UntypedHandle) -> bool {
608 TypeId::of::<A>() == other.type_id() && self.id() == other.id()
609 }
610}
611
612impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
613 #[inline]
614 fn eq(&self, other: &Handle<A>) -> bool {
615 other.eq(self)
616 }
617}
618
619impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
620 #[inline]
621 fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
622 if TypeId::of::<A>() != other.type_id() {
623 None
624 } else {
625 self.id().partial_cmp(&other.id())
626 }
627 }
628}
629
630impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
631 #[inline]
632 fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
633 Some(other.partial_cmp(self)?.reverse())
634 }
635}
636
637impl<A: Asset> From<Handle<A>> for UntypedHandle {
638 fn from(value: Handle<A>) -> Self {
639 match value {
640 Handle::Strong(handle) => UntypedHandle::Strong(handle),
641 Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
642 type_id: TypeId::of::<A>(),
643 uuid,
644 },
645 }
646 }
647}
648
649impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
650 type Error = UntypedAssetConversionError;
651
652 fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
653 let found = value.type_id();
654 let expected = TypeId::of::<A>();
655
656 if found != expected {
657 return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
658 }
659
660 Ok(match value {
661 UntypedHandle::Strong(handle) => Handle::Strong(handle),
662 UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
663 })
664 }
665}
666
667#[macro_export]
677macro_rules! uuid_handle {
678 ($uuid:expr) => {{
679 $crate::Handle::Uuid($crate::uuid::uuid!($uuid), ::core::marker::PhantomData)
680 }};
681}
682
683#[deprecated = "Use uuid_handle! instead"]
684#[macro_export]
685macro_rules! weak_handle {
686 ($uuid:expr) => {
687 $crate::uuid_handle!($uuid)
688 };
689}
690
691#[derive(Error, Debug, PartialEq, Clone)]
693#[non_exhaustive]
694pub enum UntypedAssetConversionError {
695 #[error(
697 "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
698 )]
699 TypeIdMismatch {
700 expected: TypeId,
702 found: TypeId,
704 },
705}
706
707#[cfg(test)]
708mod tests {
709 use alloc::boxed::Box;
710 use bevy_platform::hash::FixedHasher;
711 use bevy_reflect::{FromReflect, PartialReflect};
712 use core::hash::BuildHasher;
713 use uuid::Uuid;
714
715 use crate::{tests::create_app, AssetApp, Assets, VisitAssetDependencies};
716
717 use super::*;
718
719 type TestAsset = ();
720
721 const UUID_1: Uuid = Uuid::from_u128(123);
722 const UUID_2: Uuid = Uuid::from_u128(456);
723
724 fn hash<T: Hash>(data: &T) -> u64 {
726 FixedHasher.hash_one(data)
727 }
728
729 #[test]
731 fn equality() {
732 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
733 let untyped = UntypedHandle::Uuid {
734 type_id: TypeId::of::<TestAsset>(),
735 uuid: UUID_1,
736 };
737
738 assert_eq!(
739 Ok(typed.clone()),
740 Handle::<TestAsset>::try_from(untyped.clone())
741 );
742 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
743 assert_eq!(typed, untyped);
744 }
745
746 #[test]
748 #[expect(
749 clippy::cmp_owned,
750 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."
751 )]
752 fn ordering() {
753 assert!(UUID_1 < UUID_2);
754
755 let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
756 let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
757 let untyped_1 = UntypedHandle::Uuid {
758 type_id: TypeId::of::<TestAsset>(),
759 uuid: UUID_1,
760 };
761 let untyped_2 = UntypedHandle::Uuid {
762 type_id: TypeId::of::<TestAsset>(),
763 uuid: UUID_2,
764 };
765
766 assert!(typed_1 < typed_2);
767 assert!(untyped_1 < untyped_2);
768
769 assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
770 assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
771
772 assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
773 assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
774
775 assert!(typed_1 < untyped_2);
776 assert!(untyped_1 < typed_2);
777 }
778
779 #[test]
781 fn hashing() {
782 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
783 let untyped = UntypedHandle::Uuid {
784 type_id: TypeId::of::<TestAsset>(),
785 uuid: UUID_1,
786 };
787
788 assert_eq!(
789 hash(&typed),
790 hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
791 );
792 assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
793 assert_eq!(hash(&typed), hash(&untyped));
794 }
795
796 #[test]
798 fn conversion() {
799 let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
800 let untyped = UntypedHandle::Uuid {
801 type_id: TypeId::of::<TestAsset>(),
802 uuid: UUID_1,
803 };
804
805 assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
806 assert_eq!(UntypedHandle::from(typed.clone()), untyped);
807 }
808
809 #[test]
810 fn from_uuid() {
811 let uuid = UUID_1;
812 let handle: Handle<TestAsset> = uuid.into();
813
814 assert!(handle.is_uuid());
815 assert_eq!(handle.id(), AssetId::Uuid { uuid });
816 }
817
818 #[test]
820 fn strong_handle_reflect_clone() {
821 #[derive(Reflect)]
822 struct MyAsset {
823 value: u32,
824 }
825 impl Asset for MyAsset {}
826 impl VisitAssetDependencies for MyAsset {
827 fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
828 }
829
830 let mut app = create_app().0;
831 app.init_asset::<MyAsset>();
832 let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
833
834 let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
835 match &handle {
836 Handle::Strong(strong) => {
837 assert_eq!(
838 Arc::strong_count(strong),
839 1,
840 "Inserting the asset should result in a strong count of 1"
841 );
842
843 let reflected: &dyn Reflect = &handle;
844 let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
845
846 assert_eq!(
847 Arc::strong_count(strong),
848 2,
849 "Cloning the handle with reflect should increase the strong count to 2"
850 );
851
852 let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic().unwrap();
853
854 assert_eq!(
855 Arc::strong_count(strong),
856 3,
857 "Converting the handle to a dynamic should increase the strong count to 3"
858 );
859
860 let from_reflect_handle: Handle<MyAsset> =
861 FromReflect::from_reflect(&*dynamic_handle).unwrap();
862
863 assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
864 assert!(
865 from_reflect_handle.is_strong(),
866 "The cloned handle should still be strong"
867 );
868 }
869 _ => panic!("Expected a strong handle"),
870 }
871 }
872
873 #[test]
874 fn handle_from_reflect_verifies_type_id() {
875 #[derive(Reflect, Asset)]
876 struct A;
877 #[derive(Reflect, Asset)]
878 struct B;
879
880 let mut app = create_app().0;
881 app.init_asset::<A>().init_asset::<B>();
882
883 let mut assets = app.world_mut().resource_mut::<Assets<A>>();
884 let handle_a = assets.add(A);
885
886 let dynamic_handle_a = handle_a.to_dynamic().unwrap();
887 let reflected_handle_a = handle_a.as_partial_reflect();
888
889 let handle_b_from_reflect_dynamic: Option<Handle<B>> =
890 FromReflect::from_reflect(&*dynamic_handle_a);
891 let handle_b_from_reflect: Option<Handle<B>> =
892 FromReflect::from_reflect(reflected_handle_a);
893 let handle_a_from_reflect: Option<Handle<A>> =
894 FromReflect::from_reflect(reflected_handle_a);
895 assert!(
896 handle_b_from_reflect.is_none(),
897 "Handle<B> should not be constructible from reflected Handle<A>"
898 );
899 assert!(
900 handle_b_from_reflect_dynamic.is_none(),
901 "Handle<B> should not be constructible from dynamic Handle<A>"
902 );
903 assert!(
904 handle_a_from_reflect.is_some(),
905 "Handle<A> should be constructible from reflected Handle<A>"
906 );
907 }
908
909 #[test]
910 #[ignore = "Known failure tracked in #24111"]
911 fn handle_try_apply_verifies_type_id() {
912 #[derive(Reflect, Asset)]
913 struct A;
914 #[derive(Reflect, Asset)]
915 struct B;
916
917 let mut app = create_app().0;
918 app.init_asset::<A>().init_asset::<B>();
919
920 let mut assets_a = app.world_mut().resource_mut::<Assets<A>>();
921 let handle_a = assets_a.add(A);
922
923 let reflected_handle_a = handle_a.as_partial_reflect();
924
925 let mut assets_b = app.world_mut().resource_mut::<Assets<B>>();
926 let mut handle_b = assets_b.add(B);
927 assert!(
928 handle_b.try_apply(reflected_handle_a).is_err(),
929 "Handle<A> should not be applicable to Handle<B>"
930 );
931 }
932
933 #[test]
934 fn handle_from_reflect_and_try_apply() {
935 #[derive(Reflect, Asset)]
936 struct A(i32);
937
938 let mut app = create_app().0;
939 app.init_asset::<A>();
940
941 let mut assets = app.world_mut().resource_mut::<Assets<A>>();
942 let handle_1 = assets.add(A(1));
943 let reflected_handle_1 = handle_1.as_partial_reflect();
944
945 let handle_1_from_reflect: Handle<A> =
946 FromReflect::from_reflect(reflected_handle_1).unwrap();
947 assert_eq!(handle_1, handle_1_from_reflect);
948
949 let mut handle_2 = assets.add(A(2));
950 assert_ne!(handle_1, handle_2);
951 handle_2.try_apply(reflected_handle_1).unwrap();
952 assert_eq!(handle_1, handle_2);
953 }
954}