1use 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};
13use alloc::{
14 borrow::ToOwned,
15 boxed::Box,
16 string::{String, ToString},
17 vec::Vec,
18};
19use bevy_ecs::error::BevyError;
20use bevy_reflect::TypePath;
21use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
22use core::marker::PhantomData;
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25
26pub trait Process: TypePath + Send + Sync + Sized + 'static {
32 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
34 type OutputLoader: AssetLoader;
36 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#[derive(TypePath)]
66pub 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
76impl<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#[derive(Serialize, Deserialize, Default)]
93pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {
94 pub loader_settings: LoaderSettings,
96 pub transformer_settings: TransformerSettings,
98 pub saver_settings: SaverSettings,
100}
101
102impl<
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#[derive(Error, Debug)]
119pub enum ProcessError {
120 #[error(transparent)]
121 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
122 #[error(transparent)]
123 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
124 #[error("The processor '{0}' does not exist")]
126 #[from(ignore)]
127 MissingProcessor(String),
128 #[error("The processor '{processor_short_name}' is ambiguous between several processors: {ambiguous_processor_names:?}")]
130 AmbiguousProcessor {
131 processor_short_name: String,
133 ambiguous_processor_names: Vec<&'static str>,
135 },
136 #[error("Encountered an AssetReader error for '{path}': {err}")]
138 #[from(ignore)]
139 AssetReaderError {
140 path: AssetPath<'static>,
141 err: AssetReaderError,
142 },
143 #[error("Encountered an AssetWriter error for '{path}': {err}")]
145 #[from(ignore)]
146 AssetWriterError {
147 path: AssetPath<'static>,
148 err: AssetWriterError,
149 },
150 #[error(transparent)]
151 MissingAssetWriterError(#[from] MissingAssetWriterError),
152 #[error(transparent)]
153 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
154 #[error(transparent)]
155 MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),
156 #[error("Failed to read asset metadata for {path}: {err}")]
158 #[from(ignore)]
159 ReadAssetMetaError {
160 path: AssetPath<'static>,
161 err: AssetReaderError,
162 },
163 #[error(transparent)]
164 DeserializeMetaError(#[from] DeserializeMetaError),
165 #[error(transparent)]
166 AssetLoadError(#[from] AssetLoadError),
167 #[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
170 WrongMetaType,
171 #[error("Encountered an error while saving the asset: {0}")]
173 #[from(ignore)]
174 AssetSaveError(BevyError),
175 #[error("Encountered an error while transforming the asset: {0}")]
177 #[from(ignore)]
178 AssetTransformError(Box<dyn core::error::Error + Send + Sync + 'static>),
179 #[error("Assets without extensions are not supported.")]
181 ExtensionRequired,
182}
183
184impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>
185where
186 Loader: AssetLoader,
187 Transformer: AssetTransformer<AssetInput = Loader::Asset>,
188 Saver: AssetSaver<Asset = Transformer::AssetOutput>,
189{
190 type Settings =
191 LoadTransformAndSaveSettings<Loader::Settings, Transformer::Settings, Saver::Settings>;
192 type OutputLoader = Saver::OutputLoader;
193
194 async fn process(
195 &self,
196 context: &mut ProcessContext<'_>,
197 settings: &Self::Settings,
198 writer: &mut Writer,
199 ) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
200 let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(
201 context
202 .load_source_asset::<Loader>(&settings.loader_settings)
203 .await?,
204 )
205 .unwrap();
206
207 let post_transformed_asset = self
208 .transformer
209 .transform(pre_transformed_asset, &settings.transformer_settings)
210 .await
211 .map_err(|err| ProcessError::AssetTransformError(err.into()))?;
212
213 let saved_asset =
214 SavedAsset::<Transformer::AssetOutput>::from_transformed(&post_transformed_asset);
215
216 let output_settings = self
217 .saver
218 .save(
219 writer,
220 saved_asset,
221 &settings.saver_settings,
222 context.path.clone(),
223 )
224 .await
225 .map_err(|error| ProcessError::AssetSaveError(error.into()))?;
226 Ok(output_settings)
227 }
228}
229
230pub trait ErasedProcessor: Send + Sync {
233 fn process<'a>(
235 &'a self,
236 context: &'a mut ProcessContext,
237 settings: &'a dyn Settings,
238 writer: &'a mut Writer,
239 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;
240 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
243 fn type_path(&self) -> &'static str;
245 fn short_type_path(&self) -> &'static str;
247 fn default_meta(&self, processor_path_kind: MetaTypePathKind) -> Box<dyn AssetMetaDyn>;
249}
250
251pub enum MetaTypePathKind {
253 Short,
255 Long,
257}
258
259impl<P: Process> ErasedProcessor for P {
260 fn process<'a>(
261 &'a self,
262 context: &'a mut ProcessContext,
263 settings: &'a dyn Settings,
264 writer: &'a mut Writer,
265 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {
266 Box::pin(async move {
267 let settings = settings.downcast_ref().ok_or(ProcessError::WrongMetaType)?;
268 let loader_settings = <P as Process>::process(self, context, settings, writer).await?;
269 let output_meta: Box<dyn AssetMetaDyn> =
270 Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {
271 loader: P::OutputLoader::type_path().to_string(),
272 settings: loader_settings,
273 }));
274 Ok(output_meta)
275 })
276 }
277
278 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
279 let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;
280 Ok(Box::new(meta))
281 }
282
283 fn type_path(&self) -> &'static str {
284 P::type_path()
285 }
286
287 fn short_type_path(&self) -> &'static str {
288 P::short_type_path()
289 }
290
291 fn default_meta(&self, processor_path_kind: MetaTypePathKind) -> Box<dyn AssetMetaDyn> {
292 let type_path = match processor_path_kind {
293 MetaTypePathKind::Short => P::short_type_path(),
294 MetaTypePathKind::Long => P::type_path(),
295 };
296 Box::new(AssetMeta::<(), P>::new(AssetAction::Process {
297 processor: type_path.to_string(),
298 settings: P::Settings::default(),
299 }))
300 }
301}
302
303pub struct ProcessContext<'a> {
306 pub(crate) new_processed_info: &'a mut ProcessedInfo,
316 processor: &'a AssetProcessor,
325 path: &'a AssetPath<'static>,
326 reader: Box<dyn Reader + 'a>,
327}
328
329impl<'a> ProcessContext<'a> {
330 pub(crate) fn new(
331 processor: &'a AssetProcessor,
332 path: &'a AssetPath<'static>,
333 reader: Box<dyn Reader + 'a>,
334 new_processed_info: &'a mut ProcessedInfo,
335 ) -> Self {
336 Self {
337 processor,
338 path,
339 reader,
340 new_processed_info,
341 }
342 }
343
344 pub async fn load_source_asset<L: AssetLoader>(
349 &mut self,
350 settings: &L::Settings,
351 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
352 let server = &self.processor.server;
353 let loader_name = L::type_path();
354 let loader = server.get_asset_loader_with_type_name(loader_name).await?;
355 let loaded_asset = server
356 .load_with_settings_loader_and_reader(
357 self.path,
358 settings,
359 &*loader,
360 &mut self.reader,
361 false,
362 true,
363 )
364 .await?;
365 for (path, full_hash) in &loaded_asset.loader_dependencies {
366 self.new_processed_info
367 .process_dependencies
368 .push(ProcessDependencyInfo {
369 full_hash: *full_hash,
370 path: path.to_owned(),
371 });
372 }
373 Ok(loaded_asset)
374 }
375
376 #[inline]
378 pub fn path(&self) -> &AssetPath<'static> {
379 self.path
380 }
381
382 #[inline]
384 pub fn asset_reader(&mut self) -> &mut dyn Reader {
385 &mut self.reader
386 }
387}