bevy_asset/
transformer.rs

1use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle};
2use alloc::boxed::Box;
3use atomicow::CowArc;
4use bevy_platform::collections::HashMap;
5use bevy_tasks::ConditionalSendFuture;
6use core::{
7    borrow::Borrow,
8    convert::Infallible,
9    hash::Hash,
10    marker::PhantomData,
11    ops::{Deref, DerefMut},
12};
13use serde::{Deserialize, Serialize};
14
15/// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type.
16///
17/// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows.
18pub trait AssetTransformer: Send + Sync + 'static {
19    /// The [`Asset`] type which this [`AssetTransformer`] takes as and input.
20    type AssetInput: Asset;
21    /// The [`Asset`] type which this [`AssetTransformer`] outputs.
22    type AssetOutput: Asset;
23    /// The settings type used by this [`AssetTransformer`].
24    type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
25    /// The type of [error](`std::error::Error`) which could be encountered by this transformer.
26    type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
27
28    /// Transforms the given [`TransformedAsset`] to [`AssetTransformer::AssetOutput`].
29    /// The [`TransformedAsset`]'s `labeled_assets` can be altered to add new Labeled Sub-Assets
30    /// The passed in `settings` can influence how the `asset` is transformed
31    fn transform<'a>(
32        &'a self,
33        asset: TransformedAsset<Self::AssetInput>,
34        settings: &'a Self::Settings,
35    ) -> impl ConditionalSendFuture<Output = Result<TransformedAsset<Self::AssetOutput>, Self::Error>>;
36}
37
38/// An [`Asset`] (and any "sub assets") intended to be transformed
39pub struct TransformedAsset<A: Asset> {
40    pub(crate) value: A,
41    pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
42}
43
44impl<A: Asset> Deref for TransformedAsset<A> {
45    type Target = A;
46    fn deref(&self) -> &Self::Target {
47        &self.value
48    }
49}
50
51impl<A: Asset> DerefMut for TransformedAsset<A> {
52    fn deref_mut(&mut self) -> &mut Self::Target {
53        &mut self.value
54    }
55}
56
57impl<A: Asset> TransformedAsset<A> {
58    /// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`.
59    pub fn from_loaded(asset: ErasedLoadedAsset) -> Option<Self> {
60        if let Ok(value) = asset.value.downcast::<A>() {
61            return Some(TransformedAsset {
62                value: *value,
63                labeled_assets: asset.labeled_assets,
64            });
65        }
66        None
67    }
68    /// Creates a new [`TransformedAsset`] from `asset`, transferring the `labeled_assets` from this [`TransformedAsset`] to the new one
69    pub fn replace_asset<B: Asset>(self, asset: B) -> TransformedAsset<B> {
70        TransformedAsset {
71            value: asset,
72            labeled_assets: self.labeled_assets,
73        }
74    }
75    /// Takes the labeled assets from `labeled_source` and places them in this [`TransformedAsset`]
76    pub fn take_labeled_assets<B: Asset>(&mut self, labeled_source: TransformedAsset<B>) {
77        self.labeled_assets = labeled_source.labeled_assets;
78    }
79    /// Retrieves the value of this asset.
80    #[inline]
81    pub fn get(&self) -> &A {
82        &self.value
83    }
84    /// Mutably retrieves the value of this asset.
85    #[inline]
86    pub fn get_mut(&mut self) -> &mut A {
87        &mut self.value
88    }
89    /// Returns the labeled asset, if it exists and matches this type.
90    pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
91    where
92        CowArc<'static, str>: Borrow<Q>,
93        Q: ?Sized + Hash + Eq,
94    {
95        let labeled = self.labeled_assets.get_mut(label)?;
96        let value = labeled.asset.value.downcast_mut::<B>()?;
97        Some(TransformedSubAsset {
98            value,
99            labeled_assets: &mut labeled.asset.labeled_assets,
100        })
101    }
102    /// Returns the type-erased labeled asset, if it exists and matches this type.
103    pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
104    where
105        CowArc<'static, str>: Borrow<Q>,
106        Q: ?Sized + Hash + Eq,
107    {
108        let labeled = self.labeled_assets.get(label)?;
109        Some(&labeled.asset)
110    }
111    /// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
112    pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
113    where
114        CowArc<'static, str>: Borrow<Q>,
115        Q: ?Sized + Hash + Eq,
116    {
117        let labeled = self.labeled_assets.get(label)?;
118        Some(labeled.handle.clone())
119    }
120    /// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
121    pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
122    where
123        CowArc<'static, str>: Borrow<Q>,
124        Q: ?Sized + Hash + Eq,
125    {
126        let labeled = self.labeled_assets.get(label)?;
127        if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
128            return Some(handle);
129        }
130        None
131    }
132    /// Adds `asset` as a labeled sub asset using `label` and `handle`
133    pub fn insert_labeled(
134        &mut self,
135        label: impl Into<CowArc<'static, str>>,
136        handle: impl Into<UntypedHandle>,
137        asset: impl Into<ErasedLoadedAsset>,
138    ) {
139        let labeled = LabeledAsset {
140            asset: asset.into(),
141            handle: handle.into(),
142        };
143        self.labeled_assets.insert(label.into(), labeled);
144    }
145    /// Iterate over all labels for "labeled assets" in the loaded asset
146    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
147        self.labeled_assets.keys().map(|s| &**s)
148    }
149}
150
151/// A labeled sub-asset of [`TransformedAsset`]
152pub struct TransformedSubAsset<'a, A: Asset> {
153    value: &'a mut A,
154    labeled_assets: &'a mut HashMap<CowArc<'static, str>, LabeledAsset>,
155}
156
157impl<'a, A: Asset> Deref for TransformedSubAsset<'a, A> {
158    type Target = A;
159    fn deref(&self) -> &Self::Target {
160        self.value
161    }
162}
163
164impl<'a, A: Asset> DerefMut for TransformedSubAsset<'a, A> {
165    fn deref_mut(&mut self) -> &mut Self::Target {
166        self.value
167    }
168}
169
170impl<'a, A: Asset> TransformedSubAsset<'a, A> {
171    /// Creates a new [`TransformedSubAsset`] from `asset` if its internal value matches `A`.
172    pub fn from_loaded(asset: &'a mut ErasedLoadedAsset) -> Option<Self> {
173        let value = asset.value.downcast_mut::<A>()?;
174        Some(TransformedSubAsset {
175            value,
176            labeled_assets: &mut asset.labeled_assets,
177        })
178    }
179    /// Retrieves the value of this asset.
180    #[inline]
181    pub fn get(&self) -> &A {
182        self.value
183    }
184    /// Mutably retrieves the value of this asset.
185    #[inline]
186    pub fn get_mut(&mut self) -> &mut A {
187        self.value
188    }
189    /// Returns the labeled asset, if it exists and matches this type.
190    pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
191    where
192        CowArc<'static, str>: Borrow<Q>,
193        Q: ?Sized + Hash + Eq,
194    {
195        let labeled = self.labeled_assets.get_mut(label)?;
196        let value = labeled.asset.value.downcast_mut::<B>()?;
197        Some(TransformedSubAsset {
198            value,
199            labeled_assets: &mut labeled.asset.labeled_assets,
200        })
201    }
202    /// Returns the type-erased labeled asset, if it exists and matches this type.
203    pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
204    where
205        CowArc<'static, str>: Borrow<Q>,
206        Q: ?Sized + Hash + Eq,
207    {
208        let labeled = self.labeled_assets.get(label)?;
209        Some(&labeled.asset)
210    }
211    /// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
212    pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
213    where
214        CowArc<'static, str>: Borrow<Q>,
215        Q: ?Sized + Hash + Eq,
216    {
217        let labeled = self.labeled_assets.get(label)?;
218        Some(labeled.handle.clone())
219    }
220    /// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
221    pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
222    where
223        CowArc<'static, str>: Borrow<Q>,
224        Q: ?Sized + Hash + Eq,
225    {
226        let labeled = self.labeled_assets.get(label)?;
227        if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
228            return Some(handle);
229        }
230        None
231    }
232    /// Adds `asset` as a labeled sub asset using `label` and `handle`
233    pub fn insert_labeled(
234        &mut self,
235        label: impl Into<CowArc<'static, str>>,
236        handle: impl Into<UntypedHandle>,
237        asset: impl Into<ErasedLoadedAsset>,
238    ) {
239        let labeled = LabeledAsset {
240            asset: asset.into(),
241            handle: handle.into(),
242        };
243        self.labeled_assets.insert(label.into(), labeled);
244    }
245    /// Iterate over all labels for "labeled assets" in the loaded asset
246    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
247        self.labeled_assets.keys().map(|s| &**s)
248    }
249}
250
251/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.]
252pub struct IdentityAssetTransformer<A: Asset> {
253    _phantom: PhantomData<fn(A) -> A>,
254}
255
256impl<A: Asset> IdentityAssetTransformer<A> {
257    /// Creates a new [`IdentityAssetTransformer`] with the correct internal [`PhantomData`] field.
258    pub const fn new() -> Self {
259        Self {
260            _phantom: PhantomData,
261        }
262    }
263}
264
265impl<A: Asset> Default for IdentityAssetTransformer<A> {
266    fn default() -> Self {
267        Self::new()
268    }
269}
270
271impl<A: Asset> AssetTransformer for IdentityAssetTransformer<A> {
272    type AssetInput = A;
273    type AssetOutput = A;
274    type Settings = ();
275    type Error = Infallible;
276
277    async fn transform<'a>(
278        &'a self,
279        asset: TransformedAsset<Self::AssetInput>,
280        _settings: &'a Self::Settings,
281    ) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
282        Ok(asset)
283    }
284}