bevy_asset/
meta.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
use crate::{
    self as bevy_asset, loader::AssetLoader, processor::Process, Asset, AssetPath,
    DeserializeMetaError, VisitAssetDependencies,
};
use bevy_utils::tracing::error;
use downcast_rs::{impl_downcast, Downcast};
use ron::ser::PrettyConfig;
use serde::{Deserialize, Serialize};

pub const META_FORMAT_VERSION: &str = "1.0";
pub type MetaTransform = Box<dyn Fn(&mut dyn AssetMetaDyn) + Send + Sync>;

/// Asset metadata that informs how an [`Asset`] should be handled by the asset system.
///
/// `L` is the [`AssetLoader`] (if one is configured) for the [`AssetAction`]. This can be `()` if it is not required.
/// `P` is the [`Process`] processor, if one is configured for the [`AssetAction`]. This can be `()` if it is not required.
#[derive(Serialize, Deserialize)]
pub struct AssetMeta<L: AssetLoader, P: Process> {
    /// The version of the meta format being used. This will change whenever a breaking change is made to
    /// the meta format.
    pub meta_format_version: String,
    /// Information produced by the [`AssetProcessor`] _after_ processing this asset.
    /// This will only exist alongside processed versions of assets. You should not manually set it in your asset source files.
    ///
    /// [`AssetProcessor`]: crate::processor::AssetProcessor
    #[serde(skip_serializing_if = "Option::is_none")]
    pub processed_info: Option<ProcessedInfo>,
    /// How to handle this asset in the asset system. See [`AssetAction`].
    pub asset: AssetAction<L::Settings, P::Settings>,
}

impl<L: AssetLoader, P: Process> AssetMeta<L, P> {
    pub fn new(asset: AssetAction<L::Settings, P::Settings>) -> Self {
        Self {
            meta_format_version: META_FORMAT_VERSION.to_string(),
            processed_info: None,
            asset,
        }
    }

    /// Deserializes the given serialized byte representation of the asset meta.
    pub fn deserialize(bytes: &[u8]) -> Result<Self, DeserializeMetaError> {
        Ok(ron::de::from_bytes(bytes)?)
    }
}

/// Configures how an asset source file should be handled by the asset system.
#[derive(Serialize, Deserialize)]
pub enum AssetAction<LoaderSettings, ProcessSettings> {
    /// Load the asset with the given loader and settings
    /// See [`AssetLoader`].
    Load {
        loader: String,
        settings: LoaderSettings,
    },
    /// Process the asset with the given processor and settings.
    /// See [`Process`] and [`AssetProcessor`].
    ///
    /// [`AssetProcessor`]: crate::processor::AssetProcessor
    Process {
        processor: String,
        settings: ProcessSettings,
    },
    /// Do nothing with the asset
    Ignore,
}

/// Info produced by the [`AssetProcessor`] for a given processed asset. This is used to determine if an
/// asset source file (or its dependencies) has changed.
///
/// [`AssetProcessor`]: crate::processor::AssetProcessor
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct ProcessedInfo {
    /// A hash of the asset bytes and the asset .meta data
    pub hash: AssetHash,
    /// A hash of the asset bytes, the asset .meta data, and the `full_hash` of every `process_dependency`
    pub full_hash: AssetHash,
    /// Information about the "process dependencies" used to process this asset.
    pub process_dependencies: Vec<ProcessDependencyInfo>,
}

/// Information about a dependency used to process an asset. This is used to determine whether an asset's "process dependency"
/// has changed.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProcessDependencyInfo {
    pub full_hash: AssetHash,
    pub path: AssetPath<'static>,
}

/// This is a minimal counterpart to [`AssetMeta`] that exists to speed up (or enable) serialization in cases where the whole [`AssetMeta`] isn't
/// necessary.
// PERF:
// Currently, this is used when retrieving asset loader and processor information (when the actual type is not known yet). This could probably
// be replaced (and made more efficient) by a custom deserializer that reads the loader/processor information _first_, then deserializes the contents
// using a type registry.
#[derive(Serialize, Deserialize)]
pub struct AssetMetaMinimal {
    pub asset: AssetActionMinimal,
}

/// This is a minimal counterpart to [`AssetAction`] that exists to speed up (or enable) serialization in cases where the whole [`AssetAction`]
/// isn't necessary.
#[derive(Serialize, Deserialize)]
pub enum AssetActionMinimal {
    Load { loader: String },
    Process { processor: String },
    Ignore,
}

/// This is a minimal counterpart to [`ProcessedInfo`] that exists to speed up serialization in cases where the whole [`ProcessedInfo`] isn't
/// necessary.
#[derive(Serialize, Deserialize)]
pub struct ProcessedInfoMinimal {
    pub processed_info: Option<ProcessedInfo>,
}

