1use alloc::{borrow::Cow, boxed::Box, format};
2use core::any::{Any, TypeId};
3use serde::{de::Error as _, ser::Error as _, Deserialize, Deserializer, Serialize};
4use thiserror::Error;
5use tracing::warn;
6use uuid::Uuid;
7
8use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
9use bevy_reflect::{
10 serde::{ReflectDeserializerProcessor, ReflectSerializerProcessor},
11 FromReflect, FromType, PartialReflect, Reflect, TypeRegistry,
12};
13
14use crate::{
15 Asset, AssetId, AssetPath, AssetServer, Assets, Handle, InvalidGenerationError, LoadContext,
16 UntypedAssetId, UntypedHandle,
17};
18
19#[derive(Clone)]
27pub struct ReflectAsset {
28 handle_type_id: TypeId,
29 assets_resource_type_id: TypeId,
30
31 get: fn(&World, UntypedAssetId) -> Option<&dyn Reflect>,
32 get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedAssetId) -> Option<&mut dyn Reflect>,
36 add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
37 insert:
38 fn(&mut World, UntypedAssetId, &dyn PartialReflect) -> Result<(), InvalidGenerationError>,
39 len: fn(&World) -> usize,
40 ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
41 remove: fn(&mut World, UntypedAssetId) -> Option<Box<dyn Reflect>>,
42}
43
44impl ReflectAsset {
45 pub fn handle_type_id(&self) -> TypeId {
47 self.handle_type_id
48 }
49
50 pub fn assets_resource_type_id(&self) -> TypeId {
52 self.assets_resource_type_id
53 }
54
55 pub fn get<'w>(
57 &self,
58 world: &'w World,
59 asset_id: impl Into<UntypedAssetId>,
60 ) -> Option<&'w dyn Reflect> {
61 (self.get)(world, asset_id.into())
62 }
63
64 pub fn get_mut<'w>(
66 &self,
67 world: &'w mut World,
68 asset_id: impl Into<UntypedAssetId>,
69 ) -> Option<&'w mut dyn Reflect> {
70 #[expect(
71 unsafe_code,
72 reason = "Use of unsafe `Self::get_unchecked_mut()` function."
73 )]
74 unsafe {
76 (self.get_unchecked_mut)(world.as_unsafe_world_cell(), asset_id.into())
77 }
78 }
79
80 #[expect(
108 unsafe_code,
109 reason = "This function calls unsafe code and has safety requirements."
110 )]
111 pub unsafe fn get_unchecked_mut<'w>(
112 &self,
113 world: UnsafeWorldCell<'w>,
114 asset_id: impl Into<UntypedAssetId>,
115 ) -> Option<&'w mut dyn Reflect> {
116 unsafe { (self.get_unchecked_mut)(world, asset_id.into()) }
118 }
119
120 pub fn add(&self, world: &mut World, value: &dyn PartialReflect) -> UntypedHandle {
122 (self.add)(world, value)
123 }
124 pub fn insert(
126 &self,
127 world: &mut World,
128 asset_id: impl Into<UntypedAssetId>,
129 value: &dyn PartialReflect,
130 ) -> Result<(), InvalidGenerationError> {
131 (self.insert)(world, asset_id.into(), value)
132 }
133
134 pub fn remove(
136 &self,
137 world: &mut World,
138 asset_id: impl Into<UntypedAssetId>,
139 ) -> Option<Box<dyn Reflect>> {
140 (self.remove)(world, asset_id.into())
141 }
142
143 pub fn len(&self, world: &World) -> usize {
145 (self.len)(world)
146 }
147
148 pub fn is_empty(&self, world: &World) -> bool {
150 self.len(world) == 0
151 }
152
153 pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
155 (self.ids)(world)
156 }
157}
158
159impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
160 fn from_type() -> Self {
161 ReflectAsset {
162 handle_type_id: TypeId::of::<Handle<A>>(),
163 assets_resource_type_id: TypeId::of::<Assets<A>>(),
164 get: |world, asset_id| {
165 let assets = world.resource::<Assets<A>>();
166 let asset = assets.get(asset_id.typed_debug_checked());
167 asset.map(|asset| asset as &dyn Reflect)
168 },
169 get_unchecked_mut: |world, asset_id| {
170 #[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
173 let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
174 let asset = assets.get_mut(asset_id.typed_debug_checked());
175 asset.map(|asset| asset.into_inner() as &mut dyn Reflect)
176 },
177 add: |world, value| {
178 let mut assets = world.resource_mut::<Assets<A>>();
179 let value: A = FromReflect::from_reflect(value)
180 .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
181 assets.add(value).untyped()
182 },
183 insert: |world, asset_id, value| {
184 let mut assets = world.resource_mut::<Assets<A>>();
185 let value: A = FromReflect::from_reflect(value)
186 .expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
187 assets.insert(asset_id.typed_debug_checked(), value)
188 },
189 len: |world| {
190 let assets = world.resource::<Assets<A>>();
191 assets.len()
192 },
193 ids: |world| {
194 let assets = world.resource::<Assets<A>>();
195 Box::new(assets.ids().map(AssetId::untyped))
196 },
197 remove: |world, asset_id| {
198 let mut assets = world.resource_mut::<Assets<A>>();
199 let value = assets.remove(asset_id.typed_debug_checked());
200 value.map(|value| Box::new(value) as Box<dyn Reflect>)
201 },
202 }
203 }
204}
205
206#[derive(Clone)]
232pub struct ReflectHandle {
233 asset_type_id: TypeId,
234 downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
235 typed: fn(UntypedHandle) -> Box<dyn Reflect>,
236}
237
238impl ReflectHandle {
239 pub fn asset_type_id(&self) -> TypeId {
241 self.asset_type_id
242 }
243
244 pub fn downcast_handle_untyped(&self, handle: &dyn Any) -> Option<UntypedHandle> {
246 (self.downcast_handle_untyped)(handle)
247 }
248
249 pub fn typed(&self, handle: UntypedHandle) -> Box<dyn Reflect> {
252 (self.typed)(handle)
253 }
254}
255
256impl<A: Asset> FromType<Handle<A>> for ReflectHandle {
257 fn from_type() -> Self {
258 ReflectHandle {
259 asset_type_id: TypeId::of::<A>(),
260 downcast_handle_untyped: |handle: &dyn Any| {
261 handle
262 .downcast_ref::<Handle<A>>()
263 .map(|h| h.clone().untyped())
264 },
265 typed: |handle: UntypedHandle| Box::new(handle.typed_debug_checked::<A>()),
266 }
267 }
268}
269
270pub struct HandleSerializeProcessor {
279 pub ephemeral_handle_behavior: EphemeralHandleBehavior,
281}
282
283#[derive(Clone, Copy, Debug)]
288pub enum EphemeralHandleBehavior {
289 Silent,
291 Warn,
293 Error,
295}
296
297impl ReflectSerializerProcessor for HandleSerializeProcessor {
298 fn try_serialize<S>(
299 &self,
300 value: &dyn PartialReflect,
301 registry: &TypeRegistry,
302 serializer: S,
303 ) -> Result<Result<S::Ok, S>, S::Error>
304 where
305 S: serde::Serializer,
306 {
307 let Some(value_reflect) = value.try_as_reflect() else {
308 return Ok(Err(serializer));
311 };
312
313 #[derive(Error, Debug)]
314 #[error("Attempted to serialize an ephemeral asset handle {0:?} while `EphemeralHandleBehavior::Error` is set")]
315 struct SerializingEphemeralHandleError(UntypedHandle);
316
317 fn handle_reference_from_handle(
318 handle: &UntypedHandle,
319 ephemeral_handle_behavior: EphemeralHandleBehavior,
320 ) -> Result<HandleReference, SerializingEphemeralHandleError> {
321 Ok(match &handle {
322 UntypedHandle::Strong(inner) => match &inner.path {
323 None => {
324 match ephemeral_handle_behavior {
325 EphemeralHandleBehavior::Silent => {}
326 EphemeralHandleBehavior::Warn => {
327 warn!("Serializing ephemeral handle {handle:?}. Ephemeral handles cannot be deserialized. Replacing with Handle::default");
328 }
329 EphemeralHandleBehavior::Error => {
330 return Err(SerializingEphemeralHandleError(handle.clone()))
331 }
332 }
333 HandleReference::Uuid(AssetId::<()>::DEFAULT_UUID)
334 }
335 Some(path) => HandleReference::Path(path.clone_owned()),
336 },
337 UntypedHandle::Uuid { uuid, .. } => HandleReference::Uuid(*uuid),
338 })
339 }
340
341 if let Some(untyped_handle) = value_reflect.downcast_ref::<UntypedHandle>() {
342 let Some(asset_registration) = registry.get(untyped_handle.type_id()) else {
343 return Err(S::Error::custom(format!(
344 "Missing type registration for asset type of handle {:?}. Ensure the asset implements Reflect, includes #[reflect(Asset)], and is registered",
345 untyped_handle
346 )));
347 };
348 return Ok(Ok(TypedHandleReference {
349 asset_type: asset_registration.type_info().type_path().into(),
350 reference: handle_reference_from_handle(
351 untyped_handle,
352 self.ephemeral_handle_behavior,
353 )
354 .map_err(S::Error::custom)?,
355 }
356 .serialize(serializer)?));
357 }
358
359 let Some(handle_registration) = registry.get(value_reflect.type_id()) else {
360 if let Some(type_info) = value_reflect.get_represented_type_info()
364 && type_info.type_path().starts_with("bevy_asset::Handle")
365 {
366 warn!(
367 "HandleSerializeProcessor attempted to serialize a handle type \"{}\" without type data. This likely means the asset type was not registered.",
368 type_info.type_path()
369 );
370 }
371 return Ok(Err(serializer));
373 };
374
375 let Some(reflect_handle) = handle_registration.data::<ReflectHandle>() else {
376 return Ok(Err(serializer));
379 };
380
381 let untyped_handle = reflect_handle
382 .downcast_handle_untyped(value_reflect.as_any())
383 .expect("type includes `ReflectHandle` type data, so it must be a handle matching that type");
384
385 let handle_reference =
386 handle_reference_from_handle(&untyped_handle, self.ephemeral_handle_behavior)
387 .map_err(S::Error::custom)?;
388
389 Ok(Ok(handle_reference.serialize(serializer)?))
390 }
391}
392
393pub trait LoadFromPath {
399 fn load_from_path_erased(&mut self, type_id: TypeId, path: AssetPath<'static>)
403 -> UntypedHandle;
404}
405
406impl LoadFromPath for LoadContext<'_> {
407 fn load_from_path_erased(
408 &mut self,
409 type_id: TypeId,
410 path: AssetPath<'static>,
411 ) -> UntypedHandle {
412 self.load_builder().load_erased(type_id, path)
413 }
414}
415
416impl LoadFromPath for AssetServer {
417 fn load_from_path_erased(
418 &mut self,
419 type_id: TypeId,
420 path: AssetPath<'static>,
421 ) -> UntypedHandle {
422 self.load_builder().load_erased(type_id, path)
423 }
424}
425
426impl LoadFromPath for &AssetServer {
427 fn load_from_path_erased(
428 &mut self,
429 type_id: TypeId,
430 path: AssetPath<'static>,
431 ) -> UntypedHandle {
432 self.load_builder().load_erased(type_id, path)
433 }
434}
435
436pub struct HandleDeserializeProcessor<'a> {
446 pub load_from_path: &'a mut dyn LoadFromPath,
448}
449
450impl ReflectDeserializerProcessor for HandleDeserializeProcessor<'_> {
451 fn try_deserialize<'de, D>(
452 &mut self,
453 registration: &bevy_reflect::TypeRegistration,
454 registry: &TypeRegistry,
455 deserializer: D,
456 ) -> Result<Result<Box<dyn PartialReflect>, D>, D::Error>
457 where
458 D: Deserializer<'de>,
459 {
460 if registration.type_id() == TypeId::of::<UntypedHandle>() {
461 let typed_handle_reference = TypedHandleReference::deserialize(deserializer)?;
462 let Some(asset_type) = registry.get_with_type_path(&typed_handle_reference.asset_type)
463 else {
464 return Err(D::Error::custom(format!(
465 "Could not find asset type by name \"{}\" for UntypedHandle",
466 &typed_handle_reference.asset_type
467 )));
468 };
469 let type_id = asset_type.type_id();
470 return Ok(Ok(Box::new(match typed_handle_reference.reference {
471 HandleReference::Path(path) => {
472 self.load_from_path.load_from_path_erased(type_id, path)
473 }
474 HandleReference::Uuid(uuid) => UntypedHandle::Uuid { type_id, uuid },
475 })));
476 }
477
478 let Some(reflect_handle) = registration.data::<ReflectHandle>() else {
479 return Ok(Err(deserializer));
484 };
485
486 let handle_reference = HandleReference::deserialize(deserializer)?;
487
488 let type_id = reflect_handle.asset_type_id;
489 Ok(Ok(reflect_handle.typed(match handle_reference {
490 HandleReference::Path(path) => self.load_from_path.load_from_path_erased(type_id, path),
491 HandleReference::Uuid(uuid) => UntypedHandle::Uuid { type_id, uuid },
492 })))
493 }
494}
495
496#[derive(Serialize, Deserialize)]
498pub enum HandleReference {
499 Path(AssetPath<'static>),
501 Uuid(Uuid),
503}
504
505#[derive(Serialize, Deserialize)]
507pub struct TypedHandleReference {
508 pub asset_type: Cow<'static, str>,
510 pub reference: HandleReference,
512}
513
514#[cfg(test)]
515mod tests {
516 use alloc::{string::String, vec, vec::Vec};
517 use core::any::TypeId;
518 use ron::ser::PrettyConfig;
519 use serde::de::DeserializeSeed;
520 use std::path::Path;
521 use uuid::Uuid;
522
523 use crate::{
524 tests::{create_app, run_app_until, CoolText, CoolTextLoader, CoolTextRon, SubText},
525 Asset, AssetApp, AssetServer, Assets, DirectAssetAccessExt, EphemeralHandleBehavior,
526 Handle, HandleDeserializeProcessor, HandleSerializeProcessor, LoadedUntypedAsset,
527 ReflectAsset, UntypedHandle,
528 };
529 use bevy_ecs::reflect::AppTypeRegistry;
530 use bevy_reflect::{
531 serde::{TypedReflectDeserializer, TypedReflectSerializer},
532 FromReflect, Reflect, TypePath,
533 };
534
535 #[derive(Asset, Reflect)]
536 struct AssetType {
537 field: String,
538 }
539
540 #[test]
541 fn test_reflect_asset_operations() {
542 let mut app = create_app().0;
543 app.init_asset::<AssetType>()
544 .register_asset_reflect::<AssetType>();
545
546 let reflect_asset = {
547 let type_registry = app.world().resource::<AppTypeRegistry>();
548 let type_registry = type_registry.read();
549
550 type_registry
551 .get_type_data::<ReflectAsset>(TypeId::of::<AssetType>())
552 .unwrap()
553 .clone()
554 };
555
556 let value = AssetType {
557 field: "test".into(),
558 };
559
560 let handle = reflect_asset.add(app.world_mut(), &value);
561 let strukt = reflect_asset
563 .get_mut(app.world_mut(), &handle)
564 .unwrap()
565 .reflect_mut()
566 .as_struct()
567 .unwrap();
568 strukt
569 .field_mut("field")
570 .unwrap()
571 .apply(&String::from("edited"));
572
573 assert_eq!(reflect_asset.len(app.world()), 1);
574 let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
575 assert_eq!(ids.len(), 1);
576 let id = ids[0];
577
578 let asset = reflect_asset.get(app.world(), id).unwrap();
579 assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
580
581 reflect_asset.remove(app.world_mut(), id).unwrap();
582 assert_eq!(reflect_asset.len(app.world()), 0);
583 }
584
585 fn serialize_as_cool_text(text: &str) -> String {
586 let cool_text_ron = CoolTextRon {
587 text: text.into(),
588 dependencies: vec![],
589 embedded_dependencies: vec![],
590 sub_texts: vec![],
591 };
592 ron::ser::to_string_pretty(&cool_text_ron, PrettyConfig::new().new_line("\n")).unwrap()
593 }
594
595 #[test]
596 fn roundtrip_reflect_serialize_handles() {
597 #[derive(Asset, TypePath)]
598 struct OtherAsset;
599
600 #[derive(Reflect)]
601 struct Stuff {
602 typed: Handle<CoolText>,
603 untyped: UntypedHandle,
604 uuid: Handle<OtherAsset>,
605 ephemeral: Handle<OtherAsset>,
606 }
607
608 let uuid = Uuid::from_u128(123);
609
610 let ron_data = {
612 let (mut app, dir) = create_app();
613 app.init_asset::<OtherAsset>()
614 .init_asset::<CoolText>()
615 .init_asset::<SubText>()
616 .register_asset_reflect::<CoolText>()
619 .register_type::<Stuff>()
620 .register_asset_loader(CoolTextLoader);
621
622 dir.insert_asset_text(Path::new("abc.cool.ron"), &serialize_as_cool_text("hello"));
623 dir.insert_asset_text(Path::new("def.cool.ron"), &serialize_as_cool_text("world"));
624
625 let type_registry = app.world().resource::<AppTypeRegistry>().0.clone();
626 let asset_server = app.world().resource::<AssetServer>().clone();
627
628 let untyped = asset_server.load_builder().load_untyped("def.cool.ron");
629 run_app_until(&mut app, |_| asset_server.is_loaded(&untyped).then_some(()));
630 let untyped = app
631 .world()
632 .resource::<Assets<LoadedUntypedAsset>>()
633 .get(&untyped)
634 .unwrap()
635 .handle
636 .clone();
637
638 let ephemeral = app.world_mut().add_asset(OtherAsset);
639
640 let stuff = Stuff {
641 typed: asset_server.load("abc.cool.ron"),
642 untyped,
643 uuid: uuid.into(),
644 ephemeral,
645 };
646
647 let type_registry = type_registry.read();
648 let processor = HandleSerializeProcessor {
649 ephemeral_handle_behavior: EphemeralHandleBehavior::Silent,
650 };
651 let reflect_serializer =
652 TypedReflectSerializer::with_processor(&stuff, &type_registry, &processor);
653
654 ron::to_string(&reflect_serializer).unwrap()
655 };
656
657 let (mut app, dir) = create_app();
659 app.init_asset::<OtherAsset>()
660 .init_asset::<CoolText>()
661 .init_asset::<SubText>()
662 .register_asset_reflect::<CoolText>()
664 .register_type::<Stuff>()
665 .register_asset_loader(CoolTextLoader);
666
667 dir.insert_asset_text(Path::new("abc.cool.ron"), &serialize_as_cool_text("hello"));
668 dir.insert_asset_text(Path::new("def.cool.ron"), &serialize_as_cool_text("world"));
669
670 let type_registry = app.world().resource::<AppTypeRegistry>().0.clone();
671 let mut asset_server = app.world().resource::<AssetServer>().clone();
672
673 let type_registry = type_registry.read();
674 let mut processor = HandleDeserializeProcessor {
675 load_from_path: &mut asset_server,
676 };
677 let reflect_deserializer = TypedReflectDeserializer::with_processor(
678 type_registry.get(TypeId::of::<Stuff>()).unwrap(),
679 &type_registry,
680 &mut processor,
681 );
682
683 let mut ron_deserializer = ron::Deserializer::from_str(&ron_data).unwrap();
684 let stuff = Stuff::from_reflect(
685 reflect_deserializer
686 .deserialize(&mut ron_deserializer)
687 .unwrap()
688 .as_ref(),
689 )
690 .unwrap();
691
692 assert_eq!(stuff.uuid, Handle::from(uuid));
694 assert_eq!(stuff.ephemeral, Handle::default());
696
697 run_app_until(&mut app, |_| {
699 (asset_server.is_loaded(&stuff.typed) && asset_server.is_loaded(&stuff.untyped))
700 .then_some(())
701 });
702
703 assert_eq!(
705 app.world()
706 .resource::<Assets<CoolText>>()
707 .get(&stuff.typed)
708 .unwrap()
709 .text,
710 "hello"
711 );
712 assert_eq!(
713 app.world()
714 .resource::<Assets<CoolText>>()
715 .get(&stuff.untyped.try_typed::<CoolText>().unwrap())
716 .unwrap()
717 .text,
718 "world"
719 );
720 }
721}