bevy_asset/
saver.rs

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