/// A dynamic type-erased counterpart to [`AssetMeta`] that enables passing around and interacting with [`AssetMeta`] without knowing
/// its type.
pub trait AssetMetaDyn: Downcast + Send + Sync {
    /// Returns a reference to the [`AssetLoader`] settings, if they exist.
    fn loader_settings(&self) -> Option<&dyn Settings>;
    /// Returns a mutable reference to the [`AssetLoader`] settings, if they exist.
    fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings>;
    /// Serializes the internal [`AssetMeta`].
    fn serialize(&self) -> Vec<u8>;
    /// Returns a reference to the [`ProcessedInfo`] if it exists.
    fn processed_info(&self) -> &Option<ProcessedInfo>;
    /// Returns a mutable reference to the [`ProcessedInfo`] if it exists.
    fn processed_info_mut(&mut self) -> &mut Option<ProcessedInfo>;
}

impl<L: AssetLoader, P: Process> AssetMetaDyn for AssetMeta<L, P> {
    fn loader_settings(&self) -> Option<&dyn Settings> {
        if let AssetAction::Load { settings, .. } = &self.asset {
            Some(settings)
        } else {
            None
        }
    }
    fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings> {
        if let AssetAction::Load { settings, .. } = &mut self.asset {
            Some(settings)
        } else {
            None
        }
    }
    fn serialize(&self) -> Vec<u8> {
        ron::ser::to_string_pretty(&self, PrettyConfig::default())
            .expect("type is convertible to ron")
            .into_bytes()
    }
    fn processed_info(&self) -> &Option<ProcessedInfo> {
        &self.processed_info
    }
    fn processed_info_mut(&mut self) -> &mut Option<ProcessedInfo> {
        &mut self.processed_info
    }
}

impl_downcast!(AssetMetaDyn);

/// Settings used by the asset system, such as by [`AssetLoader`], [`Process`], and [`AssetSaver`]
///
/// [`AssetSaver`]: crate::saver::AssetSaver
pub trait Settings: Downcast + Send + Sync + 'static {}

impl<T: 'static> Settings for T where T: Send + Sync {}

impl_downcast!(Settings);

/// The () processor should never be called. This implementation exists to make the meta format nicer to work with.
impl Process for () {
    type Settings = ();
    type OutputLoader = ();

    async fn process(
        &self,
        _context: &mut bevy_asset::processor::ProcessContext<'_>,
        _meta: AssetMeta<(), Self>,
        _writer: &mut bevy_asset::io::Writer,
    ) -> Result<(), bevy_asset::processor::ProcessError> {
        unreachable!()
    }
}

impl Asset for () {}

impl VisitAssetDependencies for () {
    fn visit_dependencies(&self, _visit: &mut impl FnMut(bevy_asset::UntypedAssetId)) {
        unreachable!()
    }
}

/// The () loader should never be called. This implementation exists to make the meta format nicer to work with.
impl AssetLoader for () {
    type Asset = ();
    type Settings = ();
    type Error = std::io::Error;
    async fn load(
        &self,
        _reader: &mut dyn crate::io::Reader,
        _settings: &Self::Settings,
        _load_context: &mut crate::LoadContext<'_>,
    ) -> Result<Self::Asset, Self::Error> {
        unreachable!();
    }

    fn extensions(&self) -> &[&str] {
        unreachable!();
    }
}

pub(crate) fn meta_transform_settings<S: Settings>(
    meta: &mut dyn AssetMetaDyn,
    settings: &(impl Fn(&mut S) + Send + Sync + 'static),
) {
    if let Some(loader_settings) = meta.loader_settings_mut() {
        if let Some(loader_settings) = loader_settings.downcast_mut::<S>() {
            settings(loader_settings);
        } else {
            error!(
                "Configured settings type {} does not match AssetLoader settings type",
                core::any::type_name::<S>(),
            );
        }
    }
}

pub(crate) fn loader_settings_meta_transform<S: Settings>(
    settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> MetaTransform {
    Box::new(move |meta| meta_transform_settings(meta, &settings))
}

pub type AssetHash = [u8; 32];

/// NOTE: changing the hashing logic here is a _breaking change_ that requires a [`META_FORMAT_VERSION`] bump.
pub(crate) fn get_asset_hash(meta_bytes: &[u8], asset_bytes: &[u8]) -> AssetHash {
    let mut hasher = blake3::Hasher::new();
    hasher.update(meta_bytes);
    hasher.update(asset_bytes);
    *hasher.finalize().as_bytes()
}

/// NOTE: changing the hashing logic here is a _breaking change_ that requires a [`META_FORMAT_VERSION`] bump.
pub(crate) fn get_full_asset_hash(
    asset_hash: AssetHash,
    dependency_hashes: impl Iterator<Item = AssetHash>,
) -> AssetHash {
    let mut hasher = blake3::Hasher::new();
    hasher.update(&asset_hash);
    for hash in dependency_hashes {
        hasher.update(&hash);
    }
    *hasher.finalize().as_bytes()
}