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