1use alloc::{
2 boxed::Box,
3 string::{String, ToString},
4 vec::Vec,
5};
6use futures_lite::AsyncReadExt;
7
8use crate::{
9 io::{AssetReaderError, Reader},
10 loader::AssetLoader,
11 processor::Process,
12 Asset, AssetPath, DeserializeMetaError, VisitAssetDependencies,
13};
14use downcast_rs::{impl_downcast, Downcast};
15use ron::ser::PrettyConfig;
16use serde::{Deserialize, Serialize};
17use tracing::error;
18
19pub const META_FORMAT_VERSION: &str = "1.0";
20pub type MetaTransform = Box<dyn Fn(&mut dyn AssetMetaDyn) + Send + Sync>;
21
22#[derive(Serialize, Deserialize)]
27pub struct AssetMeta<L: AssetLoader, P: Process> {
28 pub meta_format_version: String,
31 #[serde(skip_serializing_if = "Option::is_none")]
36 pub processed_info: Option<ProcessedInfo>,
37 pub asset: AssetAction<L::Settings, P::Settings>,
39}
40
41impl<L: AssetLoader, P: Process> AssetMeta<L, P> {
42 pub fn new(asset: AssetAction<L::Settings, P::Settings>) -> Self {
43 Self {
44 meta_format_version: META_FORMAT_VERSION.to_string(),
45 processed_info: None,
46 asset,
47 }
48 }
49
50 pub fn deserialize(bytes: &[u8]) -> Result<Self, DeserializeMetaError> {
52 Ok(ron::de::from_bytes(bytes)?)
53 }
54}
55
56#[derive(Serialize, Deserialize)]
58pub enum AssetAction<LoaderSettings, ProcessSettings> {
59 Load {
62 loader: String,
63 settings: LoaderSettings,
64 },
65 Process {
70 processor: String,
71 settings: ProcessSettings,
72 },
73 Ignore,
75}
76
77#[derive(Serialize, Deserialize, Default, Debug, Clone)]
82pub struct ProcessedInfo {
83 pub hash: AssetHash,
85 pub full_hash: AssetHash,
87 pub process_dependencies: Vec<ProcessDependencyInfo>,
89}
90
91#[derive(Serialize, Deserialize, Debug, Clone)]
94pub struct ProcessDependencyInfo {
95 pub full_hash: AssetHash,
96 pub path: AssetPath<'static>,
97}
98
99#[derive(Serialize, Deserialize)]
106pub struct AssetMetaMinimal {
107 pub asset: AssetActionMinimal,
108}
109
110#[derive(Serialize, Deserialize)]
113pub enum AssetActionMinimal {
114 Load { loader: String },
115 Process { processor: String },
116 Ignore,
117}
118
119#[derive(Serialize, Deserialize)]
122pub struct ProcessedInfoMinimal {
123 pub processed_info: Option<ProcessedInfo>,
124}
125
126pub trait AssetMetaDyn: Downcast + Send + Sync {
129 fn loader_settings(&self) -> Option<&dyn Settings>;
131 fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings>;
133 fn process_settings(&self) -> Option<&dyn Settings>;
135 fn serialize(&self) -> Vec<u8>;
137 fn processed_info(&self) -> &Option<ProcessedInfo>;
139 fn processed_info_mut(&mut self) -> &mut Option<ProcessedInfo>;
141}
142
143impl<L: AssetLoader, P: Process> AssetMetaDyn for AssetMeta<L, P> {
144 fn loader_settings(&self) -> Option<&dyn Settings> {
145 if let AssetAction::Load { settings, .. } = &self.asset {
146 Some(settings)
147 } else {
148 None
149 }
150 }
151 fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings> {
152 if let AssetAction::Load { settings, .. } = &mut self.asset {
153 Some(settings)
154 } else {
155 None
156 }
157 }
158 fn process_settings(&self) -> Option<&dyn Settings> {
159 if let AssetAction::Process { settings, .. } = &self.asset {
160 Some(settings)
161 } else {
162 None
163 }
164 }
165 fn serialize(&self) -> Vec<u8> {
166 ron::ser::to_string_pretty(&self, PrettyConfig::default())
167 .expect("type is convertible to ron")
168 .into_bytes()
169 }
170 fn processed_info(&self) -> &Option<ProcessedInfo> {
171 &self.processed_info
172 }
173 fn processed_info_mut(&mut self) -> &mut Option<ProcessedInfo> {
174 &mut self.processed_info
175 }
176}
177
178impl_downcast!(AssetMetaDyn);
179
180pub trait Settings: Downcast + Send + Sync + 'static {}
184
185impl<T: 'static> Settings for T where T: Send + Sync {}
186
187impl_downcast!(Settings);
188
189impl Process for () {
191 type Settings = ();
192 type OutputLoader = ();
193
194 async fn process(
195 &self,
196 _context: &mut bevy_asset::processor::ProcessContext<'_>,
197 _settings: &Self::Settings,
198 _writer: &mut bevy_asset::io::Writer,
199 ) -> Result<(), bevy_asset::processor::ProcessError> {
200 unreachable!()
201 }
202}
203
204impl Asset for () {}
205
206impl VisitAssetDependencies for () {
207 fn visit_dependencies(&self, _visit: &mut impl FnMut(bevy_asset::UntypedAssetId)) {
208 unreachable!()
209 }
210}
211
212impl AssetLoader for () {
214 type Asset = ();
215 type Settings = ();
216 type Error = std::io::Error;
217 async fn load(
218 &self,
219 _reader: &mut dyn Reader,
220 _settings: &Self::Settings,
221 _load_context: &mut crate::LoadContext<'_>,
222 ) -> Result<Self::Asset, Self::Error> {
223 unreachable!();
224 }
225
226 fn reader_required_features(_settings: &Self::Settings) -> crate::io::ReaderRequiredFeatures {
227 unreachable!();
228 }
229
230 fn extensions(&self) -> &[&str] {
231 unreachable!();
232 }
233}
234
235pub(crate) fn meta_transform_settings<S: Settings>(
236 meta: &mut dyn AssetMetaDyn,
237 settings: &(impl Fn(&mut S) + Send + Sync + 'static),
238) {
239 if let Some(loader_settings) = meta.loader_settings_mut() {
240 if let Some(loader_settings) = loader_settings.downcast_mut::<S>() {
241 settings(loader_settings);
242 } else {
243 error!(
244 "Configured settings type {} does not match AssetLoader settings type",
245 core::any::type_name::<S>(),
246 );
247 }
248 }
249}
250
251pub(crate) fn loader_settings_meta_transform<S: Settings>(
252 settings: impl Fn(&mut S) + Send + Sync + 'static,
253) -> MetaTransform {
254 Box::new(move |meta| meta_transform_settings(meta, &settings))
255}
256
257pub type AssetHash = [u8; 32];
258
259pub(crate) async fn get_asset_hash(
261 meta_bytes: &[u8],
262 asset_reader: &mut impl Reader,
263) -> Result<AssetHash, AssetReaderError> {
264 let mut hasher = blake3::Hasher::new();
265 hasher.update(meta_bytes);
266 let mut buffer = [0; blake3::CHUNK_LEN];
267 loop {
268 let bytes_read = asset_reader.read(&mut buffer).await?;
269 hasher.update(&buffer[..bytes_read]);
270 if bytes_read == 0 {
271 break;
273 }
274 }
275 Ok(*hasher.finalize().as_bytes())
276}
277
278pub(crate) fn get_full_asset_hash(
280 asset_hash: AssetHash,
281 dependency_hashes: impl Iterator<Item = AssetHash>,
282) -> AssetHash {
283 let mut hasher = blake3::Hasher::new();
284 hasher.update(&asset_hash);
285 for hash in dependency_hashes {
286 hasher.update(&hash);
287 }
288 *hasher.finalize().as_bytes()
289}