mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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.
This commit is contained in:
parent
35ac1b152e
commit
76682fdcb7
10 changed files with 174 additions and 32 deletions
|
@ -2,6 +2,7 @@ pub mod io;
|
|||
pub mod meta;
|
||||
pub mod processor;
|
||||
pub mod saver;
|
||||
pub mod transformer;
|
||||
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
|
|
|
@ -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<AssetPath<'b>>,
|
||||
|
@ -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<'_>,
|
||||
|
|
|
@ -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<<Self::OutputLoader as AssetLoader>::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<AssetInput = L::Asset>,
|
||||
S: AssetSaver<Asset = T::AssetOutput>,
|
||||
> {
|
||||
transformer: T,
|
||||
saver: S,
|
||||
marker: PhantomData<fn() -> 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<LoaderSettings, TransformerSettings, SaverSettings> {
|
||||
/// 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<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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<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 loaded_asset = context
|
||||
.load_source_asset(loader_meta)
|
||||
.await?
|
||||
.take::<Loader::Asset>()
|
||||
.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::<T::AssetOutput>::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<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
|
||||
for LoadAndSave<Loader, Saver>
|
||||
{
|
||||
|
|
20
crates/bevy_asset/src/transformer.rs
Normal file
20
crates/bevy_asset/src/transformer.rs
Normal file
|
@ -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<Box<dyn std::error::Error + Send + Sync + 'static>>;
|
||||
|
||||
fn transform<'a>(
|
||||
&'a self,
|
||||
asset: Self::AssetInput,
|
||||
settings: &'a Self::Settings,
|
||||
) -> Result<Self::AssetOutput, Box<dyn std::error::Error + Send + Sync + 'static>>;
|
||||
}
|
|
@ -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::<Text>()
|
||||
.register_asset_loader(CoolTextLoader)
|
||||
.register_asset_loader(TextLoader)
|
||||
.register_asset_processor::<LoadAndSave<CoolTextLoader, CoolTextSaver>>(
|
||||
LoadAndSave::from(CoolTextSaver),
|
||||
.register_asset_processor::<LoadTransformAndSave<CoolTextLoader, CoolTextTransformer, CoolTextSaver>>(
|
||||
LoadTransformAndSave::new(CoolTextTransformer, CoolTextSaver),
|
||||
)
|
||||
.set_default_asset_processor::<LoadAndSave<CoolTextLoader, CoolTextSaver>>("cool.ron");
|
||||
.set_default_asset_processor::<LoadTransformAndSave<CoolTextLoader, CoolTextTransformer, CoolTextSaver>>("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<Self::AssetOutput, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
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<TextSettings, Self::Error>> {
|
||||
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())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
(
|
||||
meta_format_version: "1.0",
|
||||
asset: Process(
|
||||
processor: "bevy_asset::processor::process::LoadAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextSaver>",
|
||||
processor: "bevy_asset::processor::process::LoadTransformAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextTransformer, asset_processing::CoolTextSaver>",
|
||||
settings: (
|
||||
loader_settings: (),
|
||||
saver_settings: (
|
||||
transformer_settings: (
|
||||
appended: "X",
|
||||
),
|
||||
saver_settings: (),
|
||||
),
|
||||
),
|
||||
)
|
|
@ -3,6 +3,7 @@
|
|||
dependencies: [
|
||||
],
|
||||
embedded_dependencies: [
|
||||
"foo/c.cool.ron"
|
||||
"foo/c.cool.ron",
|
||||
"embedded://asset_processing/e.txt"
|
||||
],
|
||||
)
|
|
@ -1,12 +1,13 @@
|
|||
(
|
||||
meta_format_version: "1.0",
|
||||
asset: Process(
|
||||
processor: "bevy_asset::processor::process::LoadAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextSaver>",
|
||||
processor: "bevy_asset::processor::process::LoadTransformAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextTransformer, asset_processing::CoolTextSaver>",
|
||||
settings: (
|
||||
loader_settings: (),
|
||||
saver_settings: (
|
||||
transformer_settings: (
|
||||
appended: "",
|
||||
),
|
||||
saver_settings: (),
|
||||
),
|
||||
),
|
||||
)
|
|
@ -1,12 +1,13 @@
|
|||
(
|
||||
meta_format_version: "1.0",
|
||||
asset: Process(
|
||||
processor: "bevy_asset::processor::process::LoadAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextSaver>",
|
||||
processor: "bevy_asset::processor::process::LoadTransformAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextTransformer, asset_processing::CoolTextSaver>",
|
||||
settings: (
|
||||
loader_settings: (),
|
||||
saver_settings: (
|
||||
transformer_settings: (
|
||||
appended: "",
|
||||
),
|
||||
saver_settings: (),
|
||||
),
|
||||
),
|
||||
)
|
|
@ -1,12 +1,13 @@
|
|||
(
|
||||
meta_format_version: "1.0",
|
||||
asset: Process(
|
||||
processor: "bevy_asset::processor::process::LoadAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextSaver>",
|
||||
processor: "bevy_asset::processor::process::LoadTransformAndSave<asset_processing::CoolTextLoader, asset_processing::CoolTextTransformer, asset_processing::CoolTextSaver>",
|
||||
settings: (
|
||||
loader_settings: (),
|
||||
saver_settings: (
|
||||
transformer_settings: (
|
||||
appended: "",
|
||||
),
|
||||
saver_settings: (),
|
||||
),
|
||||
),
|
||||
)
|
Loading…
Reference in a new issue