use std::{borrow::Cow, fmt};
use bevy::{
asset::{AssetPath, LoadContext, UntypedAssetId},
prelude::*,
reflect::{GetTypeRegistration, ReflectMut, Typed},
};
pub trait ErasedMaterial: Send + Sync + Reflect + Struct {
fn add_labeled_asset(self: Box<Self>, load_context: &mut LoadContext, label: String) -> ErasedMaterialHandle;
fn add_asset(self: Box<Self>, asset_server: &AssetServer) -> ErasedMaterialHandle;
fn clone_erased(&self) -> Box<dyn ErasedMaterial>;
}
impl<M: Material + Reflect + Struct + Clone> ErasedMaterial for M {
fn add_labeled_asset(self: Box<Self>, load_context: &mut LoadContext, label: String) -> ErasedMaterialHandle {
load_context.add_labeled_asset(label, *self).into()
}
fn add_asset(self: Box<Self>, asset_server: &AssetServer) -> ErasedMaterialHandle {
asset_server.add(*self).into()
}
fn clone_erased(&self) -> Box<dyn ErasedMaterial> {
Box::new(self.clone())
}
}
impl<M: Material + Reflect + Struct + Clone> From<M> for Box<dyn ErasedMaterial> {
fn from(value: M) -> Self {
Box::new(value)
}
}
impl Clone for Box<dyn ErasedMaterial> {
fn clone(&self) -> Self {
self.clone_erased()
}
}
#[derive(Clone)]
pub struct ErasedMaterialHandle {
inner: UntypedHandle,
vtable: &'static ErasedMaterialHandleVTable,
}
#[allow(clippy::type_complexity)]
impl ErasedMaterialHandle {
pub fn new<M: Material + Reflect>(handle: Handle<M>) -> Self {
Self {
inner: handle.untyped(),
vtable: ErasedMaterialHandleVTable::of::<M>(),
}
}
#[inline]
pub fn inner(&self) -> &UntypedHandle {
&self.inner
}
#[inline]
pub fn take_inner(self) -> UntypedHandle {
self.inner
}
#[inline]
pub fn id(&self) -> UntypedAssetId {
self.inner.id()
}
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
self.inner.path()
}
#[inline]
pub fn insert(self, entity: EntityWorldMut) {
(self.vtable.insert)(self.inner, entity);
}
#[inline]
pub fn remove(&self, entity: EntityWorldMut) {
(self.vtable.remove)(entity);
}
#[inline]
pub fn get_from_world<'w>(&self, world: &'w World) -> Option<&'w dyn Reflect> {
(self.vtable.get_from_world)(self.id(), world)
}
#[inline]
pub fn asset_scope(&self, world: &mut World, f: Box<dyn FnOnce(&mut World, Option<&dyn Reflect>) + Send + Sync>) {
(self.vtable.asset_scope)(self.id(), world, f);
}
#[inline]
pub fn asset_scope_mut(&self, world: &mut World, f: Box<dyn FnOnce(&mut World, Option<&mut dyn Reflect>) + Send + Sync>) {
(self.vtable.asset_scope_mut)(self.id(), world, f);
}
pub fn modify_field<T: Reflect + Typed + FromReflect + GetTypeRegistration>(
&self,
world: &mut World,
field_name: impl Into<Cow<'static, str>>,
value: T,
) {
let field_name = field_name.into();
self.asset_scope_mut(
world,
Box::new(move |_, material| {
let Some(material) = material else { return };
let ReflectMut::Struct(s) = material.reflect_mut() else { return };
let Some(field) = s.field_mut(&field_name) else {
error!(
"Tried to modify field {field_name} of {}, but said field doesn't exist!",
s.reflect_short_type_path()
);
return;
};
let apply_result = if field.represents::<Option<T>>() {
field.try_apply(&Some(value))
} else {
field.try_apply(&value)
};
if let Err(err) = apply_result {
error!(
"Tried to modify field {field_name} of {}, but failed to apply: {err}",
s.reflect_short_type_path()
);
}
}),
);
}
}
#[allow(clippy::type_complexity)]
struct ErasedMaterialHandleVTable {
insert: fn(UntypedHandle, EntityWorldMut),
remove: fn(EntityWorldMut),
get_from_world: for<'w> fn(UntypedAssetId, &'w World) -> Option<&'w dyn Reflect>,
asset_scope: fn(UntypedAssetId, &mut World, Box<dyn FnOnce(&mut World, Option<&dyn Reflect>) + Send + Sync>),
asset_scope_mut: fn(UntypedAssetId, &mut World, Box<dyn FnOnce(&mut World, Option<&mut dyn Reflect>) + Send + Sync>),
}
impl ErasedMaterialHandleVTable {
fn of<M: Material + Reflect>() -> &'static Self {
&Self {
insert: |handle, mut entity| {
entity.insert(MeshMaterial3d::<M>(handle.typed_debug_checked()));
},
remove: |mut entity| {
entity.remove::<MeshMaterial3d<M>>();
},
get_from_world: |id, world| {
let asset: &dyn Reflect = world.get_resource::<Assets<M>>()?.get(id.typed_debug_checked())?;
Some(asset)
},
asset_scope: |id, world, f| {
world.resource_scope(|world, assets: Mut<'_, Assets<M>>| {
let asset = assets.get(id.typed_debug_checked());
let asset: Option<&dyn Reflect> = match asset {
Some(m) => Some(m),
None => None,
};
f(world, asset);
});
},
asset_scope_mut: |id, world, f| {
world.resource_scope(|world, mut assets: Mut<'_, Assets<M>>| {
let asset = assets.get_mut(id.typed_debug_checked());
let asset: Option<&mut dyn Reflect> = match asset {
Some(m) => Some(m.into_inner()),
None => None,
};
f(world, asset);
});
},
}
}
}
impl<M: Material + Reflect> From<Handle<M>> for ErasedMaterialHandle {
fn from(value: Handle<M>) -> Self {
Self::new(value)
}
}
impl fmt::Debug for ErasedMaterialHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}