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