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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
use std::{
    path::{Path, PathBuf},
    pin::Pin,
    sync::{
        atomic::{AtomicU32, Ordering::SeqCst},
        Arc,
    },
};

use append_only_vec::AppendOnlyVec;
use bones_utils::prelude::*;
use dashmap::DashMap;
use event_listener::{Event, EventListener};
use futures_lite::future::Boxed as BoxedFuture;
use parking_lot::Mutex;
use semver::VersionReq;

use crate::prelude::*;

/// The unique ID for an asset pack.
///
/// Asset pack IDs are made up of a human-readable label, and a unique identifier. For example:
///
/// > awesome-pack_01h502c309fddv1vq1gwa918e8
///
/// These IDs can be generated with the [TypeID gen][gen] utility.
///
/// [gen]: https://zicklag.github.io/type-id-gen/
pub type AssetPackId = LabeledId;

/// An asset pack contains assets that are loaded by the game.
///
/// The game's built-in assets are contained the the core asset pack, and mods or other assets may
/// also be loaded.
#[derive(Clone, Debug)]
pub struct AssetPack {
    /// The display name of the asset pack.
    pub name: String,
    /// The unique ID of the asset pack.
    pub id: AssetPackId,
    /// The version number of the asset pack.
    pub version: Version,

    /// The game [`VersionReq`] this asset pack is compatible with.
    pub game_version: VersionReq,

    /// Schemas provided in the asset pack.
    pub schemas: Vec<&'static Schema>,
    /// The root asset for the asset pack.
    pub root: UntypedHandle,
}

/// Specifies an asset pack, and it's exact version.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AssetPackSpec {
    /// The ID of the asset pack.
    pub id: AssetPackId,
    /// The version of the asset pack.
    pub version: Version,
}

impl std::fmt::Display for AssetPackSpec {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self { id, version } = self;
        write!(f, "{id}@{version}")
    }
}

/// A requirement specifier for an asset pack, made up of the asset pack's [`LabeledId`] and it's
/// [`VersionReq`].
#[derive(Debug, Clone)]
pub struct AssetPackReq {
    /// The asset pack ID.
    pub id: LabeledId,
    /// The version of the asset pack.
    pub version: VersionReq,
}

/// A schema reference, containing the ID of the pack that defined the schema, and the name of the
/// schema in the pack.
#[derive(Clone, Debug)]
pub struct SchemaPath {
    /// The ID of the pack, or [`None`] if it refers to the core pack.
    pub pack: Option<AssetPackReq>,
    /// The name of the schema.
    pub name: String,
}

/// Struct containing all the game's loaded assets, including the default assets and
/// asset-packs/mods.
pub struct LoadedAssets {
    /// The game's default asset pack.
    pub default: UntypedHandle,
    /// Extra asset packs. The key is the the name of the asset pack.
    pub packs: HashMap<String, UntypedHandle>,
}

/// The progress that has been made loading the game assets.
#[derive(Debug, Clone, Default)]
pub struct AssetLoadProgress {
    assets_to_load: Arc<AtomicU32>,
    assets_downloaded: Arc<AtomicU32>,
    assets_loaded: Arc<AtomicU32>,
    assets_errored: Arc<AtomicU32>,
    /// The event notifier that is used to wake interested tasks that are waiting for asset load
    /// to progress.
    event: Arc<Event>,
}

impl AssetLoadProgress {
    /// Increment the number of assets that need to be loaded by one.
    pub fn inc_to_load(&self) {
        self.assets_to_load.fetch_add(1, SeqCst);
    }

    /// Increment the number of assets that have errored during loading.
    pub fn inc_errored(&self) {
        self.assets_errored.fetch_add(1, SeqCst);
    }

    /// Increment the number of assets that have been downloaded by one.
    pub fn inc_downloaded(&self) {
        self.assets_downloaded.fetch_add(1, SeqCst);
    }

    /// Increment the number of assets that have been loaded by one.
    pub fn inc_loaded(&self) {
        self.assets_loaded.fetch_add(1, SeqCst);
        self.event.notify(usize::MAX);
    }

    /// Get whether or not all the assets are done loading.
    ///
    /// > **Note:** Assets that have errored while loading are still counted as "done loading".
    pub fn is_finished(&self) -> bool {
        let loaded = self.assets_loaded.load(SeqCst);
        let pending = self.assets_to_load.load(SeqCst);
        let errored = self.assets_errored.load(SeqCst);
        loaded != 0 && (loaded + errored) == pending
    }

    /// Get the number of assets that have been downloaded and loaded by their asset loaders.
    pub fn loaded(&self) -> u32 {
        self.assets_loaded.load(SeqCst)
    }

    /// Get the number of assets that have errored while loading.
    pub fn errored(&self) -> u32 {
        self.assets_errored.load(SeqCst)
    }

    /// Get the number of assets that must be loaded.
    ///
    /// Since assets are discovered as they are loaded this number may not be the final
    /// asset count and may increase as more assets are discovered.
    pub fn to_load(&self) -> u32 {
        self.assets_to_load.load(SeqCst)
    }

