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