use std::any::TypeId;
use bevy::{
asset::{AssetPath, ParseAssetPathError, io::AssetSourceId},
prelude::*,
reflect::{TypeRegistration, TypeRegistry},
};
use serde::Deserialize;
use super::processor::{MaterialProcessor, MaterialProcessorContext};
#[derive(TypePath, Clone)]
pub struct AssetLoadingProcessor<P: MaterialProcessor>(pub P);
impl<P: MaterialProcessor> MaterialProcessor for AssetLoadingProcessor<P> {
type Child = P;
fn child(&self) -> Option<&Self::Child> {
Some(&self.0)
}
fn try_deserialize<'de, D: serde::Deserializer<'de>>(
&self,
ctx: &mut MaterialProcessorContext,
registration: &TypeRegistration,
_registry: &TypeRegistry,
deserializer: D,
) -> Result<Result<Box<dyn PartialReflect>, D>, D::Error> {
if let Some(loader) = registration.data::<ReflectGenericMaterialSubAsset>() {
let path = String::deserialize(deserializer)?;
let path = relative_asset_path(ctx.load_context.path(), &path).map_err(serde::de::Error::custom)?;
return Ok(Ok(loader.load(ctx, path)));
}
Ok(Err(deserializer))
}
}
#[derive(Debug, Clone)]
pub struct ReflectGenericMaterialSubAsset {
load: fn(&mut MaterialProcessorContext, AssetPath<'static>) -> Box<dyn PartialReflect>,
}
impl ReflectGenericMaterialSubAsset {
pub fn load(&self, ctx: &mut MaterialProcessorContext, path: AssetPath<'static>) -> Box<dyn PartialReflect> {
(self.load)(ctx, path)
}
}
pub trait GenericMaterialSubAssetAppExt {
fn register_generic_material_sub_asset<A: Asset>(&mut self) -> &mut Self;
}
impl GenericMaterialSubAssetAppExt for App {
#[track_caller]
fn register_generic_material_sub_asset<A: Asset>(&mut self) -> &mut Self {
let mut type_registry = self.world().resource::<AppTypeRegistry>().write();
let registration = match type_registry.get_mut(TypeId::of::<Handle<A>>()) {
Some(x) => x,
None => panic!("Asset handle not registered: {}", std::any::type_name::<A>()),
};
registration.insert(ReflectGenericMaterialSubAsset {
load: |processor, path| Box::new(processor.load_context.load::<A>(path)),
});
drop(type_registry);
self
}
}
pub fn relative_asset_path(relative_to: &AssetPath<'static>, path: &str) -> Result<AssetPath<'static>, ParseAssetPathError> {
let parent = relative_to.parent().unwrap_or_default();
let root_pattern = ['/', '\\'];
if path.starts_with(root_pattern) {
let mut asset_path = AssetPath::try_parse(path.trim_start_matches(root_pattern))?.into_owned();
if let AssetSourceId::Default = asset_path.source() {
asset_path = asset_path.with_source(relative_to.source().clone_owned());
}
Ok(asset_path)
} else {
Ok(parent.resolve(&AssetPath::try_parse(path)?))
}
}