    /// Get the number of assets that have had their data downloaded. Once an asset is downloaded
    /// we have the raw bytes, but it may not have been processed by it's asset loader.
    pub fn downloaded(&self) -> u32 {
        self.assets_downloaded.load(SeqCst)
    }

    /// Get an event listener that will be notified each time asset load progress
    /// has been updated.
    pub fn listen(&self) -> Pin<Box<EventListener>> {
        self.event.listen()
    }
}

// TODO: Think of alternative to dashmap.
// Dashmap is annoying to use because it wraps all returned assets from our API in dashmap
// its reference container type to manage the locking. We should try to come up with a
// way to manage the concurrent asset loading without requring the locks if possible.

/// Stores assets for later retrieval.
#[derive(Default, Clone, Debug)]
pub struct AssetStore {
    /// Maps the handle of the asset to it's content ID.
    pub asset_ids: DashMap<UntypedHandle, Cid>,
    /// Content addressed cache of raw bytes for asset data.
    ///
    /// Storing asset data in this ways allows you to easily replicate assets to other players over
    /// the network by comparing available [`Cid`]s.
    pub asset_data: DashMap<Cid, Vec<u8>>,
    /// Maps asset content IDs, to assets that have been loaded by an asset loader from the raw
    /// bytes.
    pub assets: DashMap<Cid, LoadedAsset>,
    /// Maps the asset [`AssetLoc`] to it's handle.
    pub path_handles: DashMap<AssetLoc, UntypedHandle>,

    /// List of assets that depend on the given assets.
    pub reverse_dependencies: DashMap<UntypedHandle, HashSet<UntypedHandle>>,
    /// Lists the packs that have not been loaded due to an incompatible game version.
    pub incompabile_packs: DashMap<String, PackfileMeta>,

    /// The core asset pack, if it's been loaded.
    pub core_pack: Arc<Mutex<Option<AssetPack>>>,
    /// The asset packs that have been loaded.
    pub packs: DashMap<AssetPackSpec, AssetPack>,
    /// Maps the directory names of asset packs to their [`AssetPackSpec`].
    pub pack_dirs: DashMap<String, AssetPackSpec>,
}

/// Contains that path to an asset, and the pack_dir that it was loaded from.
///
/// A pack of [`None`] means that it was loaded from the core pack.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct AssetLoc {
    /// The path to the asset in it's pack.
    pub path: PathBuf,
    /// The pack_dir of the pack that the asset is in.
    pub pack: Option<String>,
}

impl AssetLoc {
    /// Borrow as an [`AssetLocRef`].
    pub fn as_ref(&self) -> AssetLocRef {
        AssetLocRef {
            pack: self.pack.as_deref(),
            path: &self.path,
        }
    }
}

impl From<&AssetLocRef<'_>> for AssetLoc {
    fn from(value: &AssetLocRef<'_>) -> Self {
        AssetLoc {
            path: value.path.to_owned(),
            pack: value.pack.map(|x| x.to_owned()),
        }
    }
}

/// A borrowed version of [`AssetLoc`].
#[derive(Clone, PartialEq, Eq, Hash, Debug, Copy)]
pub struct AssetLocRef<'a> {
    /// The path to the asset in it's pack.
    pub path: &'a Path,
    /// The pack_dir of the pack that the asset is in.
    pub pack: Option<&'a str>,
}

impl<'a> From<(&'a Path, Option<&'a str>)> for AssetLocRef<'a> {
    fn from((path, pack): (&'a Path, Option<&'a str>)) -> Self {
        Self { pack, path }
    }
}

impl AssetLocRef<'_> {
    /// Clone data to an owned [`AssetLoc`].
    pub fn to_owned(&self) -> AssetLoc {
        self.into()
    }
}

/// An asset that has been loaded.
#[derive(Clone, Debug, Deref, DerefMut)]
pub struct LoadedAsset {
    /// The content ID of the loaded asset.
    ///
    /// This is a hash of the contents of the asset's binary data and all of the cids of it's
    /// dependencies.
    pub cid: Cid,
    /// The asset pack this was loaded from, or [`None`] if it is from the default pack.
    pub pack_spec: Option<AssetPackSpec>,
    /// The pack and path the asset was loaded from.
    pub loc: AssetLoc,
    /// The content IDs of any assets needed by this asset as a dependency.
    pub dependencies: Vec<UntypedHandle>,
    /// The loaded data of the asset.
    #[deref]
    pub data: SchemaBox,
}

/// An identifier for an asset.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct AssetInfo {
    /// The unique ID of the asset pack this asset is located in.
    pub pack: Cid,
    /// The path to the asset, relative to the root of the asset pack.
    pub path: PathBuf,
}

