use alloc::boxed::Box;
use core::any::{Any, TypeId};
use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect};
use crate::{Asset, AssetId, Assets, Handle, UntypedAssetId, UntypedHandle};
#[derive(Clone)]
pub struct ReflectAsset {
handle_type_id: TypeId,
assets_resource_type_id: TypeId,
get: fn(&World, UntypedHandle) -> Option<&dyn Reflect>,
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedHandle) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
insert: fn(&mut World, UntypedHandle, &dyn PartialReflect),
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
remove: fn(&mut World, UntypedHandle) -> Option<Box<dyn Reflect>>,
}
impl ReflectAsset {
pub fn handle_type_id(&self) -> TypeId {
self.handle_type_id
}
pub fn assets_resource_type_id(&self) -> TypeId {
self.assets_resource_type_id
}
pub fn get<'w>(&self, world: &'w World, handle: UntypedHandle) -> Option<&'w dyn Reflect> {
(self.get)(world, handle)
}
pub fn get_mut<'w>(
&self,
world: &'w mut World,
handle: UntypedHandle,
) -> Option<&'w mut dyn Reflect> {
#[expect(
unsafe_code,
reason = "Use of unsafe `Self::get_unchecked_mut()` function."
)]
unsafe {
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), handle)
}
}
#[expect(
unsafe_code,
reason = "This function calls unsafe code and has safety requirements."
)]
pub unsafe fn get_unchecked_mut<'w>(
&self,
world: UnsafeWorldCell<'w>,
handle: UntypedHandle,
) -> Option<&'w mut dyn Reflect> {
unsafe { (self.get_unchecked_mut)(world, handle) }
}
pub fn add(&self, world: &mut World, value: &dyn PartialReflect) -> UntypedHandle {
(self.add)(world, value)
}
pub fn insert(&self, world: &mut World, handle: UntypedHandle, value: &dyn PartialReflect) {
(self.insert)(world, handle, value);
}
pub fn remove(&self, world: &mut World, handle: UntypedHandle) -> Option<Box<dyn Reflect>> {
(self.remove)(world, handle)
}
pub fn len(&self, world: &World) -> usize {
(self.len)(world)
}
pub fn is_empty(&self, world: &World) -> bool {
self.len(world) == 0
}
pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
(self.ids)(world)
}
}
impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
fn from_type() -> Self {
ReflectAsset {
handle_type_id: TypeId::of::<Handle<A>>(),
assets_resource_type_id: TypeId::of::<Assets<A>>(),
get: |world, handle| {
let assets = world.resource::<Assets<A>>();
let asset = assets.get(&handle.typed_debug_checked());
asset.map(|asset| asset as &dyn Reflect)
},
get_unchecked_mut: |world, handle| {
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
let asset = assets.get_mut(&handle.typed_debug_checked());
asset.map(|asset| asset as &mut dyn Reflect)
},
add: |world, value| {
let mut assets = world.resource_mut::<Assets<A>>();
let value: A = FromReflect::from_reflect(value)
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
assets.add(value).untyped()
},
insert: |world, handle, value| {
let mut assets = world.resource_mut::<Assets<A>>();
let value: A = FromReflect::from_reflect(value)
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
assets.insert(&handle.typed_debug_checked(), value);
},
len: |world| {
let assets = world.resource::<Assets<A>>();
assets.len()
},
ids: |world| {
let assets = world.resource::<Assets<A>>();
Box::new(assets.ids().map(AssetId::untyped))
},
remove: |world, handle| {
let mut assets = world.resource_mut::<Assets<A>>();
let value = assets.remove(&handle.typed_debug_checked());
value.map(|value| Box::new(value) as Box<dyn Reflect>)
},
}
}
}
#[derive(Clone)]
pub struct ReflectHandle {
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
}
impl ReflectHandle {
pub fn asset_type_id(&self) -> TypeId {
self.asset_type_id
}
pub fn downcast_handle_untyped(&self, handle: &dyn Any) -> Option<UntypedHandle> {
(self.downcast_handle_untyped)(handle)
}
pub fn typed(&self, handle: UntypedHandle) -> Box<dyn Reflect> {
(self.typed)(handle)
}
}
impl<A: Asset> FromType<Handle<A>> for ReflectHandle {
fn from_type() -> Self {
ReflectHandle {
asset_type_id: TypeId::of::<A>(),
downcast_handle_untyped: |handle: &dyn Any| {
handle
.downcast_ref::<Handle<A>>()
.map(|h| h.clone().untyped())
},
typed: |handle: UntypedHandle| Box::new(handle.typed_debug_checked::<A>()),
}
}
}
#[cfg(test)]
mod tests {
use alloc::{string::String, vec::Vec};
use core::any::TypeId;
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset, UntypedHandle};
use bevy_app::App;
use bevy_ecs::reflect::AppTypeRegistry;
use bevy_reflect::Reflect;
#[derive(Asset, Reflect)]
struct AssetType {
field: String,
}
#[test]
fn test_reflect_asset_operations() {
let mut app = App::new();
app.add_plugins(AssetPlugin::default())
.init_asset::<AssetType>()
.register_asset_reflect::<AssetType>();
let reflect_asset = {
let type_registry = app.world().resource::<AppTypeRegistry>();
let type_registry = type_registry.read();
type_registry
.get_type_data::<ReflectAsset>(TypeId::of::<AssetType>())
.unwrap()
.clone()
};
let value = AssetType {
field: "test".into(),
};
let handle = reflect_asset.add(app.world_mut(), &value);
let strukt = reflect_asset
.get_mut(app.world_mut(), handle)
.unwrap()
.reflect_mut()
.as_struct()
.unwrap();
strukt
.field_mut("field")
.unwrap()
.apply(&String::from("edited"));
assert_eq!(reflect_asset.len(app.world()), 1);
let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
assert_eq!(ids.len(), 1);
let fetched_handle = UntypedHandle::Weak(ids[0]);
let asset = reflect_asset
.get(app.world(), fetched_handle.clone_weak())
.unwrap();
assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
reflect_asset
.remove(app.world_mut(), fetched_handle)
.unwrap();
assert_eq!(reflect_asset.len(app.world()), 0);
}
}