bevy_asset/
saver.rs

1use crate::{
2    io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader,
3    ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
4};
5use atomicow::CowArc;
6use bevy_utils::{BoxedFuture, ConditionalSendFuture, HashMap};
7use core::{borrow::Borrow, hash::Hash, ops::Deref};
8use serde::{Deserialize, Serialize};
9
10/// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset
11/// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read.
12///
13/// This trait is generally used in concert with [`AssetWriter`](crate::io::AssetWriter) to write assets as bytes.
14///
15/// For a complementary version of this trait that can load assets, see [`AssetLoader`].
16pub trait AssetSaver: Send + Sync + 'static {
17    /// The top level [`Asset`] saved by this [`AssetSaver`].
18    type Asset: Asset;
19    /// The settings type used by this [`AssetSaver`].
20    type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
21    /// The type of [`AssetLoader`] used to load this [`Asset`]
22    type OutputLoader: AssetLoader;
23    /// The type of [error](`std::error::Error`) which could be encountered by this saver.
24    type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
25
26    /// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
27    /// `asset` is saved.
28    fn save(
29        &self,
30        writer: &mut Writer,
31        asset: SavedAsset<'_, Self::Asset>,
32        settings: &Self::Settings,
33    ) -> impl ConditionalSendFuture<
34        Output = Result<<Self::OutputLoader as AssetLoader>::Settings, Self::Error>,
35    >;
36}
37
38/// A type-erased dynamic variant of [`AssetSaver`] that allows callers to save assets without knowing the actual type of the [`AssetSaver`].
39pub trait ErasedAssetSaver: Send + Sync + 'static {
40    /// Saves the given runtime [`ErasedLoadedAsset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
41    /// `asset` is saved.
42    fn save<'a>(
43        &'a self,
44        writer: &'a mut Writer,
45        asset: &'a ErasedLoadedAsset,
46        settings: &'a dyn Settings,
47    ) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>>;
48
49    /// The type name of the [`AssetSaver`].
50    fn type_name(&self) -> &'static str;
51}
52
53impl<S: AssetSaver> ErasedAssetSaver for S {
54    fn save<'a>(
55        &'a self,
56        writer: &'a mut Writer,
57        asset: &'a ErasedLoadedAsset,
58        settings: &'a dyn Settings,
59    ) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>> {
60        Box::pin(async move {
61            let settings = settings
62                .downcast_ref::<S::Settings>()
63                .expect("AssetLoader settings should match the loader type");
64            let saved_asset = SavedAsset::<S::Asset>::from_loaded(asset).unwrap();
65            if let Err(err) = self.save(writer, saved_asset, settings).await {
66                return Err(err.into());
67            }
68            Ok(())
69        })
70    }
71    fn type_name(&self) -> &'static str {
72        core::any::type_name::<S>()
73    }
74}
75
76/// An [`Asset`] (and any labeled "sub assets") intended to be saved.
77pub struct SavedAsset<'a, A: Asset> {
78    value: &'a A,
79    labeled_assets: &'a HashMap<CowArc<'static, str>, LabeledAsset>,
80}
81
82impl<'a, A: Asset> Deref for SavedAsset<'a, A> {
83    type Target = A;
84
85    fn deref(&self) -> &Self::Target {
86        self.value
87    }
88}
89
90impl<'a, A: Asset> SavedAsset<'a, A> {
91    /// Creates a new [`SavedAsset`] from `asset` if its internal value matches `A`.
92    pub fn from_loaded(asset: &'a ErasedLoadedAsset) -> Option<Self> {
93        let value = asset.value.downcast_ref::<A>()?;
94        Some(SavedAsset {
95            value,
96            labeled_assets: &asset.labeled_assets,
97        })
98    }
99
100    /// Creates a new [`SavedAsset`] from the a [`TransformedAsset`]
101    pub fn from_transformed(asset: &'a TransformedAsset<A>) -> Self {
102        Self {
103            value: &asset.value,
104            labeled_assets: &asset.labeled_assets,
105        }
106    }
107
108    /// Retrieves the value of this asset.
109    #[inline]
110    pub fn get(&self) -> &'a A {
111        self.value
112    }
113
114    /// Returns the labeled asset, if it exists and matches this type.
115    pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<SavedAsset<B>>
116    where
117        CowArc<'static, str>: Borrow<Q>,
118        Q: ?Sized + Hash + Eq,
119    {
120        let labeled = self.labeled_assets.get(label)?;
121        let value = labeled.asset.value.downcast_ref::<B>()?;
122        Some(SavedAsset {
123            value,
124            labeled_assets: &labeled.asset.labeled_assets,
125        })
126    }
127
128    /// Returns the type-erased labeled asset, if it exists and matches this type.
129    pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
130    where
131        CowArc<'static, str>: Borrow<Q>,
132        Q: ?Sized + Hash + Eq,
133    {
134        let labeled = self.labeled_assets.get(label)?;
135        Some(&labeled.asset)
136    }
137
138    /// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
139    pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
140    where
141        CowArc<'static, str>: Borrow<Q>,
142        Q: ?Sized + Hash + Eq,
143    {
144        let labeled = self.labeled_assets.get(label)?;
145        Some(labeled.handle.clone())
146    }
147
148    /// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
149    pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
150    where
151        CowArc<'static, str>: Borrow<Q>,
152        Q: ?Sized + Hash + Eq,
153    {
154        let labeled = self.labeled_assets.get(label)?;
155        if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
156            return Some(handle);
157        }
158        None
159    }
160
161    /// Iterate over all labels for "labeled assets" in the loaded asset
162    pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
163        self.labeled_assets.keys().map(|s| &**s)
164    }
165}