use crate::{
io::{
AssetReaderError, AssetWriterError, MissingAssetWriterError,
MissingProcessedAssetReaderError, MissingProcessedAssetWriterError, Writer,
},
meta::{AssetAction, AssetMeta, AssetMetaDyn, ProcessDependencyInfo, ProcessedInfo, Settings},
processor::AssetProcessor,
saver::{AssetSaver, SavedAsset},
transformer::{AssetTransformer, TransformedAsset},
AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset,
MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,
};
use bevy_utils::BoxedFuture;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use thiserror::Error;
pub trait Process: Send + Sync + Sized + 'static {
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
type OutputLoader: AssetLoader;
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: AssetMeta<(), Self>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>>;
}
pub struct LoadTransformAndSave<
L: AssetLoader,
T: AssetTransformer<AssetInput = L::Asset>,
S: AssetSaver<Asset = T::AssetOutput>,
> {
transformer: T,
saver: S,
marker: PhantomData<fn() -> L>,
}
#[derive(Serialize, Deserialize, Default)]
pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {
pub loader_settings: LoaderSettings,
pub transformer_settings: TransformerSettings,
pub saver_settings: SaverSettings,
}
impl<
L: AssetLoader,
T: AssetTransformer<AssetInput = L::Asset>,
S: AssetSaver<Asset = T::AssetOutput>,
> LoadTransformAndSave<L, T, S>
{
pub fn new(transformer: T, saver: S) -> Self {
LoadTransformAndSave {
transformer,
saver,
marker: PhantomData,
}
}
}
pub struct LoadAndSave<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> {
saver: S,
marker: PhantomData<fn() -> L>,
}
impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S> for LoadAndSave<L, S> {
fn from(value: S) -> Self {
LoadAndSave {
saver: value,
marker: PhantomData,
}
}
}
#[derive(Serialize, Deserialize, Default)]
pub struct LoadAndSaveSettings<LoaderSettings, SaverSettings> {
pub loader_settings: LoaderSettings,
pub saver_settings: SaverSettings,
}
#[derive(Error, Debug)]
pub enum ProcessError {
#[error(transparent)]
MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
#[error(transparent)]
MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
#[error("The processor '{0}' does not exist")]
MissingProcessor(String),
#[error("Encountered an AssetReader error for '{path}': {err}")]
AssetReaderError {
path: AssetPath<'static>,
err: AssetReaderError,
},
#[error("Encountered an AssetWriter error for '{path}': {err}")]
AssetWriterError {
path: AssetPath<'static>,
err: AssetWriterError,
},
#[error(transparent)]
MissingAssetWriterError(#[from] MissingAssetWriterError),
#[error(transparent)]
MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
#[error(transparent)]
MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),
#[error("Failed to read asset metadata for {path}: {err}")]
ReadAssetMetaError {
path: AssetPath<'static>,
err: AssetReaderError,
},
#[error(transparent)]
DeserializeMetaError(#[from] DeserializeMetaError),
#[error(transparent)]
AssetLoadError(#[from] AssetLoadError),
#[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
WrongMetaType,
#[error("Encountered an error while saving the asset: {0}")]
AssetSaveError(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Encountered an error while transforming the asset: {0}")]
AssetTransformError(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Assets without extensions are not supported.")]
ExtensionRequired,
}
impl<
Loader: AssetLoader,
T: AssetTransformer<AssetInput = Loader::Asset>,
Saver: AssetSaver<Asset = T::AssetOutput>,
> Process for LoadTransformAndSave<Loader, T, Saver>
{
type Settings = LoadTransformAndSaveSettings<Loader::Settings, T::Settings, Saver::Settings>;
type OutputLoader = Saver::OutputLoader;
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: AssetMeta<(), Self>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>> {
Box::pin(async move {
let AssetAction::Process { settings, .. } = meta.asset else {
return Err(ProcessError::WrongMetaType);
};
let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {
loader: std::any::type_name::<Loader>().to_string(),
settings: settings.loader_settings,
});
let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(
context.load_source_asset(loader_meta).await?,
)
.unwrap();
let post_transformed_asset = self
.transformer
.transform(pre_transformed_asset, &settings.transformer_settings)
.await
.map_err(|err| ProcessError::AssetTransformError(err.into()))?;
let saved_asset =
SavedAsset::<T::AssetOutput>::from_transformed(&post_transformed_asset);
let output_settings = self
.saver
.save(writer, saved_asset, &settings.saver_settings)
.await
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings)
})
}
}
impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
for LoadAndSave<Loader, Saver>
{
type Settings = LoadAndSaveSettings<Loader::Settings, Saver::Settings>;
type OutputLoader = Saver::OutputLoader;
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: AssetMeta<(), Self>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>> {
Box::pin(async move {
let AssetAction::Process { settings, .. } = meta.asset else {
return Err(ProcessError::WrongMetaType);
};
let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {
loader: std::any::type_name::<Loader>().to_string(),
settings: settings.loader_settings,
});
let loaded_asset = context.load_source_asset(loader_meta).await?;
let saved_asset = SavedAsset::<Loader::Asset>::from_loaded(&loaded_asset).unwrap();
let output_settings = self
.saver
.save(writer, saved_asset, &settings.saver_settings)
.await
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings)
})
}
}
pub trait ErasedProcessor: Send + Sync {
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: Box<dyn AssetMetaDyn>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
}
impl<P: Process> ErasedProcessor for P {
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: Box<dyn AssetMetaDyn>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {
Box::pin(async move {
let meta = meta
.downcast::<AssetMeta<(), P>>()
.map_err(|_e| ProcessError::WrongMetaType)?;
let loader_settings = <P as Process>::process(self, context, *meta, writer).await?;
let output_meta: Box<dyn AssetMetaDyn> =
Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {
loader: std::any::type_name::<P::OutputLoader>().to_string(),
settings: loader_settings,
}));
Ok(output_meta)
})
}
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;
Ok(Box::new(meta))
}
fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
Box::new(AssetMeta::<(), P>::new(AssetAction::Process {
processor: std::any::type_name::<P>().to_string(),
settings: P::Settings::default(),
}))
}
}
pub struct ProcessContext<'a> {
pub(crate) new_processed_info: &'a mut ProcessedInfo,
processor: &'a AssetProcessor,
path: &'a AssetPath<'static>,
asset_bytes: &'a [u8],
}
impl<'a> ProcessContext<'a> {
pub(crate) fn new(
processor: &'a AssetProcessor,
path: &'a AssetPath<'static>,
asset_bytes: &'a [u8],
new_processed_info: &'a mut ProcessedInfo,
) -> Self {
Self {
processor,
path,
asset_bytes,
new_processed_info,
}
}
pub async fn load_source_asset<L: AssetLoader>(
&mut self,
meta: AssetMeta<L, ()>,
) -> Result<ErasedLoadedAsset, AssetLoadError> {
let server = &self.processor.server;
let loader_name = std::any::type_name::<L>();
let loader = server.get_asset_loader_with_type_name(loader_name).await?;
let loaded_asset = server
.load_with_meta_loader_and_reader(
self.path,
Box::new(meta),
&*loader,
&mut self.asset_bytes,
false,
true,
)
.await?;
for (path, full_hash) in &loaded_asset.loader_dependencies {
self.new_processed_info
.process_dependencies
.push(ProcessDependencyInfo {
full_hash: *full_hash,
path: path.to_owned(),
});
}
Ok(loaded_asset)
}
#[inline]
pub fn asset_bytes(&self) -> &[u8] {
self.asset_bytes
}
}