From 76682fdcb7aef33df43a3fbfeb508dfea2f25092 Mon Sep 17 00:00:00 2001 From: thepackett Date: Fri, 26 Jan 2024 14:20:58 -0600 Subject: [PATCH] AssetSaver and AssetTransformer split (#11260) # Objective One of a few Bevy Asset improvements I would like to make: #11216. Currently asset processing and asset saving are handled by the same trait, `AssetSaver`. This makes it difficult to reuse saving implementations and impossible to have a single "universal" saver for a given asset type. ## Solution This PR splits off the processing portion of `AssetSaver` into `AssetTransformer`, which is responsible for transforming assets. This change involves adding the `LoadTransformAndSave` processor, which utilizes the new API. The `LoadAndSave` still exists since it remains useful in situations where no "transformation" of the asset is done, such as when compressing assets. ## Notes: As an aside, Bikeshedding is welcome on the names. I'm not entirely convinced by `AssetTransformer`, which was chosen mostly because `AssetProcessor` is taken. Additionally, `LoadTransformSave` may be sufficient instead of `LoadTransformAndSave`. --- ## Changelog ### Added - `AssetTransformer` which is responsible for transforming Assets. - `LoadTransformAndSave`, a `Process` implementation. ### Changed - Changed `AssetSaver`'s responsibilities from processing and saving to just saving. - Updated `asset_processing` example to use new API. - Old asset .meta files regenerated with new processor. --- crates/bevy_asset/src/lib.rs | 1 + crates/bevy_asset/src/loader.rs | 8 +- crates/bevy_asset/src/processor/process.rs | 108 +++++++++++++++++- crates/bevy_asset/src/transformer.rs | 20 ++++ examples/asset/processing/asset_processing.rs | 46 +++++--- .../asset/processing/assets/a.cool.ron.meta | 5 +- examples/asset/processing/assets/d.cool.ron | 3 +- .../asset/processing/assets/d.cool.ron.meta | 5 +- .../processing/assets/foo/b.cool.ron.meta | 5 +- .../processing/assets/foo/c.cool.ron.meta | 5 +- 10 files changed, 174 insertions(+), 32 deletions(-) create mode 100644 crates/bevy_asset/src/transformer.rs diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 6d86737485..49703ff3ec 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -2,6 +2,7 @@ pub mod io; pub mod meta; pub mod processor; pub mod saver; +pub mod transformer; pub mod prelude { #[doc(hidden)] diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index da3468201e..b2ccc4350e 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -527,11 +527,11 @@ impl<'a> LoadContext<'a> { /// deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a /// "load dependency". /// - /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadAndSave`] preprocessor, + /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadTransformAndSave`] preprocessor, /// changing a "load dependency" will result in re-processing of the asset. /// /// [`Process`]: crate::processor::Process - /// [`LoadAndSave`]: crate::processor::LoadAndSave + /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave pub async fn load_direct<'b>( &mut self, path: impl Into>, @@ -575,11 +575,11 @@ impl<'a> LoadContext<'a> { /// For example, if you are deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a /// "load dependency". /// - /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadAndSave`] preprocessor, + /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadTransformAndSave`] preprocessor, /// changing a "load dependency" will result in re-processing of the asset. /// /// [`Process`]: crate::processor::Process - /// [`LoadAndSave`]: crate::processor::LoadAndSave + /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave pub async fn load_direct_with_reader<'b>( &mut self, reader: &mut Reader<'_>, diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index fd4f46630b..62fdd0c1fa 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -6,7 +6,8 @@ use crate::{ meta::{AssetAction, AssetMeta, AssetMetaDyn, ProcessDependencyInfo, ProcessedInfo, Settings}, processor::AssetProcessor, saver::{AssetSaver, SavedAsset}, - AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset, + transformer::AssetTransformer, + AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset, LoadedAsset, MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError, }; use bevy_utils::BoxedFuture; @@ -17,7 +18,7 @@ use thiserror::Error; /// Asset "processor" logic that reads input asset bytes (stored on [`ProcessContext`]), processes the value in some way, /// and then writes the final processed bytes with [`Writer`]. The resulting bytes must be loadable with the given [`Process::OutputLoader`]. /// -/// This is a "low level", maximally flexible interface. Most use cases are better served by the [`LoadAndSave`] implementation +/// This is a "low level", maximally flexible interface. Most use cases are better served by the [`LoadTransformAndSave`] implementation /// of [`Process`]. pub trait Process: Send + Sync + Sized + 'static { /// The configuration / settings used to process the asset. This will be stored in the [`AssetMeta`] and is user-configurable per-asset. @@ -34,13 +35,62 @@ pub trait Process: Send + Sync + Sized + 'static { ) -> BoxedFuture<'a, Result<::Settings, ProcessError>>; } +/// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms +/// the `L` asset into an `S` [`AssetSaver`] asset using the `T` [`AssetTransformer`], and lastly saves the asset using the `S` [`AssetSaver`]. +/// +/// When creating custom processors, it is generally recommended to use the [`LoadTransformAndSave`] [`Process`] implementation, +/// as it encourages you to separate your code into an [`AssetLoader`] capable of loading assets without processing enabled, +/// an [`AssetTransformer`] capable of converting from an `L` asset to an `S` asset, and +/// an [`AssetSaver`] that allows you save any `S` asset. However you can +/// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary. +/// +/// This uses [`LoadTransformAndSaveSettings`] to configure the processor. +/// +/// [`Asset`]: crate::Asset +pub struct LoadTransformAndSave< + L: AssetLoader, + T: AssetTransformer, + S: AssetSaver, +> { + transformer: T, + saver: S, + marker: PhantomData L>, +} + +/// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation. +/// +/// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`], +/// and `SaverSettings` corresponds to [`AssetSaver::Settings`]. +#[derive(Serialize, Deserialize, Default)] +pub struct LoadTransformAndSaveSettings { + /// The [`AssetLoader::Settings`] for [`LoadTransformAndSave`]. + pub loader_settings: LoaderSettings, + /// The [`AssetTransformer::Settings`] for [`LoadTransformAndSave`]. + pub transformer_settings: TransformerSettings, + /// The [`AssetSaver::Settings`] for [`LoadTransformAndSave`]. + pub saver_settings: SaverSettings, +} + +impl< + L: AssetLoader, + T: AssetTransformer, + S: AssetSaver, + > LoadTransformAndSave +{ + pub fn new(transformer: T, saver: S) -> Self { + LoadTransformAndSave { + transformer, + saver, + marker: PhantomData, + } + } +} + /// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then /// saves that `L` asset using the `S` [`AssetSaver`]. /// -/// When creating custom processors, it is generally recommended to use the [`LoadAndSave`] [`Process`] implementation, -/// as it encourages you to write both an [`AssetLoader`] capable of loading assets without processing enabled _and_ -/// an [`AssetSaver`] that allows you to efficiently process that asset type when that is desirable by users. However you can -/// also implement [`Process`] directly if [`LoadAndSave`] feels limiting or unnecessary. +/// This is a specialized use case of [`LoadTransformAndSave`] and is useful where there is no asset manipulation +/// such as when compressing assets. /// /// This uses [`LoadAndSaveSettings`] to configure the processor. /// @@ -112,6 +162,52 @@ pub enum ProcessError { ExtensionRequired, } +impl< + Loader: AssetLoader, + T: AssetTransformer, + Saver: AssetSaver, + > Process for LoadTransformAndSave +{ + type Settings = LoadTransformAndSaveSettings; + type OutputLoader = Saver::OutputLoader; + + fn process<'a>( + &'a self, + context: &'a mut ProcessContext, + meta: AssetMeta<(), Self>, + writer: &'a mut Writer, + ) -> BoxedFuture<'a, Result<::Settings, ProcessError>> { + Box::pin(async move { + let AssetAction::Process { settings, .. } = meta.asset else { + return Err(ProcessError::WrongMetaType); + }; + let loader_meta = AssetMeta::::new(AssetAction::Load { + loader: std::any::type_name::().to_string(), + settings: settings.loader_settings, + }); + let loaded_asset = context + .load_source_asset(loader_meta) + .await? + .take::() + .expect("Asset type is known"); + let transformed_asset = self + .transformer + .transform(loaded_asset, &settings.transformer_settings)?; + let loaded_transformed_asset = + ErasedLoadedAsset::from(LoadedAsset::from(transformed_asset)); + let saved_asset = + SavedAsset::::from_loaded(&loaded_transformed_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) + }) + } +} + impl> Process for LoadAndSave { diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs new file mode 100644 index 0000000000..13dcf648e0 --- /dev/null +++ b/crates/bevy_asset/src/transformer.rs @@ -0,0 +1,20 @@ +use crate::{meta::Settings, Asset}; +use serde::{Deserialize, Serialize}; + +/// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type. +pub trait AssetTransformer: Send + Sync + 'static { + /// The [`Asset`] type which this [`AssetTransformer`] takes as and input. + type AssetInput: Asset; + /// The [`Asset`] type which this [`AssetTransformer`] outputs. + type AssetOutput: Asset; + /// The settings type used by this [`AssetTransformer`]. + type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; + /// The type of [error](`std::error::Error`) which could be encountered by this saver. + type Error: Into>; + + fn transform<'a>( + &'a self, + asset: Self::AssetInput, + settings: &'a Self::Settings, + ) -> Result>; +} diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index 5947be2ab4..306b7ee5a2 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -1,12 +1,13 @@ -//! This example illustrates how to define custom `AssetLoader`s and `AssetSaver`s, how to configure them, and how to register asset processors. +//! This example illustrates how to define custom `AssetLoader`s, `AssetTransfomers`, and `AssetSaver`s, how to configure them, and how to register asset processors. use bevy::{ asset::{ embedded_asset, io::{Reader, Writer}, - processor::LoadAndSave, + processor::LoadTransformAndSave, ron, saver::{AssetSaver, SavedAsset}, + transformer::AssetTransformer, AssetLoader, AsyncReadExt, AsyncWriteExt, LoadContext, }, prelude::*, @@ -14,6 +15,7 @@ use bevy::{ utils::{thiserror, BoxedFuture}, }; use serde::{Deserialize, Serialize}; +use std::convert::Infallible; use thiserror::Error; fn main() { @@ -59,10 +61,10 @@ impl Plugin for TextPlugin { .init_asset::() .register_asset_loader(CoolTextLoader) .register_asset_loader(TextLoader) - .register_asset_processor::>( - LoadAndSave::from(CoolTextSaver), + .register_asset_processor::>( + LoadTransformAndSave::new(CoolTextTransformer, CoolTextSaver), ) - .set_default_asset_processor::>("cool.ron"); + .set_default_asset_processor::>("cool.ron"); } } @@ -133,9 +135,7 @@ enum CoolTextLoaderError { impl AssetLoader for CoolTextLoader { type Asset = CoolText; - type Settings = (); - type Error = CoolTextLoaderError; fn load<'a>( @@ -170,16 +170,37 @@ impl AssetLoader for CoolTextLoader { } } -struct CoolTextSaver; +#[derive(Default)] +struct CoolTextTransformer; #[derive(Default, Serialize, Deserialize)] -pub struct CoolTextSaverSettings { +pub struct CoolTextTransformerSettings { appended: String, } +impl AssetTransformer for CoolTextTransformer { + type AssetInput = CoolText; + type AssetOutput = CoolText; + type Settings = CoolTextTransformerSettings; + type Error = Infallible; + + fn transform<'a>( + &'a self, + asset: Self::AssetInput, + settings: &'a Self::Settings, + ) -> Result> { + Ok(CoolText { + text: format!("{}{}", asset.text, settings.appended), + dependencies: asset.dependencies.clone(), + }) + } +} + +struct CoolTextSaver; + impl AssetSaver for CoolTextSaver { type Asset = CoolText; - type Settings = CoolTextSaverSettings; + type Settings = (); type OutputLoader = TextLoader; type Error = std::io::Error; @@ -187,11 +208,10 @@ impl AssetSaver for CoolTextSaver { &'a self, writer: &'a mut Writer, asset: SavedAsset<'a, Self::Asset>, - settings: &'a Self::Settings, + _settings: &'a Self::Settings, ) -> BoxedFuture<'a, Result> { Box::pin(async move { - let text = format!("{}{}", asset.text.clone(), settings.appended); - writer.write_all(text.as_bytes()).await?; + writer.write_all(asset.text.as_bytes()).await?; Ok(TextSettings::default()) }) } diff --git a/examples/asset/processing/assets/a.cool.ron.meta b/examples/asset/processing/assets/a.cool.ron.meta index 7feb4d3a7b..d87c629bac 100644 --- a/examples/asset/processing/assets/a.cool.ron.meta +++ b/examples/asset/processing/assets/a.cool.ron.meta @@ -1,12 +1,13 @@ ( meta_format_version: "1.0", asset: Process( - processor: "bevy_asset::processor::process::LoadAndSave", + processor: "bevy_asset::processor::process::LoadTransformAndSave", settings: ( loader_settings: (), - saver_settings: ( + transformer_settings: ( appended: "X", ), + saver_settings: (), ), ), ) \ No newline at end of file diff --git a/examples/asset/processing/assets/d.cool.ron b/examples/asset/processing/assets/d.cool.ron index cfe835b258..12b8254c51 100644 --- a/examples/asset/processing/assets/d.cool.ron +++ b/examples/asset/processing/assets/d.cool.ron @@ -3,6 +3,7 @@ dependencies: [ ], embedded_dependencies: [ - "foo/c.cool.ron" + "foo/c.cool.ron", + "embedded://asset_processing/e.txt" ], ) \ No newline at end of file diff --git a/examples/asset/processing/assets/d.cool.ron.meta b/examples/asset/processing/assets/d.cool.ron.meta index c79e622562..7bc926f575 100644 --- a/examples/asset/processing/assets/d.cool.ron.meta +++ b/examples/asset/processing/assets/d.cool.ron.meta @@ -1,12 +1,13 @@ ( meta_format_version: "1.0", asset: Process( - processor: "bevy_asset::processor::process::LoadAndSave", + processor: "bevy_asset::processor::process::LoadTransformAndSave", settings: ( loader_settings: (), - saver_settings: ( + transformer_settings: ( appended: "", ), + saver_settings: (), ), ), ) \ No newline at end of file diff --git a/examples/asset/processing/assets/foo/b.cool.ron.meta b/examples/asset/processing/assets/foo/b.cool.ron.meta index c79e622562..7bc926f575 100644 --- a/examples/asset/processing/assets/foo/b.cool.ron.meta +++ b/examples/asset/processing/assets/foo/b.cool.ron.meta @@ -1,12 +1,13 @@ ( meta_format_version: "1.0", asset: Process( - processor: "bevy_asset::processor::process::LoadAndSave", + processor: "bevy_asset::processor::process::LoadTransformAndSave", settings: ( loader_settings: (), - saver_settings: ( + transformer_settings: ( appended: "", ), + saver_settings: (), ), ), ) \ No newline at end of file diff --git a/examples/asset/processing/assets/foo/c.cool.ron.meta b/examples/asset/processing/assets/foo/c.cool.ron.meta index c79e622562..7bc926f575 100644 --- a/examples/asset/processing/assets/foo/c.cool.ron.meta +++ b/examples/asset/processing/assets/foo/c.cool.ron.meta @@ -1,12 +1,13 @@ ( meta_format_version: "1.0", asset: Process( - processor: "bevy_asset::processor::process::LoadAndSave", + processor: "bevy_asset::processor::process::LoadTransformAndSave", settings: ( loader_settings: (), - saver_settings: ( + transformer_settings: ( appended: "", ), + saver_settings: (), ), ), ) \ No newline at end of file