/// Context provided to custom asset loaders in the [`AssetLoader::load`] method.
pub struct AssetLoadCtx {
    /// The asset server.
    pub asset_server: AssetServer,
    /// The location of the asset.
    pub loc: AssetLoc,
    /// The [`Cid`]s of the assets this asset depends on.
    ///
    /// This is automatically updated when calling [`AssetLoadCtx::load_asset`].
    pub dependencies: Arc<AppendOnlyVec<UntypedHandle>>,
}

impl AssetLoadCtx {
    /// Load another asset as a child of this asset.
    pub fn load_asset(&mut self, path: &Path) -> anyhow::Result<UntypedHandle> {
        let handle = self.asset_server.load_asset(AssetLocRef {
            path,
            pack: self.loc.as_ref().pack,
        });
        self.dependencies.push(handle);
        Ok(handle)
    }
}

/// A custom assset loader.
pub trait AssetLoader: Sync + Send + 'static {
    /// Load the asset from raw bytes.
    fn load(&self, ctx: AssetLoadCtx, bytes: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>>;
}

/// A custom asset loader implementation for a metadata asset.
///
/// This is similar in purpose to implementing [`AssetLoader`], but instead of loading from bytes,
/// it loads from the deserialized [`SchemaRefMut`] of a metadata asset and must be added as a
/// schema type data.
#[derive(HasSchema)]
#[schema(no_clone, no_default)]
pub struct SchemaMetaAssetLoader(
    pub  fn(
        ctx: &mut MetaAssetLoadCtx,
        ptr: SchemaRefMut<'_>,
        deserialzer: &mut dyn erased_serde::Deserializer,
    ) -> anyhow::Result<()>,
);

impl SchemaMetaAssetLoader {
    /// Load the asset
    pub fn load<'a, 'de, D>(
        &self,
        ctx: &mut MetaAssetLoadCtx,
        ptr: SchemaRefMut<'a>,
        deserializer: D,
    ) -> Result<(), erased_serde::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::Error;
        let mut de = <dyn erased_serde::Deserializer>::erase(deserializer);
        (self.0)(ctx, ptr, &mut de).map_err(|e| erased_serde::Error::custom(e.to_string()))
    }
}

/// A [type data][bones_schema::alloc::TypeDatas] that indicates how to load a type as an asset.
#[derive(HasSchema)]
#[schema(opaque, no_default, no_clone)]
pub enum AssetKind {
    /// This is a metadata asset that can be loaded from JSON or YAML files.
    Metadata {
        /// The `extension` is the portion of the extension that comes before the `.json`, `.yml`,
        /// or `.yaml` extension. For example, if the `extension` was set to `weapon`, then the asset
        /// could be loaded from `.weapon.json`, `.weapon.yml`, or `.weapon.yaml` files.
        extension: String,
    },
    /// An asset with a custom asset loader.
    Custom {
        /// The loader implementation for the asset.
        loader: Box<dyn AssetLoader>,
        /// The list of file extensions to load this asset from.
        extensions: Vec<String>,
    },
}

/// Helper function to return type data for a metadata asset.
///
/// # Example
///
/// This is meant to be used in a `type_data` attribute when deriving [`HasSchema`].
///
/// ```
/// # use bones_asset::prelude::*;
/// # use glam::*;
/// #[derive(HasSchema, Default, Clone)]
/// #[type_data(metadata_asset("atlas"))]
/// #[repr(C)]
/// struct AtlasMeta {
///     pub tile_size: Vec2,
///     pub grid_size: UVec2,
/// }
/// ```
pub fn metadata_asset(extension: &str) -> AssetKind {
    AssetKind::Metadata {
        extension: extension.into(),
    }
}

/// Helper function to return type data for a custom asset loader.
///
/// # Example
///
/// This is meant to be used in a `type_data` attribute when deriving [`HasSchema`].
///
/// ```
/// # use bones_asset::prelude::*;
/// #[derive(HasSchema, Default, Clone)]
/// #[type_data(asset_loader("png", PngLoader))]
/// #[repr(C)]
/// struct Image {
///     data: SVec<u8>,
///     width: u32,
///     height: u32,
/// }
///
/// struct PngLoader;
/// impl AssetLoader for PngLoader {
///     fn load(&self, ctx: AssetLoadCtx, data: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>> {
///         Box::pin(async move {
///             todo!("Load PNG from data");
///         })
///     }
/// }
/// ```
pub fn asset_loader<L: AssetLoader, E: Into<AssetExtensions>>(
    extensions: E,
    loader: L,
) -> AssetKind {
    AssetKind::Custom {
        loader: Box::new(loader),
        extensions: extensions.into().0,
    }
}

/// Helper type for storing asset extensions.
pub struct AssetExtensions(Vec<String>);
impl<'a, const N: usize> From<[&'a str; N]> for AssetExtensions {
    fn from(value: [&'a str; N]) -> Self {
        Self(value.iter().map(|x| x.to_string()).collect())
    }
}
impl<'a> From<&'a str> for AssetExtensions {
    fn from(value: &'a str) -> Self {
        Self(vec![value.to_string()])
    }
}