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