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")]
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
172impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>
173where
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(
207 writer,
208 saved_asset,
209 &settings.saver_settings,
210 context.path.clone(),
211 )
212 .await
213 .map_err(|error| ProcessError::AssetSaveError(error.into()))?;
214 Ok(output_settings)
215 }
216}
217
218pub trait ErasedProcessor: Send + Sync {
221 fn process<'a>(
223 &'a self,
224 context: &'a mut ProcessContext,
225 settings: &'a dyn Settings,
226 writer: &'a mut Writer,
227 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;
228 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
231 fn type_path(&self) -> &'static str;
233 fn short_type_path(&self) -> &'static str;
235 fn default_meta(&self, processor_path_kind: MetaTypePathKind) -> Box<dyn AssetMetaDyn>;
237}
238
239pub enum MetaTypePathKind {
241 Short,
243 Long,
245}
246
247impl<P: Process> ErasedProcessor for P {
248 fn process<'a>(
249 &'a self,
250 context: &'a mut ProcessContext,
251 settings: &'a dyn Settings,
252 writer: &'a mut Writer,
253 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {
254 Box::pin(async move {
255 let settings = settings.downcast_ref().ok_or(ProcessError::WrongMetaType)?;
256 let loader_settings = <P as Process>::process(self, context, settings, writer).await?;
257 let output_meta: Box<dyn AssetMetaDyn> =
258 Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {
259 loader: P::OutputLoader::type_path().to_string(),
260 settings: loader_settings,
261 }));
262 Ok(output_meta)
263 })
264 }
265
266 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
267 let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;
268 Ok(Box::new(meta))
269 }
270
271 fn type_path(&self) -> &'static str {
272 P::type_path()
273 }
274
275 fn short_type_path(&self) -> &'static str {
276 P::short_type_path()
277 }
278
279 fn default_meta(&self, processor_path_kind: MetaTypePathKind) -> Box<dyn AssetMetaDyn> {
280 let type_path = match processor_path_kind {
281 MetaTypePathKind::Short => P::short_type_path(),
282 MetaTypePathKind::Long => P::type_path(),
283 };
284 Box::new(AssetMeta::<(), P>::new(AssetAction::Process {
285 processor: type_path.to_string(),
286 settings: P::Settings::default(),
287 }))
288 }
289}
290
291pub struct ProcessContext<'a> {
294 pub(crate) new_processed_info: &'a mut ProcessedInfo,
304 processor: &'a AssetProcessor,
313 path: &'a AssetPath<'static>,
314 reader: Box<dyn Reader + 'a>,
315}
316
317impl<'a> ProcessContext<'a> {
318 pub(crate) fn new(
319 processor: &'a AssetProcessor,
320 path: &'a AssetPath<'static>,
321 reader: Box<dyn Reader + 'a>,
322 new_processed_info: &'a mut ProcessedInfo,
323 ) -> Self {
324 Self {
325 processor,
326 path,
327 reader,
328 new_processed_info,
329 }
330 }
331
332 pub async fn load_source_asset<L: AssetLoader>(
337 &mut self,
338 settings: &L::Settings,
339 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
340 let server = &self.processor.server;
341 let loader_name = L::type_path();
342 let loader = server.get_asset_loader_with_type_name(loader_name).await?;
343 let loaded_asset = server
344 .load_with_settings_loader_and_reader(
345 self.path,
346 settings,
347 &*loader,
348 &mut self.reader,
349 false,
350 true,
351 )
352 .await?;
353 for (path, full_hash) in &loaded_asset.loader_dependencies {
354 self.new_processed_info
355 .process_dependencies
356 .push(ProcessDependencyInfo {
357 full_hash: *full_hash,
358 path: path.to_owned(),
359 });
360 }
361 Ok(loaded_asset)
362 }
363
364 #[inline]
366 pub fn path(&self) -> &AssetPath<'static> {
367 self.path
368 }
369
370 #[inline]
372 pub fn asset_reader(&mut self) -> &mut dyn Reader {
373 &mut self.reader
374 }
375}