Path: blob/main/crates/bevy_asset/src/processor/process.rs
6604 views
use crate::{1io::{2AssetReaderError, AssetWriterError, MissingAssetWriterError,3MissingProcessedAssetReaderError, MissingProcessedAssetWriterError, SliceReader, Writer,4},5meta::{AssetAction, AssetMeta, AssetMetaDyn, ProcessDependencyInfo, ProcessedInfo, Settings},6processor::AssetProcessor,7saver::{AssetSaver, SavedAsset},8transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset},9AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset,10MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,11};12use alloc::{13borrow::ToOwned,14boxed::Box,15string::{String, ToString},16};17use bevy_tasks::{BoxedFuture, ConditionalSendFuture};18use core::marker::PhantomData;19use serde::{Deserialize, Serialize};20use thiserror::Error;2122/// Asset "processor" logic that reads input asset bytes (stored on [`ProcessContext`]), processes the value in some way,23/// and then writes the final processed bytes with [`Writer`]. The resulting bytes must be loadable with the given [`Process::OutputLoader`].24///25/// This is a "low level", maximally flexible interface. Most use cases are better served by the [`LoadTransformAndSave`] implementation26/// of [`Process`].27pub trait Process: Send + Sync + Sized + 'static {28/// The configuration / settings used to process the asset. This will be stored in the [`AssetMeta`] and is user-configurable per-asset.29type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;30/// The [`AssetLoader`] that will be used to load the final processed asset.31type OutputLoader: AssetLoader;32/// Processes the asset stored on `context` in some way using the settings stored on `meta`. The results are written to `writer`. The33/// final written processed asset is loadable using [`Process::OutputLoader`]. This load will use the returned [`AssetLoader::Settings`].34fn process(35&self,36context: &mut ProcessContext,37meta: AssetMeta<(), Self>,38writer: &mut Writer,39) -> impl ConditionalSendFuture<40Output = Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>,41>;42}4344/// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms45/// the `L` asset into an `S` [`AssetSaver`] asset using the `T` [`AssetTransformer`], and lastly saves the asset using the `S` [`AssetSaver`].46///47/// When creating custom processors, it is generally recommended to use the [`LoadTransformAndSave`] [`Process`] implementation,48/// as it encourages you to separate your code into an [`AssetLoader`] capable of loading assets without processing enabled,49/// an [`AssetTransformer`] capable of converting from an `L` asset to an `S` asset, and50/// an [`AssetSaver`] that allows you save any `S` asset. However you can51/// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary.52///53/// If your [`Process`] does not need to transform the [`Asset`], you can use [`IdentityAssetTransformer`] as `T`.54/// This will directly return the input [`Asset`], allowing your [`Process`] to directly load and then save an [`Asset`].55/// However, this pattern should only be used for cases such as file format conversion.56/// Otherwise, consider refactoring your [`AssetLoader`] and [`AssetSaver`] to isolate the transformation step into an explicit [`AssetTransformer`].57///58/// This uses [`LoadTransformAndSaveSettings`] to configure the processor.59///60/// [`Asset`]: crate::Asset61pub struct LoadTransformAndSave<62L: AssetLoader,63T: AssetTransformer<AssetInput = L::Asset>,64S: AssetSaver<Asset = T::AssetOutput>,65> {66transformer: T,67saver: S,68marker: PhantomData<fn() -> L>,69}7071impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S>72for LoadTransformAndSave<L, IdentityAssetTransformer<L::Asset>, S>73{74fn from(value: S) -> Self {75LoadTransformAndSave {76transformer: IdentityAssetTransformer::new(),77saver: value,78marker: PhantomData,79}80}81}8283/// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation.84///85/// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`],86/// and `SaverSettings` corresponds to [`AssetSaver::Settings`].87#[derive(Serialize, Deserialize, Default)]88pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {89/// The [`AssetLoader::Settings`] for [`LoadTransformAndSave`].90pub loader_settings: LoaderSettings,91/// The [`AssetTransformer::Settings`] for [`LoadTransformAndSave`].92pub transformer_settings: TransformerSettings,93/// The [`AssetSaver::Settings`] for [`LoadTransformAndSave`].94pub saver_settings: SaverSettings,95}9697impl<98L: AssetLoader,99T: AssetTransformer<AssetInput = L::Asset>,100S: AssetSaver<Asset = T::AssetOutput>,101> LoadTransformAndSave<L, T, S>102{103pub fn new(transformer: T, saver: S) -> Self {104LoadTransformAndSave {105transformer,106saver,107marker: PhantomData,108}109}110}111112/// An error that is encountered during [`Process::process`].113#[derive(Error, Debug)]114pub enum ProcessError {115#[error(transparent)]116MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),117#[error(transparent)]118MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),119#[error("The processor '{0}' does not exist")]120#[from(ignore)]121MissingProcessor(String),122#[error("Encountered an AssetReader error for '{path}': {err}")]123#[from(ignore)]124AssetReaderError {125path: AssetPath<'static>,126err: AssetReaderError,127},128#[error("Encountered an AssetWriter error for '{path}': {err}")]129#[from(ignore)]130AssetWriterError {131path: AssetPath<'static>,132err: AssetWriterError,133},134#[error(transparent)]135MissingAssetWriterError(#[from] MissingAssetWriterError),136#[error(transparent)]137MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),138#[error(transparent)]139MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),140#[error("Failed to read asset metadata for {path}: {err}")]141#[from(ignore)]142ReadAssetMetaError {143path: AssetPath<'static>,144err: AssetReaderError,145},146#[error(transparent)]147DeserializeMetaError(#[from] DeserializeMetaError),148#[error(transparent)]149AssetLoadError(#[from] AssetLoadError),150#[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]151WrongMetaType,152#[error("Encountered an error while saving the asset: {0}")]153#[from(ignore)]154AssetSaveError(Box<dyn core::error::Error + Send + Sync + 'static>),155#[error("Encountered an error while transforming the asset: {0}")]156#[from(ignore)]157AssetTransformError(Box<dyn core::error::Error + Send + Sync + 'static>),158#[error("Assets without extensions are not supported.")]159ExtensionRequired,160}161162impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>163where164Loader: AssetLoader,165Transformer: AssetTransformer<AssetInput = Loader::Asset>,166Saver: AssetSaver<Asset = Transformer::AssetOutput>,167{168type Settings =169LoadTransformAndSaveSettings<Loader::Settings, Transformer::Settings, Saver::Settings>;170type OutputLoader = Saver::OutputLoader;171172async fn process(173&self,174context: &mut ProcessContext<'_>,175meta: AssetMeta<(), Self>,176writer: &mut Writer,177) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {178let AssetAction::Process { settings, .. } = meta.asset else {179return Err(ProcessError::WrongMetaType);180};181let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {182loader: core::any::type_name::<Loader>().to_string(),183settings: settings.loader_settings,184});185let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(186context.load_source_asset(loader_meta).await?,187)188.unwrap();189190let post_transformed_asset = self191.transformer192.transform(pre_transformed_asset, &settings.transformer_settings)193.await194.map_err(|err| ProcessError::AssetTransformError(err.into()))?;195196let saved_asset =197SavedAsset::<Transformer::AssetOutput>::from_transformed(&post_transformed_asset);198199let output_settings = self200.saver201.save(writer, saved_asset, &settings.saver_settings)202.await203.map_err(|error| ProcessError::AssetSaveError(error.into()))?;204Ok(output_settings)205}206}207208/// A type-erased variant of [`Process`] that enables interacting with processor implementations without knowing209/// their type.210pub trait ErasedProcessor: Send + Sync {211/// Type-erased variant of [`Process::process`].212fn process<'a>(213&'a self,214context: &'a mut ProcessContext,215meta: Box<dyn AssetMetaDyn>,216writer: &'a mut Writer,217) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;218/// Deserialized `meta` as type-erased [`AssetMeta`], operating under the assumption that it matches the meta219/// for the underlying [`Process`] impl.220fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;221/// Returns the default type-erased [`AssetMeta`] for the underlying [`Process`] impl.222fn default_meta(&self) -> Box<dyn AssetMetaDyn>;223}224225impl<P: Process> ErasedProcessor for P {226fn process<'a>(227&'a self,228context: &'a mut ProcessContext,229meta: Box<dyn AssetMetaDyn>,230writer: &'a mut Writer,231) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {232Box::pin(async move {233let meta = meta234.downcast::<AssetMeta<(), P>>()235.map_err(|_e| ProcessError::WrongMetaType)?;236let loader_settings = <P as Process>::process(self, context, *meta, writer).await?;237let output_meta: Box<dyn AssetMetaDyn> =238Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {239loader: core::any::type_name::<P::OutputLoader>().to_string(),240settings: loader_settings,241}));242Ok(output_meta)243})244}245246fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {247let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;248Ok(Box::new(meta))249}250251fn default_meta(&self) -> Box<dyn AssetMetaDyn> {252Box::new(AssetMeta::<(), P>::new(AssetAction::Process {253processor: core::any::type_name::<P>().to_string(),254settings: P::Settings::default(),255}))256}257}258259/// Provides scoped data access to the [`AssetProcessor`].260/// This must only expose processor data that is represented in the asset's hash.261pub struct ProcessContext<'a> {262/// The "new" processed info for the final processed asset. It is [`ProcessContext`]'s263/// job to populate `process_dependencies` with any asset dependencies used to process264/// this asset (ex: loading an asset value from the [`AssetServer`] of the [`AssetProcessor`])265///266/// DO NOT CHANGE ANY VALUES HERE OTHER THAN APPENDING TO `process_dependencies`267///268/// Do not expose this publicly as it would be too easily to invalidate state.269///270/// [`AssetServer`]: crate::server::AssetServer271pub(crate) new_processed_info: &'a mut ProcessedInfo,272/// This exists to expose access to asset values (via the [`AssetServer`]).273///274/// ANY ASSET VALUE THAT IS ACCESSED SHOULD BE ADDED TO `new_processed_info.process_dependencies`275///276/// Do not expose this publicly as it would be too easily to invalidate state by forgetting to update277/// `process_dependencies`.278///279/// [`AssetServer`]: crate::server::AssetServer280processor: &'a AssetProcessor,281path: &'a AssetPath<'static>,282asset_bytes: &'a [u8],283}284285impl<'a> ProcessContext<'a> {286pub(crate) fn new(287processor: &'a AssetProcessor,288path: &'a AssetPath<'static>,289asset_bytes: &'a [u8],290new_processed_info: &'a mut ProcessedInfo,291) -> Self {292Self {293processor,294path,295asset_bytes,296new_processed_info,297}298}299300/// Load the source asset using the `L` [`AssetLoader`] and the passed in `meta` config.301/// This will take the "load dependencies" (asset values used when loading with `L`]) and302/// register them as "process dependencies" because they are asset values required to process the303/// current asset.304pub async fn load_source_asset<L: AssetLoader>(305&mut self,306meta: AssetMeta<L, ()>,307) -> Result<ErasedLoadedAsset, AssetLoadError> {308let server = &self.processor.server;309let loader_name = core::any::type_name::<L>();310let loader = server.get_asset_loader_with_type_name(loader_name).await?;311let mut reader = SliceReader::new(self.asset_bytes);312let loaded_asset = server313.load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true)314.await?;315for (path, full_hash) in &loaded_asset.loader_dependencies {316self.new_processed_info317.process_dependencies318.push(ProcessDependencyInfo {319full_hash: *full_hash,320path: path.to_owned(),321});322}323Ok(loaded_asset)324}325326/// The path of the asset being processed.327#[inline]328pub fn path(&self) -> &AssetPath<'static> {329self.path330}331332/// The source bytes of the asset being processed.333#[inline]334pub fn asset_bytes(&self) -> &[u8] {335self.asset_bytes336}337}338339340