1use crate::{
2 io::{
3 AssetReaderError, AssetWriterError, MissingAssetWriterError,
4 MissingProcessedAssetReaderError, MissingProcessedAssetWriterError, Reader,
5 ReaderRequiredFeatures, Writer,
6 },
7 meta::{AssetAction, AssetMeta, AssetMetaDyn, ProcessDependencyInfo, ProcessedInfo, Settings},
8 processor::AssetProcessor,
9 saver::{AssetSaver, SavedAsset},
10 transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset},
11 AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset,
12 MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,
13};
14use alloc::{
15 borrow::ToOwned,
16 boxed::Box,
17 string::{String, ToString},
18 vec::Vec,
19};
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 fn reader_required_features(_settings: &Self::Settings) -> ReaderRequiredFeatures {
49 ReaderRequiredFeatures::default()
50 }
51}
52
53#[derive(TypePath)]
71pub struct LoadTransformAndSave<
72 L: AssetLoader,
73 T: AssetTransformer<AssetInput = L::Asset>,
74 S: AssetSaver<Asset = T::AssetOutput>,
75> {
76 transformer: T,
77 saver: S,
78 marker: PhantomData<fn() -> L>,
79}
80
81impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S>
82 for LoadTransformAndSave<L, IdentityAssetTransformer<L::Asset>, S>
83{
84 fn from(value: S) -> Self {
85 LoadTransformAndSave {
86 transformer: IdentityAssetTransformer::new(),
87 saver: value,
88 marker: PhantomData,
89 }
90 }
91}
92
93#[derive(Serialize, Deserialize, Default)]
98pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {
99 pub loader_settings: LoaderSettings,
101 pub transformer_settings: TransformerSettings,
103 pub saver_settings: SaverSettings,
105}
106
107impl<
108 L: AssetLoader,
109 T: AssetTransformer<AssetInput = L::Asset>,
110 S: AssetSaver<Asset = T::AssetOutput>,
111 > LoadTransformAndSave<L, T, S>
112{
113 pub fn new(transformer: T, saver: S) -> Self {
114 LoadTransformAndSave {
115 transformer,
116 saver,
117 marker: PhantomData,
118 }
119 }
120}
121
122#[derive(Error, Debug)]
124pub enum ProcessError {
125 #[error(transparent)]
126 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
127 #[error(transparent)]
128 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
129 #[error("The processor '{0}' does not exist")]
130 #[from(ignore)]
131 MissingProcessor(String),
132 #[error("The processor '{processor_short_name}' is ambiguous between several processors: {ambiguous_processor_names:?}")]
133 AmbiguousProcessor {
134 processor_short_name: String,
135 ambiguous_processor_names: Vec<&'static str>,
136 },
137 #[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}")]
144 #[from(ignore)]
145 AssetWriterError {
146 path: AssetPath<'static>,
147 err: AssetWriterError,
148 },
149 #[error(transparent)]
150 MissingAssetWriterError(#[from] MissingAssetWriterError),
151 #[error(transparent)]
152 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
153 #[error(transparent)]
154 MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),
155 #[error("Failed to read asset metadata for {path}: {err}")]
156 #[from(ignore)]
157 ReadAssetMetaError {
158 path: AssetPath<'static>,
159 err: AssetReaderError,
160 },
161 #[error(transparent)]
162 DeserializeMetaError(#[from] DeserializeMetaError),
163 #[error(transparent)]
164 AssetLoadError(#[from] AssetLoadError),
165 #[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
166 WrongMetaType,
167 #[error("Encountered an error while saving the asset: {0}")]
168 #[from(ignore)]
169 AssetSaveError(Box<dyn core::error::Error + Send + Sync + 'static>),
170 #[error("Encountered an error while transforming the asset: {0}")]
171 #[from(ignore)]
172 AssetTransformError(Box<dyn core::error::Error + Send + Sync + 'static>),
173 #[error("Assets without extensions are not supported.")]
174 ExtensionRequired,
175}
176
177impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>
178where
179 Loader: AssetLoader,
180 Transformer: AssetTransformer<AssetInput = Loader::Asset>,
181 Saver: AssetSaver<Asset = Transformer::AssetOutput>,
182{
183 type Settings =
184 LoadTransformAndSaveSettings<Loader::Settings, Transformer::Settings, Saver::Settings>;
185 type OutputLoader = Saver::OutputLoader;
186
187 async fn process(
188 &self,
189 context: &mut ProcessContext<'_>,
190 settings: &Self::Settings,
191 writer: &mut Writer,
192 ) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
193 let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(
194 context
195 .load_source_asset::<Loader>(&settings.loader_settings)
196 .await?,
197 )
198 .unwrap();
199
200 let post_transformed_asset = self
201 .transformer
202 .transform(pre_transformed_asset, &settings.transformer_settings)
203 .await
204 .map_err(|err| ProcessError::AssetTransformError(err.into()))?;
205
206 let saved_asset =
207 SavedAsset::<Transformer::AssetOutput>::from_transformed(&post_transformed_asset);
208
209 let output_settings = self
210 .saver
211 .save(writer, saved_asset, &settings.saver_settings)
212 .await
213 .map_err(|error| ProcessError::AssetSaveError(error.into()))?;
214 Ok(output_settings)
215 }
216
217 fn reader_required_features(settings: &Self::Settings) -> ReaderRequiredFeatures {
218 Loader::reader_required_features(&settings.loader_settings)
219 }
220}
221
222pub trait ErasedProcessor: Send + Sync {
225 fn process<'a>(
227 &'a self,
228 context: &'a mut ProcessContext,
229 settings: &'a dyn Settings,
230 writer: &'a mut Writer,
231 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;
232 #[expect(
235 clippy::result_large_err,
236 reason = "this is only an error here because this isn't a future"
237 )]
238 fn reader_required_features(
239 &self,
240 settings: &dyn Settings,
241 ) -> Result<ReaderRequiredFeatures, ProcessError>;
242 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
245 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
247}
248
249impl<P: Process> ErasedProcessor for P {
250 fn process<'a>(
251 &'a self,
252 context: &'a mut ProcessContext,
253 settings: &'a dyn Settings,
254 writer: &'a mut Writer,
255 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {
256 Box::pin(async move {
257 let settings = settings.downcast_ref().ok_or(ProcessError::WrongMetaType)?;
258 let loader_settings = <P as Process>::process(self, context, settings, writer).await?;
259 let output_meta: Box<dyn AssetMetaDyn> =
260 Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {
261 loader: P::OutputLoader::type_path().to_string(),
262 settings: loader_settings,
263 }));
264 Ok(output_meta)
265 })
266 }
267
268 fn reader_required_features(
269 &self,
270 settings: &dyn Settings,
271 ) -> Result<ReaderRequiredFeatures, ProcessError> {
272 let settings = settings.downcast_ref().ok_or(ProcessError::WrongMetaType)?;
273 Ok(P::reader_required_features(settings))
274 }
275
276 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
277 let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;
278 Ok(Box::new(meta))
279 }
280
281 fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
282 Box::new(AssetMeta::<(), P>::new(AssetAction::Process {
283 processor: P::type_path().to_string(),
284 settings: P::Settings::default(),
285 }))
286 }
287}
288
289pub struct ProcessContext<'a> {
292 pub(crate) new_processed_info: &'a mut ProcessedInfo,
302 processor: &'a AssetProcessor,
311 path: &'a AssetPath<'static>,
312 reader: Box<dyn Reader + 'a>,
313}
314
315impl<'a> ProcessContext<'a> {
316 pub(crate) fn new(
317 processor: &'a AssetProcessor,
318 path: &'a AssetPath<'static>,
319 reader: Box<dyn Reader + 'a>,
320 new_processed_info: &'a mut ProcessedInfo,
321 ) -> Self {
322 Self {
323 processor,
324 path,
325 reader,
326 new_processed_info,
327 }
328 }
329
330 pub async fn load_source_asset<L: AssetLoader>(
335 &mut self,
336 settings: &L::Settings,
337 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
338 let server = &self.processor.server;
339 let loader_name = L::type_path();
340 let loader = server.get_asset_loader_with_type_name(loader_name).await?;
341 let loaded_asset = server
342 .load_with_settings_loader_and_reader(
343 self.path,
344 settings,
345 &*loader,
346 &mut self.reader,
347 false,
348 true,
349 )
350 .await?;
351 for (path, full_hash) in &loaded_asset.loader_dependencies {
352 self.new_processed_info
353 .process_dependencies
354 .push(ProcessDependencyInfo {
355 full_hash: *full_hash,
356 path: path.to_owned(),
357 });
358 }
359 Ok(loaded_asset)
360 }
361
362 #[inline]
364 pub fn path(&self) -> &AssetPath<'static> {
365 self.path
366 }
367
368 #[inline]
370 pub fn asset_reader(&mut self) -> &mut dyn Reader {
371 &mut self.reader
372 }
373}