Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AssetSaver and AssetTransformer split #11260

Merged
merged 6 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod io;
pub mod meta;
pub mod processor;
pub mod saver;
pub mod transformer;

pub mod prelude {
#[doc(hidden)]
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand Down Expand Up @@ -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<'_>,
Expand Down
108 changes: 102 additions & 6 deletions crates/bevy_asset/src/processor/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not have to constrain the loader type, or the transformer output here? Huh.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following. Does:

LoadTransformAndSave<L: AssetLoader, T: AssetTransformer<AssetInput = L::Asset>, S: AssetSaver<Asset = T::AssetOutput>>

need further constraints?
L can be any AssetLoader, T can be any AssetTransformer that accepts L's output as an input, and S can by any AssetSaver that accepts T's output as an input.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a second look I guess it makes sense. I'm a bit surprised we don't have to specify e.g. the AssetTransformer output type, but it does make sense as is.

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.
///
Expand Down Expand Up @@ -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>
{
Expand Down
20 changes: 20 additions & 0 deletions crates/bevy_asset/src/transformer.rs
Original file line number Diff line number Diff line change
@@ -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>>;
}
46 changes: 33 additions & 13 deletions examples/asset/processing/asset_processing.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
//! 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::*,
reflect::TypePath,
utils::{thiserror, BoxedFuture},
};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
use thiserror::Error;

fn main() {
Expand Down Expand Up @@ -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");
}
}

Expand Down Expand Up @@ -133,9 +135,7 @@ enum CoolTextLoaderError {

impl AssetLoader for CoolTextLoader {
type Asset = CoolText;

type Settings = ();

type Error = CoolTextLoaderError;

fn load<'a>(
Expand Down Expand Up @@ -170,28 +170,48 @@ 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;

fn save<'a>(
&'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())
})
}
Expand Down
5 changes: 3 additions & 2 deletions examples/asset/processing/assets/a.cool.ron.meta
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 1 deletion examples/asset/processing/assets/d.cool.ron
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
dependencies: [
],
embedded_dependencies: [
"foo/c.cool.ron"
"foo/c.cool.ron",
"embedded://asset_processing/e.txt"
],
)
5 changes: 3 additions & 2 deletions examples/asset/processing/assets/d.cool.ron.meta
Original file line number Diff line number Diff line change
@@ -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: (),
),
),
)
5 changes: 3 additions & 2 deletions examples/asset/processing/assets/foo/b.cool.ron.meta
Original file line number Diff line number Diff line change
@@ -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: (),
),
),
)
5 changes: 3 additions & 2 deletions examples/asset/processing/assets/foo/c.cool.ron.meta
Original file line number Diff line number Diff line change
@@ -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: (),
),
),
)