Skip to main content

bevy_asset/
transformer.rs

1use crate::{
2    meta::Settings, Asset, AssetId, ErasedLoadedAsset, Handle, LabeledAsset, UntypedAssetId,
3    UntypedHandle,
4};
5use alloc::{boxed::Box, vec::Vec};
6use atomicow::CowArc;
7use bevy_platform::collections::{hash_map::Entry, HashMap};
8use bevy_reflect::TypePath;
9use bevy_tasks::ConditionalSendFuture;
10use core::{
11    borrow::Borrow,
12    convert::Infallible,
13    hash::Hash,
14    marker::PhantomData,
15    ops::{Deref, DerefMut},
16};
17use serde::{Deserialize, Serialize};
18
19/// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type.
20///
21/// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows.
22pub trait AssetTransformer: TypePath + Send + Sync + 'static {
23    /// The [`Asset`] type which this [`AssetTransformer`] takes as and input.
24    type AssetInput: Asset;
25    /// The [`Asset`] type which this [`AssetTransformer`] outputs.
26    type AssetOutput: Asset;
27    /// The settings type used by this [`AssetTransformer`].
28    type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
29    /// The type of [error](`std::error::Error`) which could be encountered by this transformer.
30    type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
31
32    /// Transforms the given [`TransformedAsset`] to [`AssetTransformer::AssetOutput`].
33    /// The [`TransformedAsset`]'s `labeled_assets` can be altered to add new Labeled Sub-Assets
34    /// The passed in `settings` can influence how the `asset` is transformed
35    fn transform<'a>(
36        &'a self,
37        asset: TransformedAsset<Self::AssetInput>,
38        settings: &'a Self::Settings,
39    ) -> impl ConditionalSendFuture<Output = Result<TransformedAsset<Self::AssetOutput>, Self::Error>>;
40}
41
42/// An [`Asset`] (and any "sub assets") intended to be transformed
43pub struct TransformedAsset<A: Asset> {
44    pub(crate) value: A,
45    pub(crate) labeled_assets: Vec<LabeledAsset>,
46    pub(crate) label_to_asset_index: HashMap<CowArc<'static, str>, usize>,
47    /// The mapping from a subasset asset IDs to their index in [`Self::labeled_assets`].
48    ///
49    /// This is entirely redundant with [`Self::labeled_assets`], but it allows looking up the
50    /// labeled asset by its asset ID.
51    pub(crate) asset_id_to_asset_index: HashMap<UntypedAssetId, usize>,
52}
53
54impl<A: Asset> Deref for TransformedAsset<A> {
55    type Target = A;
56    fn deref(&self) -> &Self::Target {
57        &self.value
58    }
59}
60
61impl<A: Asset> DerefMut for TransformedAsset<A> {
62    fn deref_mut(&mut self) -> &mut Self::Target {
63        &mut self.value
64    }
65}
66
67impl<A: Asset> TransformedAsset<A> {
68    /// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`.
69    pub fn from_loaded(asset: ErasedLoadedAsset) -> Option<Self> {
70        if let Ok(value) = asset.value.downcast::<A>() {
71            return Some(TransformedAsset {
72                value: *value,
73                labeled_assets: asset.labeled_assets,
74                label_to_asset_index: asset.label_to_asset_index,
75                asset_id_to_asset_index: asset.asset_id_to_asset_index,
76            });
77        }
78        None
79    }
80
81    /// Creates a new [`TransformedAsset`] from `asset`, transferring the `labeled_assets` from this [`TransformedAsset`] to the new one
82    pub fn replace_asset<B: Asset>(self, asset: B) -> TransformedAsset<B> {
83        TransformedAsset {
84            value: asset,
85            labeled_assets: self.labeled_assets,
86            label_to_asset_index: self.label_to_asset_index,
87            asset_id_to_asset_index: self.asset_id_to_asset_index,
88        }
89    }
90
91    /// Takes the labeled assets from `labeled_source` and places them in this [`TransformedAsset`]
92    pub fn take_labeled_assets<B: Asset>(&mut self, labeled_source: TransformedAsset<B>) {
93        self.labeled_assets = labeled_source.labeled_assets;
94        self.label_to_asset_index = labeled_source.label_to_asset_index;
95        self.asset_id_to_asset_index = labeled_source.asset_id_to_asset_index;
96    }
97
98    /// Retrieves the value of this asset.
99    #[inline]
100    pub fn get(&self) -> &A {
101        &self.value
102    }
103
104    /// Mutably retrieves the value of this asset.
105    #[inline]
106    pub fn get_mut(&mut self) -> &mut A {
107        &mut self.value
108    }
109
110    /// Returns the labeled asset, if it exists and matches this type.
111    pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<'_, B>>
112    where
113        CowArc<'static, str>: Borrow<Q>,
114        Q: ?Sized + Hash + Eq,
115    {
116        let index = self.label_to_asset_index.get(label)?;
117        let labeled = &mut self.labeled_assets[*index];
118        let value = labeled.asset.value.downcast_mut::<B>()?;
119        Some(TransformedSubAsset {
120            value,
121            labeled_assets: &mut labeled.asset.labeled_assets,
122            label_to_asset_index: &mut labeled.asset.label_to_asset_index,
123            asset_id_to_asset_index: &mut labeled.asset.asset_id_to_asset_index,
124        })
125    }
126
127    /// Returns the type-erased labeled asset, if it exists.
128    pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
129    where
130        CowArc<'static, str>: Borrow<Q>,
131        Q: ?Sized + Hash + Eq,
132    {
133        let index = self.label_to_asset_index.get(label)?;
134        let labeled = &self.labeled_assets[*index];
135        Some(&labeled.asset)
136    }
137
138    /// Returns the labeled asset given its asset ID if it exists and matches the type.
139    ///
140    /// This can be used to get the asset from its handle since `&Handle` implements
141    /// [`Into<AssetId<B>>`].
142    pub fn get_labeled_by_id<B: Asset>(
143        &mut self,
144        id: impl Into<AssetId<B>>,
145    ) -> Option<TransformedSubAsset<'_, B>> {
146        let index = self.asset_id_to_asset_index.get(&id.into().untyped())?;
147        let labeled = &mut self.labeled_assets[*index];
148        let value = labeled.asset.value.downcast_mut::<B>()?;
149        Some(TransformedSubAsset {
150            value,
151            labeled_assets: &mut labeled.asset.labeled_assets,
152            label_to_asset_index: &mut labeled.asset.label_to_asset_index,
153            asset_id_to_asset_index: &mut labeled.asset.asset_id_to_asset_index,
154        })
155    }
156
157    /// Returns the type-erased labeled asset, if it exists.
158    ///
159    /// This can be used to get the asset from its handle since `&UntypedHandle` implements
160    /// [`Into<UntypedAssetId>`].
161    pub fn get_erased_labeled_by_id(
162        &self,
163        id: impl Into<UntypedAssetId>,
164    ) -> Option<&ErasedLoadedAsset> {
165        let index = self.asset_id_to_asset_index.get(&id.into())?;
166        let labeled = &self.labeled_assets[*index];
167        Some(&labeled.asset)
168    }
169
170    /// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
171    pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
172    where
173        CowArc<'static, str>: Borrow<Q>,
174        Q: ?Sized + Hash + Eq,
175    {
176        let index = self.label_to_asset_index.get(label)?;
177        let labeled = &self.labeled_assets[*index];
178        Some(labeled.handle.clone())
179    }
180
181    /// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
182    pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
183    where
184        CowArc<'static, str>: Borrow<Q>,
185        Q: ?Sized + Hash + Eq,
186    {
187        let index = self.label_to_asset_index.get(label)?;
188        let labeled = &self.labeled_assets[*index];
189        if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
190            return Some(handle);
191        }
192        None
193    }
194
195    /// Adds `asset` as a labeled sub asset using `label` and `handle`
196    pub fn insert_labeled(
197        &mut self,
198        label: impl Into<CowArc<'static, str>>,
199        handle: impl Into<UntypedHandle>,
200        asset: impl Into<ErasedLoadedAsset>,
201    ) {
202        let labeled = LabeledAsset {
203            asset: asset.into(),
204            handle: handle.into(),
205        };
206        match self.label_to_asset_index.entry(label.into()) {
207            Entry::Occupied(entry) => {
208                let labeled_entry = &mut self.labeled_assets[*entry.get()];
209                if labeled.handle != labeled_entry.handle {
210                    self.asset_id_to_asset_index
211                        .remove(&labeled_entry.handle.id());
212                    self.asset_id_to_asset_index
213                        .insert(labeled.handle.id(), *entry.get());
214                }
215                *labeled_entry = labeled;
216            }
217            Entry::Vacant(entry) => {
218                entry.insert(self.labeled_assets.len());
219                self.asset_id_to_asset_index
220                    .insert(labeled.handle.id(), self.labeled_assets.len());
221                self.labeled_assets.push(labeled);
222            }
223        }
224    }
225
226    /// Iterate over all labels for "labeled assets" in the loaded asset
227    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
228        self.label_to_asset_index.keys().map(|s| &**s)
229    }
230}
231
232/// A labeled sub-asset of [`TransformedAsset`]
233pub struct TransformedSubAsset<'a, A: Asset> {
234    value: &'a mut A,
235    labeled_assets: &'a mut Vec<LabeledAsset>,
236    label_to_asset_index: &'a mut HashMap<CowArc<'static, str>, usize>,
237    /// The mapping from a subasset asset IDs to their index in [`Self::labeled_assets`].
238    ///
239    /// This is entirely redundant with [`Self::labeled_assets`], but it allows looking up the
240    /// labeled asset by its asset ID.
241    asset_id_to_asset_index: &'a mut HashMap<UntypedAssetId, usize>,
242}
243
244impl<'a, A: Asset> Deref for TransformedSubAsset<'a, A> {
245    type Target = A;
246    fn deref(&self) -> &Self::Target {
247        self.value
248    }
249}
250
251impl<'a, A: Asset> DerefMut for TransformedSubAsset<'a, A> {
252    fn deref_mut(&mut self) -> &mut Self::Target {
253        self.value
254    }
255}
256
257impl<'a, A: Asset> TransformedSubAsset<'a, A> {
258    /// Creates a new [`TransformedSubAsset`] from `asset` if its internal value matches `A`.
259    pub fn from_loaded(asset: &'a mut ErasedLoadedAsset) -> Option<Self> {
260        let value = asset.value.downcast_mut::<A>()?;
261        Some(TransformedSubAsset {
262            value,
263            labeled_assets: &mut asset.labeled_assets,
264            label_to_asset_index: &mut asset.label_to_asset_index,
265            asset_id_to_asset_index: &mut asset.asset_id_to_asset_index,
266        })
267    }
268
269    /// Retrieves the value of this asset.
270    #[inline]
271    pub fn get(&self) -> &A {
272        self.value
273    }
274
275    /// Mutably retrieves the value of this asset.
276    #[inline]
277    pub fn get_mut(&mut self) -> &mut A {
278        self.value
279    }
280
281    /// Returns the labeled asset, if it exists and matches this type.
282    pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<'_, B>>
283    where
284        CowArc<'static, str>: Borrow<Q>,
285        Q: ?Sized + Hash + Eq,
286    {
287        let index = self.label_to_asset_index.get(label)?;
288        let labeled = &mut self.labeled_assets[*index];
289        let value = labeled.asset.value.downcast_mut::<B>()?;
290        Some(TransformedSubAsset {
291            value,
292            labeled_assets: &mut labeled.asset.labeled_assets,
293            label_to_asset_index: &mut labeled.asset.label_to_asset_index,
294            asset_id_to_asset_index: &mut labeled.asset.asset_id_to_asset_index,
295        })
296    }
297
298    /// Returns the type-erased labeled asset, if it exists.
299    pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
300    where
301        CowArc<'static, str>: Borrow<Q>,
302        Q: ?Sized + Hash + Eq,
303    {
304        let index = self.label_to_asset_index.get(label)?;
305        let labeled = &self.labeled_assets[*index];
306        Some(&labeled.asset)
307    }
308
309    /// Returns the labeled asset given its asset ID if it exists and matches the type.
310    ///
311    /// This can be used to get the asset from its handle since `&Handle` implements
312    /// [`Into<AssetId<B>>`].
313    pub fn get_labeled_by_id<B: Asset>(
314        &mut self,
315        id: impl Into<AssetId<B>>,
316    ) -> Option<TransformedSubAsset<'_, B>> {
317        let index = self.asset_id_to_asset_index.get(&id.into().untyped())?;
318        let labeled = &mut self.labeled_assets[*index];
319        let value = labeled.asset.value.downcast_mut::<B>()?;
320        Some(TransformedSubAsset {
321            value,
322            labeled_assets: &mut labeled.asset.labeled_assets,
323            label_to_asset_index: &mut labeled.asset.label_to_asset_index,
324            asset_id_to_asset_index: &mut labeled.asset.asset_id_to_asset_index,
325        })
326    }
327
328    /// Returns the type-erased labeled asset given its asset ID if it exists.
329    ///
330    /// This can be used to get the asset from its handle since `&UntypedHandle` implements
331    /// [`Into<UntypedAssetId>`].
332    pub fn get_erased_labeled_by_id(
333        &self,
334        id: impl Into<UntypedAssetId>,
335    ) -> Option<&ErasedLoadedAsset> {
336        let index = self.asset_id_to_asset_index.get(&id.into())?;
337        let labeled = &self.labeled_assets[*index];
338        Some(&labeled.asset)
339    }
340
341    /// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
342    pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
343    where
344        CowArc<'static, str>: Borrow<Q>,
345        Q: ?Sized + Hash + Eq,
346    {
347        let index = self.label_to_asset_index.get(label)?;
348        let labeled = &self.labeled_assets[*index];
349        Some(labeled.handle.clone())
350    }
351
352    /// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
353    pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
354    where
355        CowArc<'static, str>: Borrow<Q>,
356        Q: ?Sized + Hash + Eq,
357    {
358        let index = self.label_to_asset_index.get(label)?;
359        let labeled = &self.labeled_assets[*index];
360        if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
361            return Some(handle);
362        }
363        None
364    }
365
366    /// Adds `asset` as a labeled sub asset using `label` and `handle`
367    pub fn insert_labeled(
368        &mut self,
369        label: impl Into<CowArc<'static, str>>,
370        handle: impl Into<UntypedHandle>,
371        asset: impl Into<ErasedLoadedAsset>,
372    ) {
373        let labeled = LabeledAsset {
374            asset: asset.into(),
375            handle: handle.into(),
376        };
377        match self.label_to_asset_index.entry(label.into()) {
378            Entry::Occupied(entry) => {
379                let labeled_entry = &mut self.labeled_assets[*entry.get()];
380                if labeled.handle != labeled_entry.handle {
381                    self.asset_id_to_asset_index
382                        .remove(&labeled_entry.handle.id());
383                    self.asset_id_to_asset_index
384                        .insert(labeled.handle.id(), *entry.get());
385                }
386                *labeled_entry = labeled;
387            }
388            Entry::Vacant(entry) => {
389                entry.insert(self.labeled_assets.len());
390                self.asset_id_to_asset_index
391                    .insert(labeled.handle.id(), self.labeled_assets.len());
392                self.labeled_assets.push(labeled);
393            }
394        }
395    }
396    /// Iterate over all labels for "labeled assets" in the loaded asset
397    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
398        self.label_to_asset_index.keys().map(|s| &**s)
399    }
400}
401
402/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.]
403#[derive(TypePath)]
404pub struct IdentityAssetTransformer<A: Asset> {
405    _phantom: PhantomData<fn(A) -> A>,
406}
407
408impl<A: Asset> IdentityAssetTransformer<A> {
409    /// Creates a new [`IdentityAssetTransformer`] with the correct internal [`PhantomData`] field.
410    pub const fn new() -> Self {
411        Self {
412            _phantom: PhantomData,
413        }
414    }
415}
416
417impl<A: Asset> Default for IdentityAssetTransformer<A> {
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423impl<A: Asset> AssetTransformer for IdentityAssetTransformer<A> {
424    type AssetInput = A;
425    type AssetOutput = A;
426    type Settings = ();
427    type Error = Infallible;
428
429    async fn transform<'a>(
430        &'a self,
431        asset: TransformedAsset<Self::AssetInput>,
432        _settings: &'a Self::Settings,
433    ) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
434        Ok(asset)
435    }
436}