Skip to main content

bevy_asset/
transformer.rs

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