bones_asset/asset.rs
1use std::{
2 path::{Path, PathBuf},
3 pin::Pin,
4 sync::{
5 atomic::{AtomicU32, Ordering::SeqCst},
6 Arc,
7 },
8};
9
10use append_only_vec::AppendOnlyVec;
11use bones_utils::prelude::*;
12use dashmap::DashMap;
13use event_listener::{Event, EventListener};
14use futures_lite::future::Boxed as BoxedFuture;
15use parking_lot::Mutex;
16use semver::VersionReq;
17
18use crate::prelude::*;
19
20/// The unique ID for an asset pack.
21///
22/// Asset pack IDs are made up of a human-readable label, and a unique identifier. For example:
23///
24/// > awesome-pack_01h502c309fddv1vq1gwa918e8
25///
26/// These IDs can be generated with the [TypeID gen][gen] utility.
27///
28/// [gen]: https://zicklag.github.io/type-id-gen/
29pub type AssetPackId = LabeledId;
30
31/// An asset pack contains assets that are loaded by the game.
32///
33/// The game's built-in assets are contained the the core asset pack, and mods or other assets may
34/// also be loaded.
35#[derive(Clone, Debug)]
36pub struct AssetPack {
37 /// The display name of the asset pack.
38 pub name: String,
39 /// The unique ID of the asset pack.
40 pub id: AssetPackId,
41 /// The version number of the asset pack.
42 pub version: Version,
43
44 /// The game [`VersionReq`] this asset pack is compatible with.
45 pub game_version: VersionReq,
46
47 /// Schemas provided in the asset pack.
48 pub schemas: Vec<&'static Schema>,
49 /// The root asset for the asset pack.
50 pub root: UntypedHandle,
51}
52
53/// Specifies an asset pack, and it's exact version.
54#[derive(Clone, Debug, PartialEq, Eq, Hash)]
55pub struct AssetPackSpec {
56 /// The ID of the asset pack.
57 pub id: AssetPackId,
58 /// The version of the asset pack.
59 pub version: Version,
60}
61
62impl std::fmt::Display for AssetPackSpec {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 let Self { id, version } = self;
65 write!(f, "{id}@{version}")
66 }
67}
68
69/// A requirement specifier for an asset pack, made up of the asset pack's [`LabeledId`] and it's
70/// [`VersionReq`].
71#[derive(Debug, Clone)]
72pub struct AssetPackReq {
73 /// The asset pack ID.
74 pub id: LabeledId,
75 /// The version of the asset pack.
76 pub version: VersionReq,
77}
78
79/// A schema reference, containing the ID of the pack that defined the schema, and the name of the
80/// schema in the pack.
81#[derive(Clone, Debug)]
82pub struct SchemaPath {
83 /// The ID of the pack, or [`None`] if it refers to the core pack.
84 pub pack: Option<AssetPackReq>,
85 /// The name of the schema.
86 pub name: String,
87}
88
89/// Struct containing all the game's loaded assets, including the default assets and
90/// asset-packs/mods.
91pub struct LoadedAssets {
92 /// The game's default asset pack.
93 pub default: UntypedHandle,
94 /// Extra asset packs. The key is the the name of the asset pack.
95 pub packs: HashMap<String, UntypedHandle>,
96}
97
98/// The progress that has been made loading the game assets.
99#[derive(Debug, Clone, Default)]
100pub struct AssetLoadProgress {
101 assets_to_load: Arc<AtomicU32>,
102 assets_downloaded: Arc<AtomicU32>,
103 assets_loaded: Arc<AtomicU32>,
104 assets_errored: Arc<AtomicU32>,
105 /// The event notifier that is used to wake interested tasks that are waiting for asset load
106 /// to progress.
107 event: Arc<Event>,
108}
109
110impl AssetLoadProgress {
111 /// Increment the number of assets that need to be loaded by one.
112 pub fn inc_to_load(&self) {
113 self.assets_to_load.fetch_add(1, SeqCst);
114 }
115
116 /// Increment the number of assets that have errored during loading.
117 pub fn inc_errored(&self) {
118 self.assets_errored.fetch_add(1, SeqCst);
119 }
120
121 /// Increment the number of assets that have been downloaded by one.
122 pub fn inc_downloaded(&self) {
123 self.assets_downloaded.fetch_add(1, SeqCst);
124 }
125
126 /// Increment the number of assets that have been loaded by one.
127 pub fn inc_loaded(&self) {
128 self.assets_loaded.fetch_add(1, SeqCst);
129 self.event.notify(usize::MAX);
130 }
131
132 /// Get whether or not all the assets are done loading.
133 ///
134 /// > **Note:** Assets that have errored while loading are still counted as "done loading".
135 pub fn is_finished(&self) -> bool {
136 let loaded = self.assets_loaded.load(SeqCst);
137 let pending = self.assets_to_load.load(SeqCst);
138 let errored = self.assets_errored.load(SeqCst);
139 loaded != 0 && (loaded + errored) == pending
140 }
141
142 /// Get the number of assets that have been downloaded and loaded by their asset loaders.
143 pub fn loaded(&self) -> u32 {
144 self.assets_loaded.load(SeqCst)
145 }
146
147 /// Get the number of assets that have errored while loading.
148 pub fn errored(&self) -> u32 {
149 self.assets_errored.load(SeqCst)
150 }
151
152 /// Get the number of assets that must be loaded.
153 ///
154 /// Since assets are discovered as they are loaded this number may not be the final
155 /// asset count and may increase as more assets are discovered.
156 pub fn to_load(&self) -> u32 {
157 self.assets_to_load.load(SeqCst)
158 }
159
160 /// Get the number of assets that have had their data downloaded. Once an asset is downloaded
161 /// we have the raw bytes, but it may not have been processed by it's asset loader.
162 pub fn downloaded(&self) -> u32 {
163 self.assets_downloaded.load(SeqCst)
164 }
165
166 /// Get an event listener that will be notified each time asset load progress
167 /// has been updated.
168 pub fn listen(&self) -> Pin<Box<EventListener>> {
169 self.event.listen()
170 }
171}
172
173// TODO: Think of alternative to dashmap.
174// Dashmap is annoying to use because it wraps all returned assets from our API in dashmap
175// its reference container type to manage the locking. We should try to come up with a
176// way to manage the concurrent asset loading without requring the locks if possible.
177
178/// Stores assets for later retrieval.
179#[derive(Default, Clone, Debug)]
180pub struct AssetStore {
181 /// Maps the handle of the asset to it's content ID.
182 pub asset_ids: DashMap<UntypedHandle, Cid>,
183 /// Content addressed cache of raw bytes for asset data.
184 ///
185 /// Storing asset data in this ways allows you to easily replicate assets to other players over
186 /// the network by comparing available [`Cid`]s.
187 pub asset_data: DashMap<Cid, Vec<u8>>,
188 /// Maps asset content IDs, to assets that have been loaded by an asset loader from the raw
189 /// bytes.
190 pub assets: DashMap<Cid, LoadedAsset>,
191 /// Maps the asset [`AssetLoc`] to it's handle.
192 pub path_handles: DashMap<AssetLoc, UntypedHandle>,
193
194 /// List of assets that depend on the given assets.
195 pub reverse_dependencies: DashMap<UntypedHandle, HashSet<UntypedHandle>>,
196 /// Lists the packs that have not been loaded due to an incompatible game version.
197 pub incompabile_packs: DashMap<String, PackfileMeta>,
198
199 /// The core asset pack, if it's been loaded.
200 pub core_pack: Arc<Mutex<Option<AssetPack>>>,
201 /// The asset packs that have been loaded.
202 pub packs: DashMap<AssetPackSpec, AssetPack>,
203 /// Maps the directory names of asset packs to their [`AssetPackSpec`].
204 pub pack_dirs: DashMap<String, AssetPackSpec>,
205}
206
207/// Contains that path to an asset, and the pack_dir that it was loaded from.
208///
209/// A pack of [`None`] means that it was loaded from the core pack.
210#[derive(Clone, PartialEq, Eq, Hash, Debug)]
211pub struct AssetLoc {
212 /// The path to the asset in it's pack.
213 pub path: PathBuf,
214 /// The pack_dir of the pack that the asset is in.
215 pub pack: Option<String>,
216}
217
218impl AssetLoc {
219 /// Borrow as an [`AssetLocRef`].
220 pub fn as_ref(&self) -> AssetLocRef<'_> {
221 AssetLocRef {
222 pack: self.pack.as_deref(),
223 path: &self.path,
224 }
225 }
226}
227
228impl From<&AssetLocRef<'_>> for AssetLoc {
229 fn from(value: &AssetLocRef<'_>) -> Self {
230 AssetLoc {
231 path: value.path.to_owned(),
232 pack: value.pack.map(|x| x.to_owned()),
233 }
234 }
235}
236
237/// A borrowed version of [`AssetLoc`].
238#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)]
239pub struct AssetLocRef<'a> {
240 /// The path to the asset in it's pack.
241 pub path: &'a Path,
242 /// The pack_dir of the pack that the asset is in.
243 pub pack: Option<&'a str>,
244}
245
246impl<'a> From<(&'a Path, Option<&'a str>)> for AssetLocRef<'a> {
247 fn from((path, pack): (&'a Path, Option<&'a str>)) -> Self {
248 Self { pack, path }
249 }
250}
251
252impl AssetLocRef<'_> {
253 /// Clone data to an owned [`AssetLoc`].
254 pub fn to_owned(&self) -> AssetLoc {
255 self.into()
256 }
257}
258
259/// An asset that has been loaded.
260#[derive(Clone, Debug, Deref, DerefMut)]
261pub struct LoadedAsset {
262 /// The content ID of the loaded asset.
263 ///
264 /// This is a hash of the contents of the asset's binary data and all of the cids of it's
265 /// dependencies.
266 pub cid: Cid,
267 /// The asset pack this was loaded from, or [`None`] if it is from the default pack.
268 pub pack_spec: Option<AssetPackSpec>,
269 /// The pack and path the asset was loaded from.
270 pub loc: AssetLoc,
271 /// The content IDs of any assets needed by this asset as a dependency.
272 pub dependencies: Vec<UntypedHandle>,
273 /// The loaded data of the asset.
274 #[deref]
275 pub data: SchemaBox,
276}
277
278/// An identifier for an asset.
279#[derive(Clone, Debug, Eq, PartialEq, Hash, Default)]
280pub struct AssetInfo {
281 /// The unique ID of the asset pack this asset is located in.
282 pub pack: Cid,
283 /// The path to the asset, relative to the root of the asset pack.
284 pub path: PathBuf,
285}
286
287/// Context provided to custom asset loaders in the [`AssetLoader::load`] method.
288pub struct AssetLoadCtx {
289 /// The asset server.
290 pub asset_server: AssetServer,
291 /// The location of the asset.
292 pub loc: AssetLoc,
293 /// The [`Cid`]s of the assets this asset depends on.
294 ///
295 /// This is automatically updated when calling [`AssetLoadCtx::load_asset`].
296 pub dependencies: Arc<AppendOnlyVec<UntypedHandle>>,
297}
298
299impl AssetLoadCtx {
300 /// Load another asset as a child of this asset.
301 pub fn load_asset(&mut self, path: &Path) -> anyhow::Result<UntypedHandle> {
302 let handle = self.asset_server.load_asset(AssetLocRef {
303 path,
304 pack: self.loc.as_ref().pack,
305 });
306 self.dependencies.push(handle);
307 Ok(handle)
308 }
309}
310
311/// A custom assset loader.
312pub trait AssetLoader: Sync + Send + 'static {
313 /// Load the asset from raw bytes.
314 fn load(&self, ctx: AssetLoadCtx, bytes: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>>;
315}
316
317/// A custom asset loader implementation for a metadata asset.
318///
319/// This is similar in purpose to implementing [`AssetLoader`], but instead of loading from bytes,
320/// it loads from the deserialized [`SchemaRefMut`] of a metadata asset and must be added as a
321/// schema type data.
322#[derive(HasSchema)]
323#[schema(no_clone, no_default)]
324pub struct SchemaMetaAssetLoader(
325 pub fn(
326 ctx: &mut MetaAssetLoadCtx,
327 ptr: SchemaRefMut<'_>,
328 deserialzer: &mut dyn erased_serde::Deserializer,
329 ) -> anyhow::Result<()>,
330);
331
332impl SchemaMetaAssetLoader {
333 /// Load the asset
334 pub fn load<'a, 'de, D>(
335 &self,
336 ctx: &mut MetaAssetLoadCtx,
337 ptr: SchemaRefMut<'a>,
338 deserializer: D,
339 ) -> Result<(), erased_serde::Error>
340 where
341 D: serde::Deserializer<'de>,
342 {
343 use serde::de::Error;
344 let mut de = <dyn erased_serde::Deserializer>::erase(deserializer);
345 (self.0)(ctx, ptr, &mut de).map_err(|e| erased_serde::Error::custom(e.to_string()))
346 }
347}
348
349/// A [type data][bones_schema::alloc::TypeDatas] that indicates how to load a type as an asset.
350#[derive(HasSchema)]
351#[schema(opaque, no_default, no_clone)]
352pub enum AssetKind {
353 /// This is a metadata asset that can be loaded from JSON or YAML files.
354 Metadata {
355 /// The `extension` is the portion of the extension that comes before the `.json`, `.yml`,
356 /// or `.yaml` extension. For example, if the `extension` was set to `weapon`, then the asset
357 /// could be loaded from `.weapon.json`, `.weapon.yml`, or `.weapon.yaml` files.
358 extension: String,
359 },
360 /// An asset with a custom asset loader.
361 Custom {
362 /// The loader implementation for the asset.
363 loader: Box<dyn AssetLoader>,
364 /// The list of file extensions to load this asset from.
365 extensions: Vec<String>,
366 },
367}
368
369/// Helper function to return type data for a metadata asset.
370///
371/// # Example
372///
373/// This is meant to be used in a `type_data` attribute when deriving [`HasSchema`].
374///
375/// ```
376/// # use bones_asset::prelude::*;
377/// # use glam::*;
378/// #[derive(HasSchema, Default, Clone)]
379/// #[type_data(metadata_asset("atlas"))]
380/// #[repr(C)]
381/// struct AtlasMeta {
382/// pub tile_size: Vec2,
383/// pub grid_size: UVec2,
384/// }
385/// ```
386pub fn metadata_asset(extension: &str) -> AssetKind {
387 AssetKind::Metadata {
388 extension: extension.into(),
389 }
390}
391
392/// Helper function to return type data for a custom asset loader.
393///
394/// # Example
395///
396/// This is meant to be used in a `type_data` attribute when deriving [`HasSchema`].
397///
398/// ```
399/// # use bones_asset::prelude::*;
400/// #[derive(HasSchema, Default, Clone)]
401/// #[type_data(asset_loader("png", PngLoader))]
402/// #[repr(C)]
403/// struct Image {
404/// data: SVec<u8>,
405/// width: u32,
406/// height: u32,
407/// }
408///
409/// struct PngLoader;
410/// impl AssetLoader for PngLoader {
411/// fn load(&self, ctx: AssetLoadCtx, data: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>> {
412/// Box::pin(async move {
413/// todo!("Load PNG from data");
414/// })
415/// }
416/// }
417/// ```
418pub fn asset_loader<L: AssetLoader, E: Into<AssetExtensions>>(
419 extensions: E,
420 loader: L,
421) -> AssetKind {
422 AssetKind::Custom {
423 loader: Box::new(loader),
424 extensions: extensions.into().0,
425 }
426}
427
428/// Helper type for storing asset extensions.
429pub struct AssetExtensions(Vec<String>);
430impl<'a, const N: usize> From<[&'a str; N]> for AssetExtensions {
431 fn from(value: [&'a str; N]) -> Self {
432 Self(value.iter().map(|x| x.to_string()).collect())
433 }
434}
435impl<'a> From<&'a str> for AssetExtensions {
436 fn from(value: &'a str) -> Self {
437 Self(vec![value.to_string()])
438 }
439}