Path: blob/main/crates/bevy_asset/src/processor/process.rs
9357 views
use crate::{1io::{2AssetReaderError, AssetWriterError, MissingAssetWriterError,3MissingProcessedAssetReaderError, MissingProcessedAssetWriterError, Reader, 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},16vec::Vec,17};18use bevy_ecs::error::BevyError;19use bevy_reflect::TypePath;20use bevy_tasks::{BoxedFuture, ConditionalSendFuture};21use core::marker::PhantomData;22use serde::{Deserialize, Serialize};23use thiserror::Error;2425/// Asset "processor" logic that reads input asset bytes (stored on [`ProcessContext`]), processes the value in some way,26/// and then writes the final processed bytes with [`Writer`]. The resulting bytes must be loadable with the given [`Process::OutputLoader`].27///28/// This is a "low level", maximally flexible interface. Most use cases are better served by the [`LoadTransformAndSave`] implementation29/// of [`Process`].30pub trait Process: TypePath + Send + Sync + Sized + 'static {31/// The configuration / settings used to process the asset. This will be stored in the [`AssetMeta`] and is user-configurable per-asset.32type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;33/// The [`AssetLoader`] that will be used to load the final processed asset.34type OutputLoader: AssetLoader;35/// Processes the asset stored on `context` in some way using the settings stored on `meta`. The results are written to `writer`. The36/// final written processed asset is loadable using [`Process::OutputLoader`]. This load will use the returned [`AssetLoader::Settings`].37fn process(38&self,39context: &mut ProcessContext,40settings: &Self::Settings,41writer: &mut Writer,42) -> impl ConditionalSendFuture<43Output = Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>,44>;45}4647/// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms48/// the `L` asset into an `S` [`AssetSaver`] asset using the `T` [`AssetTransformer`], and lastly saves the asset using the `S` [`AssetSaver`].49///50/// When creating custom processors, it is generally recommended to use the [`LoadTransformAndSave`] [`Process`] implementation,51/// as it encourages you to separate your code into an [`AssetLoader`] capable of loading assets without processing enabled,52/// an [`AssetTransformer`] capable of converting from an `L` asset to an `S` asset, and53/// an [`AssetSaver`] that allows you save any `S` asset. However you can54/// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary.55///56/// If your [`Process`] does not need to transform the [`Asset`], you can use [`IdentityAssetTransformer`] as `T`.57/// This will directly return the input [`Asset`], allowing your [`Process`] to directly load and then save an [`Asset`].58/// However, this pattern should only be used for cases such as file format conversion.59/// Otherwise, consider refactoring your [`AssetLoader`] and [`AssetSaver`] to isolate the transformation step into an explicit [`AssetTransformer`].60///61/// This uses [`LoadTransformAndSaveSettings`] to configure the processor.62///63/// [`Asset`]: crate::Asset64#[derive(TypePath)]65pub struct LoadTransformAndSave<66L: AssetLoader,67T: AssetTransformer<AssetInput = L::Asset>,68S: AssetSaver<Asset = T::AssetOutput>,69> {70transformer: T,71saver: S,72marker: PhantomData<fn() -> L>,73}7475impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S>76for LoadTransformAndSave<L, IdentityAssetTransformer<L::Asset>, S>77{78fn from(value: S) -> Self {79LoadTransformAndSave {80transformer: IdentityAssetTransformer::new(),81saver: value,82marker: PhantomData,83}84}85}8687/// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation.88///89/// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`],90/// and `SaverSettings` corresponds to [`AssetSaver::Settings`].91#[derive(Serialize, Deserialize, Default)]92pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {93/// The [`AssetLoader::Settings`] for [`LoadTransformAndSave`].94pub loader_settings: LoaderSettings,95/// The [`AssetTransformer::Settings`] for [`LoadTransformAndSave`].96pub transformer_settings: TransformerSettings,97/// The [`AssetSaver::Settings`] for [`LoadTransformAndSave`].98pub saver_settings: SaverSettings,99}100101impl<102L: AssetLoader,103T: AssetTransformer<AssetInput = L::Asset>,104S: AssetSaver<Asset = T::AssetOutput>,105> LoadTransformAndSave<L, T, S>106{107pub fn new(transformer: T, saver: S) -> Self {108LoadTransformAndSave {109transformer,110saver,111marker: PhantomData,112}113}114}115116/// An error that is encountered during [`Process::process`].117#[derive(Error, Debug)]118pub enum ProcessError {119#[error(transparent)]120MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),121#[error(transparent)]122MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),123#[error("The processor '{0}' does not exist")]124#[from(ignore)]125MissingProcessor(String),126#[error("The processor '{processor_short_name}' is ambiguous between several processors: {ambiguous_processor_names:?}")]127AmbiguousProcessor {128processor_short_name: String,129ambiguous_processor_names: Vec<&'static str>,130},131#[error("Encountered an AssetReader error for '{path}': {err}")]132#[from(ignore)]133AssetReaderError {134path: AssetPath<'static>,135err: AssetReaderError,136},137#[error("Encountered an AssetWriter error for '{path}': {err}")]138#[from(ignore)]139AssetWriterError {140path: AssetPath<'static>,141err: AssetWriterError,142},143#[error(transparent)]144MissingAssetWriterError(#[from] MissingAssetWriterError),145#[error(transparent)]146MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),147#[error(transparent)]148MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),149#[error("Failed to read asset metadata for {path}: {err}")]150#[from(ignore)]151ReadAssetMetaError {152path: AssetPath<'static>,153err: AssetReaderError,154},155#[error(transparent)]156DeserializeMetaError(#[from] DeserializeMetaError),157#[error(transparent)]158AssetLoadError(#[from] AssetLoadError),159#[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]160WrongMetaType,161#[error("Encountered an error while saving the asset: {0}")]162#[from(ignore)]163AssetSaveError(BevyError),164#[error("Encountered an error while transforming the asset: {0}")]165#[from(ignore)]166AssetTransformError(Box<dyn core::error::Error + Send + Sync + 'static>),167#[error("Assets without extensions are not supported.")]168ExtensionRequired,169}170171impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>172where173Loader: AssetLoader,174Transformer: AssetTransformer<AssetInput = Loader::Asset>,175Saver: AssetSaver<Asset = Transformer::AssetOutput>,176{177type Settings =178LoadTransformAndSaveSettings<Loader::Settings, Transformer::Settings, Saver::Settings>;179type OutputLoader = Saver::OutputLoader;180181async fn process(182&self,183context: &mut ProcessContext<'_>,184settings: &Self::Settings,185writer: &mut Writer,186) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {187let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(188context189.load_source_asset::<Loader>(&settings.loader_settings)190.await?,191)192.unwrap();193194let post_transformed_asset = self195.transformer196.transform(pre_transformed_asset, &settings.transformer_settings)197.await198.map_err(|err| ProcessError::AssetTransformError(err.into()))?;199200let saved_asset =201SavedAsset::<Transformer::AssetOutput>::from_transformed(&post_transformed_asset);202203let output_settings = self204.saver205.save(writer, saved_asset, &settings.saver_settings)206.await207.map_err(|error| ProcessError::AssetSaveError(error.into()))?;208Ok(output_settings)209}210}211212/// A type-erased variant of [`Process`] that enables interacting with processor implementations without knowing213/// their type.214pub trait ErasedProcessor: Send + Sync {215/// Type-erased variant of [`Process::process`].216fn process<'a>(217&'a self,218context: &'a mut ProcessContext,219settings: &'a dyn Settings,220writer: &'a mut Writer,221) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;222/// Deserialized `meta` as type-erased [`AssetMeta`], operating under the assumption that it matches the meta223/// for the underlying [`Process`] impl.224fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;225/// Returns the type-path of the original [`Process`].226fn type_path(&self) -> &'static str;227/// Returns the default type-erased [`AssetMeta`] for the underlying [`Process`] impl.228fn default_meta(&self) -> Box<dyn AssetMetaDyn>;229}230231impl<P: Process> ErasedProcessor for P {232fn process<'a>(233&'a self,234context: &'a mut ProcessContext,235settings: &'a dyn Settings,236writer: &'a mut Writer,237) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {238Box::pin(async move {239let settings = settings.downcast_ref().ok_or(ProcessError::WrongMetaType)?;240let loader_settings = <P as Process>::process(self, context, settings, writer).await?;241let output_meta: Box<dyn AssetMetaDyn> =242Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {243loader: P::OutputLoader::type_path().to_string(),244settings: loader_settings,245}));246Ok(output_meta)247})248}249250fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {251let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;252Ok(Box::new(meta))253}254255fn type_path(&self) -> &'static str {256P::type_path()257}258259fn default_meta(&self) -> Box<dyn AssetMetaDyn> {260Box::new(AssetMeta::<(), P>::new(AssetAction::Process {261processor: P::type_path().to_string(),262settings: P::Settings::default(),263}))264}265}266267/// Provides scoped data access to the [`AssetProcessor`].268/// This must only expose processor data that is represented in the asset's hash.269pub struct ProcessContext<'a> {270/// The "new" processed info for the final processed asset. It is [`ProcessContext`]'s271/// job to populate `process_dependencies` with any asset dependencies used to process272/// this asset (ex: loading an asset value from the [`AssetServer`] of the [`AssetProcessor`])273///274/// DO NOT CHANGE ANY VALUES HERE OTHER THAN APPENDING TO `process_dependencies`275///276/// Do not expose this publicly as it would be too easily to invalidate state.277///278/// [`AssetServer`]: crate::server::AssetServer279pub(crate) new_processed_info: &'a mut ProcessedInfo,280/// This exists to expose access to asset values (via the [`AssetServer`]).281///282/// ANY ASSET VALUE THAT IS ACCESSED SHOULD BE ADDED TO `new_processed_info.process_dependencies`283///284/// Do not expose this publicly as it would be too easily to invalidate state by forgetting to update285/// `process_dependencies`.286///287/// [`AssetServer`]: crate::server::AssetServer288processor: &'a AssetProcessor,289path: &'a AssetPath<'static>,290reader: Box<dyn Reader + 'a>,291}292293impl<'a> ProcessContext<'a> {294pub(crate) fn new(295processor: &'a AssetProcessor,296path: &'a AssetPath<'static>,297reader: Box<dyn Reader + 'a>,298new_processed_info: &'a mut ProcessedInfo,299) -> Self {300Self {301processor,302path,303reader,304new_processed_info,305}306}307308/// Load the source asset using the `L` [`AssetLoader`] and the passed in `meta` config.309/// This will take the "load dependencies" (asset values used when loading with `L`]) and310/// register them as "process dependencies" because they are asset values required to process the311/// current asset.312pub async fn load_source_asset<L: AssetLoader>(313&mut self,314settings: &L::Settings,315) -> Result<ErasedLoadedAsset, AssetLoadError> {316let server = &self.processor.server;317let loader_name = L::type_path();318let loader = server.get_asset_loader_with_type_name(loader_name).await?;319let loaded_asset = server320.load_with_settings_loader_and_reader(321self.path,322settings,323&*loader,324&mut self.reader,325false,326true,327)328.await?;329for (path, full_hash) in &loaded_asset.loader_dependencies {330self.new_processed_info331.process_dependencies332.push(ProcessDependencyInfo {333full_hash: *full_hash,334path: path.to_owned(),335});336}337Ok(loaded_asset)338}339340/// The path of the asset being processed.341#[inline]342pub fn path(&self) -> &AssetPath<'static> {343self.path344}345346/// The reader for the asset being processed.347#[inline]348pub fn asset_reader(&mut self) -> &mut dyn Reader {349&mut self.reader350}351}352353354