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