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_reflect::TypePath;
20use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
21use core::marker::PhantomData;
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24
25pub trait Process: TypePath + Send + Sync + Sized + 'static {
31 type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
33 type OutputLoader: AssetLoader;
35 fn process(
38 &self,
39 context: &mut ProcessContext,
40 settings: &Self::Settings,
41 writer: &mut Writer,
42 ) -> impl ConditionalSendFuture<
43 Output = Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>,
44 >;
45}
46
47#[derive(TypePath)]
65pub struct LoadTransformAndSave<
66 L: AssetLoader,
67 T: AssetTransformer<AssetInput = L::Asset>,
68 S: AssetSaver<Asset = T::AssetOutput>,
69> {
70 transformer: T,
71 saver: S,
72 marker: PhantomData<fn() -> L>,
73}
74
75impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S>
76 for LoadTransformAndSave<L, IdentityAssetTransformer<L::Asset>, S>
77{
78 fn from(value: S) -> Self {
79 LoadTransformAndSave {
80 transformer: IdentityAssetTransformer::new(),
81 saver: value,
82 marker: PhantomData,
83 }
84 }
85}
86
87#[derive(Serialize, Deserialize, Default)]
92pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {
93 pub loader_settings: LoaderSettings,
95 pub transformer_settings: TransformerSettings,
97 pub saver_settings: SaverSettings,
99}
100
101impl<
102 L: AssetLoader,
103 T: AssetTransformer<AssetInput = L::Asset>,
104 S: AssetSaver<Asset = T::AssetOutput>,
105 > LoadTransformAndSave<L, T, S>
106{
107 pub fn new(transformer: T, saver: S) -> Self {
108 LoadTransformAndSave {
109 transformer,
110 saver,
111 marker: PhantomData,
112 }
113 }
114}
115
116#[derive(Error, Debug)]
118pub enum ProcessError {
119 #[error(transparent)]
120 MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
121 #[error(transparent)]
122 MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
123 #[error("The processor '{0}' does not exist")]
124 #[from(ignore)]
125 MissingProcessor(String),
126 #[error("The processor '{processor_short_name}' is ambiguous between several processors: {ambiguous_processor_names:?}")]
127 AmbiguousProcessor {
128 processor_short_name: String,
129 ambiguous_processor_names: Vec<&'static str>,
130 },
131 #[error("Encountered an AssetReader error for '{path}': {err}")]
132 #[from(ignore)]
133 AssetReaderError {
134 path: AssetPath<'static>,
135 err: AssetReaderError,
136 },
137 #[error("Encountered an AssetWriter error for '{path}': {err}")]
138 #[from(ignore)]
139 AssetWriterError {
140 path: AssetPath<'static>,
141 err: AssetWriterError,
142 },
143 #[error(transparent)]
144 MissingAssetWriterError(#[from] MissingAssetWriterError),
145 #[error(transparent)]
146 MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
147 #[error(transparent)]
148 MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),
149 #[error("Failed to read asset metadata for {path}: {err}")]
150 #[from(ignore)]
151 ReadAssetMetaError {
152 path: AssetPath<'static>,
153 err: AssetReaderError,
154 },
155 #[error(transparent)]
156 DeserializeMetaError(#[from] DeserializeMetaError),
157 #[error(transparent)]
158 AssetLoadError(#[from] AssetLoadError),
159 #[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
160 WrongMetaType,
161 #[error("Encountered an error while saving the asset: {0}")]
162 #[from(ignore)]
163 AssetSaveError(Box<dyn core::error::Error + Send + Sync + 'static>),
164 #[error("Encountered an error while transforming the asset: {0}")]
165 #[from(ignore)]
166 AssetTransformError(Box<dyn core::error::Error + Send + Sync + 'static>),
167 #[error("Assets without extensions are not supported.")]
168 ExtensionRequired,
169}
170
171impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>
172where
173 Loader: AssetLoader,
174 Transformer: AssetTransformer<AssetInput = Loader::Asset>,
175 Saver: AssetSaver<Asset = Transformer::AssetOutput>,
176{
177 type Settings =
178 LoadTransformAndSaveSettings<Loader::Settings, Transformer::Settings, Saver::Settings>;
179 type OutputLoader = Saver::OutputLoader;
180
181 async fn process(
182 &self,
183 context: &mut ProcessContext<'_>,
184 settings: &Self::Settings,
185 writer: &mut Writer,
186 ) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
187 let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(
188 context
189 .load_source_asset::<Loader>(&settings.loader_settings)
190 .await?,
191 )
192 .unwrap();
193
194 let post_transformed_asset = self
195 .transformer
196 .transform(pre_transformed_asset, &settings.transformer_settings)
197 .await
198 .map_err(|err| ProcessError::AssetTransformError(err.into()))?;
199
200 let saved_asset =
201 SavedAsset::<Transformer::AssetOutput>::from_transformed(&post_transformed_asset);
202
203 let output_settings = self
204 .saver
205 .save(writer, saved_asset, &settings.saver_settings)
206 .await
207 .map_err(|error| ProcessError::AssetSaveError(error.into()))?;
208 Ok(output_settings)
209 }
210}
211
212pub trait ErasedProcessor: Send + Sync {
215 fn process<'a>(
217 &'a self,
218 context: &'a mut ProcessContext,
219 settings: &'a dyn Settings,
220 writer: &'a mut Writer,
221 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;
222 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
225 fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
227}
228
229impl<P: Process> ErasedProcessor for P {
230 fn process<'a>(
231 &'a self,
232 context: &'a mut ProcessContext,
233 settings: &'a dyn Settings,
234 writer: &'a mut Writer,
235 ) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {
236 Box::pin(async move {
237 let settings = settings.downcast_ref().ok_or(ProcessError::WrongMetaType)?;
238 let loader_settings = <P as Process>::process(self, context, settings, writer).await?;
239 let output_meta: Box<dyn AssetMetaDyn> =
240 Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {
241 loader: P::OutputLoader::type_path().to_string(),
242 settings: loader_settings,
243 }));
244 Ok(output_meta)
245 })
246 }
247
248 fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
249 let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;
250 Ok(Box::new(meta))
251 }
252
253 fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
254 Box::new(AssetMeta::<(), P>::new(AssetAction::Process {
255 processor: P::type_path().to_string(),
256 settings: P::Settings::default(),
257 }))
258 }
259}
260
261pub struct ProcessContext<'a> {
264 pub(crate) new_processed_info: &'a mut ProcessedInfo,
274 processor: &'a AssetProcessor,
283 path: &'a AssetPath<'static>,
284 reader: Box<dyn Reader + 'a>,
285}
286
287impl<'a> ProcessContext<'a> {
288 pub(crate) fn new(
289 processor: &'a AssetProcessor,
290 path: &'a AssetPath<'static>,
291 reader: Box<dyn Reader + 'a>,
292 new_processed_info: &'a mut ProcessedInfo,
293 ) -> Self {
294 Self {
295 processor,
296 path,
297 reader,
298 new_processed_info,
299 }
300 }
301
302 pub async fn load_source_asset<L: AssetLoader>(
307 &mut self,
308 settings: &L::Settings,
309 ) -> Result<ErasedLoadedAsset, AssetLoadError> {
310 let server = &self.processor.server;
311 let loader_name = L::type_path();
312 let loader = server.get_asset_loader_with_type_name(loader_name).await?;
313 let loaded_asset = server
314 .load_with_settings_loader_and_reader(
315 self.path,
316 settings,
317 &*loader,
318 &mut self.reader,
319 false,
320 true,
321 )
322 .await?;
323 for (path, full_hash) in &loaded_asset.loader_dependencies {
324 self.new_processed_info
325 .process_dependencies
326 .push(ProcessDependencyInfo {
327 full_hash: *full_hash,
328 path: path.to_owned(),
329 });
330 }
331 Ok(loaded_asset)
332 }
333
334 #[inline]
336 pub fn path(&self) -> &AssetPath<'static> {
337 self.path
338 }
339
340 #[inline]
342 pub fn asset_reader(&mut self) -> &mut dyn Reader {
343 &mut self.reader
344 }
345}