1use std::{
2 path::{Path, PathBuf},
3 sync::{Arc, Mutex},
4};
5
6use anyhow::Context;
7use append_only_vec::AppendOnlyVec;
8use async_channel::{Receiver, Sender};
9use bevy_tasks::IoTaskPool;
10use bones_utils::{default, Deref, DerefMut, UlidExt};
11use dashmap::{
12 mapref::one::{
13 MappedRef as MappedMapRef, MappedRefMut as MappedMapRefMut, Ref as MapRef,
14 RefMut as MapRefMut,
15 },
16 DashMap,
17};
18use once_cell::sync::Lazy;
19use parking_lot::{MappedMutexGuard, MutexGuard};
20use semver::VersionReq;
21use serde::{de::DeserializeSeed, Deserialize};
22#[allow(unused_imports)]
23use tracing::info;
24use ulid::Ulid;
25
26use crate::prelude::*;
27
28mod schema_loader;
29
30#[derive(HasSchema, Deref, DerefMut, Clone)]
33pub struct AssetServer {
34 #[deref]
35 pub inner: Arc<AssetServerInner>,
37 pub io: Arc<dyn AssetIo>,
39}
40
41pub struct AssetServerInner {
43 pub game_version: Mutex<Version>,
46 pub store: AssetStore,
48 pub asset_change_send: Sender<ChangedAsset>,
51 pub asset_change_recv: Receiver<ChangedAsset>,
53 pub load_progress: AssetLoadProgress,
55}
56
57pub enum ChangedAsset {
59 Loc(AssetLoc),
61 Handle(UntypedHandle),
63}
64
65impl Default for AssetServer {
66 fn default() -> Self {
67 Self {
68 inner: default(),
69 io: Arc::new(DummyIo::new([])),
70 }
71 }
72}
73
74impl Default for AssetServerInner {
75 fn default() -> Self {
76 let (asset_change_send, asset_change_recv) = async_channel::unbounded();
77 Self {
78 game_version: Mutex::new(Version::new(0, 0, 0)),
79 store: default(),
80 load_progress: default(),
81 asset_change_send,
82 asset_change_recv,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Deserialize)]
89pub struct CorePackfileMeta {
90 pub root: PathBuf,
92 #[serde(default)]
94 pub schemas: Vec<PathBuf>,
95}
96
97#[derive(Debug, Clone, Deserialize)]
99pub struct PackfileMeta {
100 pub name: String,
102 pub id: AssetPackId,
104 pub version: Version,
106 pub game_version: VersionReq,
108 #[serde(default)]
110 pub schemas: Vec<PathBuf>,
111 pub root: PathBuf,
113}
114
115pub static CORE_PACK_ID: Lazy<AssetPackId> =
117 Lazy::new(|| AssetPackId::new_with_ulid("core", Ulid(0)).unwrap());
118
119#[derive(Debug, Clone)]
121pub struct IncompatibleGameVersionError {
122 pub game_version: Version,
124 pub pack_dir: String,
126 pub pack_meta: PackfileMeta,
128}
129
130impl std::error::Error for IncompatibleGameVersionError {}
131impl std::fmt::Display for IncompatibleGameVersionError {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(
134 f,
135 "Asset pack `{}` v{} from folder `{}` is only compatible with game versions matching {}, not {}",
136 self.pack_meta.id,
137 self.pack_meta.version,
138 self.pack_dir,
139 self.pack_meta.game_version,
140 self.game_version
141 )
142 }
143}
144
145#[derive(Debug)]
146struct LoaderNotFound {
147 name: String,
148}
149impl std::fmt::Display for LoaderNotFound {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 write!(
152 f,
153 "Schema/loader not found for schema/extension: {}\n\
154 You may need to register the asset by calling `MyAsset::schema()`",
155 self.name
156 )
157 }
158}
159impl std::error::Error for LoaderNotFound {}
160
161impl AssetServer {
162 pub fn new<Io: AssetIo + 'static>(io: Io, version: Version) -> Self {
164 Self {
165 inner: Arc::new(AssetServerInner {
166 game_version: Mutex::new(version),
167 ..default()
168 }),
169 io: Arc::new(io),
170 }
171 }
172
173 pub async fn load_asset_bytes(&self, loc: AssetLoc, force: bool) -> anyhow::Result<Cid> {
180 if !force {
182 let cid = self
183 .store
184 .path_handles
185 .get(&loc)
186 .and_then(|handle| self.store.asset_ids.get(&handle));
187 if let Some(cid) = cid {
188 return Ok(*cid);
189 }
190 }
191
192 let data = self.io.load_file(loc.as_ref()).await?;
194
195 let mut cid = Cid::default();
197 cid.update(&data);
198
199 self.store.asset_data.insert(cid, data);
201
202 Ok(cid)
204 }
205
206 pub fn watch_for_changes(&self) {
208 self.io.watch(self.asset_change_send.clone());
209 }
210
211 pub fn set_io<Io: AssetIo + 'static>(&mut self, io: Io) {
215 self.io = Arc::new(io);
216 }
217
218 pub fn handle_asset_changes<F: FnMut(&mut AssetServer, UntypedHandle)>(
223 &mut self,
224 mut handle_change: F,
225 ) {
226 while let Ok(changed) = self.asset_change_recv.try_recv() {
227 match changed {
228 ChangedAsset::Loc(loc) => {
229 let handle = self.load_asset_forced(loc.as_ref());
230 handle_change(self, handle)
231 }
232 ChangedAsset::Handle(handle) => {
233 let entry = self
234 .store
235 .path_handles
236 .iter()
237 .find(|entry| *entry.value() == handle)
238 .unwrap();
239 let loc = entry.key().to_owned();
240 drop(entry);
241 self.load_asset_forced(loc.as_ref());
242 handle_change(self, handle)
243 }
244 }
245 }
246 }
247
248 pub async fn load_assets(&self) -> anyhow::Result<()> {
250 self.load_pack(None).await?;
252
253 for pack_dir in self.io.enumerate_packs().await? {
255 let pack_result = self.load_pack(Some(&pack_dir)).await;
257 if let Err(e) = pack_result {
258 match e.downcast::<IncompatibleGameVersionError>() {
259 Ok(e) => {
261 tracing::warn!(
262 "Not loading pack `{}` because it requires game version \
263 `{}` and this is version `{}`",
264 e.pack_meta.name,
265 e.pack_meta.game_version,
266 e.game_version,
267 );
268 self.store.incompabile_packs.insert(e.pack_dir, e.pack_meta);
270 }
271 Err(e) => {
273 return Err(e).context(format!("Error loading asset pack: {pack_dir}"))
274 }
275 }
276 }
277 }
278
279 Ok(())
280 }
281
282 pub async fn load_pack(&self, pack: Option<&str>) -> anyhow::Result<AssetPackSpec> {
284 if pack.is_none() {
286 return self
287 .load_core_pack()
288 .await
289 .context("Error loading core asset pack");
290 }
291
292 let packfile_contents = self
294 .io
295 .load_file((Path::new("pack.yaml"), pack).into())
296 .await?;
297 let meta: PackfileMeta = serde_yaml::from_slice(&packfile_contents)?;
298 tracing::debug!(?pack, ?meta, "Loaded asset pack meta.");
299
300 if !meta.game_version.matches(&self.game_version()) {
302 return Err(IncompatibleGameVersionError {
303 game_version: self.game_version(),
304 pack_dir: pack.unwrap().to_owned(),
305 pack_meta: meta,
306 }
307 .into());
308 }
309
310 if let Some(pack_dir) = pack {
312 self.store.pack_dirs.insert(
313 pack_dir.into(),
314 AssetPackSpec {
315 id: meta.id,
316 version: meta.version.clone(),
317 },
318 );
319 }
320
321 let schemas = self.load_pack_schemas(pack, &meta.schemas).await?;
323
324 let root_loc = AssetLocRef {
326 path: &meta.root,
327 pack,
328 };
329
330 let spec = AssetPackSpec {
332 id: meta.id,
333 version: meta.version.clone(),
334 };
335 self.store.packs.insert(
336 spec.clone(),
337 AssetPack {
338 name: meta.name,
339 id: meta.id,
340 version: meta.version,
341 game_version: meta.game_version,
342 schemas,
343 root: default(),
344 },
345 );
346 let root_handle = self.load_asset(root_loc);
347 self.store.packs.get_mut(&spec).unwrap().root = root_handle;
348
349 Ok(spec)
350 }
351
352 pub async fn load_core_pack(&self) -> anyhow::Result<AssetPackSpec> {
354 let packfile_contents = self
356 .io
357 .load_file(AssetLocRef {
358 path: Path::new("pack.yaml"),
359 pack: None,
360 })
361 .await
362 .context("Could not load pack file")?;
363 let meta: CorePackfileMeta = serde_yaml::from_slice(&packfile_contents)?;
364 tracing::debug!(?meta, "Loaded core pack meta.");
365
366 let root_loc = AssetLocRef {
368 path: &meta.root,
369 pack: None,
370 };
371 let handle = self.load_asset(root_loc);
372
373 let schemas = self.load_pack_schemas(None, &meta.schemas).await?;
375
376 let game_version = self.game_version();
378
379 let id = *CORE_PACK_ID;
380 *self.store.core_pack.lock() = Some(AssetPack {
381 name: "Core".into(),
382 id,
383 version: game_version.clone(),
384 game_version: VersionReq {
385 comparators: [semver::Comparator {
386 op: semver::Op::Exact,
387 major: game_version.major,
388 minor: Some(game_version.minor),
389 patch: Some(game_version.patch),
390 pre: game_version.pre.clone(),
391 }]
392 .to_vec(),
393 },
394 schemas,
395 root: handle,
396 });
397
398 Ok(AssetPackSpec {
399 id,
400 version: game_version.clone(),
401 })
402 }
403
404 pub fn load_asset(&self, loc: AssetLocRef<'_>) -> UntypedHandle {
406 self.impl_load_asset(loc, false)
407 }
408
409 pub fn load_asset_forced(&self, loc: AssetLocRef<'_>) -> UntypedHandle {
412 self.impl_load_asset(loc, true)
413 }
414
415 fn impl_load_asset(&self, loc: AssetLocRef<'_>, force: bool) -> UntypedHandle {
416 let pool = IoTaskPool::get();
418
419 let loc = AssetLoc {
421 path: loc.path.absolutize_from("/").unwrap().into_owned(),
422 pack: loc.pack.map(|x| x.to_owned()),
423 };
424
425 if !force {
427 if let Some(handle) = self.store.path_handles.get(&loc) {
429 return *handle;
431 }
432 }
433
434 let mut should_load = true;
436 let handle = *self
437 .store
438 .path_handles
439 .entry(loc.clone())
440 .and_modify(|handle| {
442 if self.store.asset_ids.get(handle).is_some() && !force {
445 should_load = false;
446 }
447 })
448 .or_insert(UntypedHandle {
450 rid: Ulid::create(),
451 });
452
453 if should_load {
454 self.load_progress.inc_to_load();
456
457 let server = self.clone();
459 let loc_ = loc.clone();
460 pool.spawn(async move {
461 tracing::debug!(?loc, ?force, "Loading asset");
462 let loc = loc_;
463 let result = async {
464 let cid = server.load_asset_bytes(loc.clone(), force).await?;
465 server.load_progress.inc_downloaded();
466 let data = server
467 .store
468 .asset_data
469 .get(&cid)
470 .expect("asset not loaded")
471 .clone();
472
473 let partial = if path_is_metadata(&loc.path) {
477 match server.load_metadata_asset(loc.as_ref(), &data).await {
478 Err(meta_err) => {
479 if meta_err.downcast_ref::<LoaderNotFound>().is_some() {
480 match server.load_data_asset(loc.as_ref(), &data).await {
481 Err(data_err) => {
482 if data_err.downcast_ref::<LoaderNotFound>().is_some() {
483 Err(meta_err)
484 } else {
485 Err(data_err)
486 }
487 }
488 ok => ok,
489 }
490 } else {
491 Err(meta_err)
492 }
493 }
494 ok => ok,
495 }
496 } else {
497 server.load_data_asset(loc.as_ref(), &data).await
498 }?;
499
500 let loaded_asset = LoadedAsset {
501 cid: partial.cid,
502 pack_spec: loc.pack.as_ref().map(|x| {
503 server
504 .store
505 .pack_dirs
506 .get(x.as_str())
507 .expect("Pack dir not loaded properly")
508 .clone()
509 }),
510 loc: loc.to_owned(),
511 dependencies: partial.dependencies,
512 data: partial.data,
513 };
514 server.store.assets.insert(partial.cid, loaded_asset);
520
521 for dep in server
523 .store
524 .assets
525 .get(&partial.cid)
526 .unwrap()
527 .dependencies
528 .iter()
529 {
530 server
531 .store
532 .reverse_dependencies
533 .entry(*dep)
534 .or_default()
535 .insert(handle);
536 }
537
538 let previous_cid = server.store.asset_ids.insert(handle, partial.cid);
540
541 if let Some(cid) = previous_cid {
544 if server.store.asset_ids.iter().all(|map| *map.value() != cid) {
546 tracing::debug!(?cid, "Removing asset content");
548 let (_, previous_asset) = server.store.assets.remove(&cid).unwrap();
549
550 for dep in previous_asset.dependencies.iter() {
559 server
560 .store
561 .reverse_dependencies
562 .get_mut(dep)
563 .unwrap()
564 .remove(&handle);
565 }
566 }
567
568 if let Some(rev_deps) = server.store.reverse_dependencies.get(&handle) {
570 for dep in rev_deps.iter() {
571 server
572 .asset_change_send
573 .try_send(ChangedAsset::Handle(*dep))
574 .unwrap();
575 }
576 }
577 }
578
579 server.load_progress.inc_loaded();
580
581 Ok::<_, anyhow::Error>(())
582 }
583 .await;
584
585 if let Err(e) = result {
586 server.load_progress.inc_errored();
587 tracing::error!("Error loading asset: {e}");
588 }
589 })
590 .detach();
591 }
592
593 handle
594 }
595
596 async fn load_metadata_asset<'a>(
597 &'a self,
598 loc: AssetLocRef<'a>,
599 contents: &[u8],
600 ) -> anyhow::Result<PartialAsset> {
601 let filename = loc
603 .path
604 .file_name()
605 .ok_or_else(|| anyhow::format_err!("Invalid asset filename"))?
606 .to_str()
607 .ok_or_else(|| anyhow::format_err!("Invalid unicode in filename"))?;
608 let before_extension = filename.rsplit_once('.').unwrap().0;
609 let schema_name = before_extension
610 .rsplit_once('.')
611 .map(|x| x.1)
612 .unwrap_or(before_extension);
613 let schema = SCHEMA_REGISTRY
614 .schemas
615 .iter()
616 .find(|schema| {
617 schema
618 .type_data
619 .get::<AssetKind>()
620 .and_then(|asset_kind| match asset_kind {
621 AssetKind::Metadata { extension } => Some(extension == schema_name),
622 _ => None,
623 })
624 .unwrap_or(false)
625 })
626 .ok_or_else(|| LoaderNotFound {
627 name: schema_name.into(),
628 })?;
629 let mut dependencies = Vec::new();
630
631 let mut cid = Cid::default();
632
633 #[cfg(feature = "cid_debug_trace")]
637 let mut cid_debug = CidDebugTrace::new(schema.full_name, loc.path);
638
639 cid.update(schema.full_name.as_bytes());
642
643 #[cfg(feature = "cid_debug_trace")]
644 {
645 cid_debug.cid_after_schema_fullname = cid;
646 }
647
648 cid.update(contents);
649
650 #[cfg(feature = "cid_debug_trace")]
651 {
652 cid_debug.cid_after_contents = cid;
653 }
654
655 let loader = MetaAssetLoadCtx {
656 server: self,
657 loc,
658 schema,
659 dependencies: &mut dependencies,
660 };
661 let data = if loc.path.extension().unwrap().to_str().unwrap() == "json" {
662 let mut deserializer = serde_json::Deserializer::from_slice(contents);
663 loader.deserialize(&mut deserializer)?
664 } else {
665 let deserializer = serde_yaml::Deserializer::from_slice(contents);
666 loader.deserialize(deserializer)?
667 };
668
669 let mut dep_cids: Vec<Cid> = vec![];
678 for dep in &dependencies {
679 let dep_cid = loop {
680 let listener = self.load_progress.listen();
681 let Some(cid) = self.store.asset_ids.get(dep) else {
682 listener.await;
683 continue;
684 };
685 break *cid;
686 };
687 dep_cids.push(dep_cid);
689 }
690
691 dep_cids.sort();
692 for dep_cid in dep_cids {
693 cid.update(dep_cid.0.as_slice());
694
695 #[cfg(feature = "cid_debug_trace")]
696 {
697 let asset_loc = self.store.assets.get(&cid).map(|x| x.loc.clone());
701 cid_debug.cid_after_deps.push((dep_cid, cid, asset_loc));
702 }
703 }
704
705 #[cfg(feature = "cid_debug_trace")]
706 {
707 cid_debug.final_cid = cid;
709 info!("{cid_debug}");
710 }
711
712 Ok(PartialAsset {
713 cid,
714 dependencies,
715 data,
716 })
717 }
718
719 async fn load_data_asset<'a>(
720 &self,
721 loc: AssetLocRef<'a>,
722 contents: &'a [u8],
723 ) -> anyhow::Result<PartialAsset> {
724 let filename = loc
726 .path
727 .file_name()
728 .ok_or_else(|| anyhow::format_err!("Invalid asset filename"))?
729 .to_str()
730 .ok_or_else(|| anyhow::format_err!("Invalid unicode in filename"))?;
731 let (_name, extension) = filename.split_once('.').unwrap();
732 let (loader, schema) = SCHEMA_REGISTRY
733 .schemas
734 .iter()
735 .find_map(|schema| {
736 if let Some(asset_kind) = schema.type_data.get::<AssetKind>() {
737 match asset_kind {
738 AssetKind::Custom { extensions, loader } => {
739 if extensions
740 .iter()
741 .any(|ext| ext == extension || ext == filename)
742 {
743 Some((loader, schema))
744 } else {
745 None
746 }
747 }
748 _ => None,
749 }
750 } else {
751 None
752 }
753 })
754 .ok_or_else(|| LoaderNotFound {
755 name: extension.into(),
756 })?;
757
758 let dependencies = Arc::new(AppendOnlyVec::new());
759
760 let mut cid = Cid::default();
761 cid.update(schema.full_name.as_bytes());
764 cid.update(contents);
765
766 let ctx = AssetLoadCtx {
767 asset_server: self.clone(),
768 loc: loc.to_owned(),
769 dependencies: dependencies.clone(),
770 };
771 let sbox = loader.load(ctx, contents).await?;
772
773 let dependencies = dependencies.iter().cloned().collect::<Vec<_>>();
775
776 let mut dep_cids: Vec<Cid> = vec![];
783
784 for dep in &dependencies {
785 let dep_cid = loop {
786 let listener = self.load_progress.listen();
787 let Some(cid) = self.store.asset_ids.get(dep) else {
788 listener.await;
789 continue;
790 };
791 break *cid;
792 };
793 dep_cids.push(dep_cid);
794 }
796 dep_cids.sort();
797 for dep_cid in dep_cids {
798 cid.update(dep_cid.0.as_slice());
799 }
800
801 Ok(PartialAsset {
802 cid,
803 data: sbox,
804 dependencies,
805 })
806 }
807
808 pub fn get_asset_untyped(&self, handle: UntypedHandle) -> Option<MapRef<'_, Cid, LoadedAsset>> {
810 let cid = self.store.asset_ids.get(&handle)?;
811 self.store.assets.get(&cid)
812 }
813
814 pub fn get_asset_untyped_mut(
816 &self,
817 handle: UntypedHandle,
818 ) -> Option<MapRefMut<'_, Cid, LoadedAsset>> {
819 let cid = self.store.asset_ids.get(&handle)?;
820 self.store.assets.get_mut(&cid)
821 }
822
823 #[track_caller]
829 pub fn core(&self) -> MappedMutexGuard<'_, AssetPack> {
830 MutexGuard::map(self.store.core_pack.lock(), |x| x.as_mut().unwrap())
831 }
832
833 pub fn root<T: HasSchema>(&self) -> MappedMapRef<'_, Cid, LoadedAsset, T> {
835 self.get(self.core().root.typed())
836 }
837
838 pub fn untyped_root(&self) -> MappedMapRef<'_, Cid, LoadedAsset, SchemaBox> {
840 self.get_untyped(self.core().root)
841 }
842
843 pub fn packs(&self) -> &DashMap<AssetPackSpec, AssetPack> {
845 &self.store.packs
846 }
847
848 #[track_caller]
855 pub fn get<T: HasSchema>(&self, handle: Handle<T>) -> MappedMapRef<'_, Cid, LoadedAsset, T> {
856 self.try_get(handle)
857 .expect("asset not found (handle has no cid)")
858 .expect("asset does not have matching schema for given type")
859 }
860
861 #[track_caller]
867 pub fn get_untyped(
868 &self,
869 handle: UntypedHandle,
870 ) -> MappedMapRef<'_, Cid, LoadedAsset, SchemaBox> {
871 self.try_get_untyped(handle).unwrap()
872 }
873
874 #[track_caller]
880 pub fn get_untyped_mut(
881 &self,
882 handle: UntypedHandle,
883 ) -> MappedMapRefMut<'_, Cid, LoadedAsset, SchemaBox> {
884 self.try_get_untyped_mut(handle).unwrap()
885 }
886
887 pub fn try_get<T: HasSchema>(
891 &self,
892 handle: Handle<T>,
893 ) -> Option<
894 Result<
895 MappedMapRef<'_, Cid, LoadedAsset, T, std::collections::hash_map::RandomState>,
896 SchemaMismatchError,
897 >,
898 > {
899 let cid = self.store.asset_ids.get(&handle.untyped())?;
900 Some(
901 MapRef::try_map(self.store.assets.get(&cid).unwrap(), |x| {
902 let asset = &x.data;
903
904 if T::schema() == <SchemaBox as HasSchema>::schema() {
906 Some(unsafe { std::mem::transmute::<&SchemaBox, &T>(asset) })
909 } else {
910 asset.try_cast_ref().ok()
911 }
912 })
913 .map_err(|_| SchemaMismatchError),
914 )
915 }
916
917 pub fn try_get_untyped(
919 &self,
920 handle: UntypedHandle,
921 ) -> Option<MappedMapRef<'_, Cid, LoadedAsset, SchemaBox>> {
922 let cid = self.store.asset_ids.get(&handle)?;
923 Some(MapRef::map(self.store.assets.get(&cid).unwrap(), |x| {
924 &x.data
925 }))
926 }
927
928 pub fn try_get_untyped_mut(
930 &self,
931 handle: UntypedHandle,
932 ) -> Option<MappedMapRefMut<'_, Cid, LoadedAsset, SchemaBox>> {
933 let cid = self.store.asset_ids.get_mut(&handle)?;
934 Some(MapRefMut::map(
935 self.store.assets.get_mut(&cid).unwrap(),
936 |x| &mut x.data,
937 ))
938 }
939
940 pub fn get_handle_from_cid<T>(&self, cid: &Cid) -> Handle<T> {
942 self.get_untyped_handle_from_cid(cid).typed()
943 }
944
945 pub fn get_untyped_handle_from_cid(&self, cid: &Cid) -> UntypedHandle {
947 self.try_get_untyped_handle_from_cid(cid).unwrap()
948 }
949
950 pub fn try_get_handle_from_cid<T>(&self, cid: &Cid) -> Option<Handle<T>> {
952 Some(self.try_get_untyped_handle_from_cid(cid)?.typed())
953 }
954
955 pub fn try_get_untyped_handle_from_cid(&self, cid: &Cid) -> Option<UntypedHandle> {
957 let loaded_asset = self.store.assets.get(cid)?;
958 Some(*self.store.path_handles.get(&loaded_asset.loc)?)
959 }
960
961 #[track_caller]
968 pub fn get_mut<T: HasSchema>(
969 &mut self,
970 handle: &Handle<T>,
971 ) -> MappedMapRefMut<'_, Cid, LoadedAsset, T> {
972 let cid = self
973 .store
974 .asset_ids
975 .get(&handle.untyped())
976 .expect(NO_ASSET_MSG);
977 MapRefMut::map(self.store.assets.get_mut(&cid).unwrap(), |x| {
978 let asset = &mut x.data;
979
980 if T::schema() == <SchemaBox as HasSchema>::schema() {
982 unsafe { std::mem::transmute::<&mut SchemaBox, &mut T>(asset) }
985 } else {
986 asset.cast_mut()
987 }
988 })
989 }
990
991 async fn load_pack_schemas(
993 &self,
994 pack: Option<&str>,
995 schema_paths: &[PathBuf],
996 ) -> anyhow::Result<Vec<&'static Schema>> {
997 let mut schemas = Vec::with_capacity(schema_paths.len());
998 for schema_path in schema_paths {
999 let contents = self
1000 .io
1001 .load_file(AssetLocRef {
1002 path: schema_path,
1003 pack,
1004 })
1005 .await?;
1006
1007 let pack_schema: schema_loader::PackSchema = serde_yaml::from_slice(&contents)?;
1008 let schema = pack_schema.0;
1009 tracing::debug!(?pack, ?schema.name, "Loaded schema from pack.");
1010 schemas.push(schema);
1011 }
1012 Ok(schemas)
1013 }
1014
1015 pub fn game_version(&self) -> Version {
1018 self.game_version.lock().unwrap().clone()
1019 }
1020
1021 pub fn set_game_version(&self, version: Version) {
1024 *self.game_version.lock().unwrap() = version;
1025 }
1026}
1027
1028struct PartialAsset {
1030 pub cid: Cid,
1031 pub data: SchemaBox,
1032 pub dependencies: Vec<UntypedHandle>,
1033}
1034
1035const NO_ASSET_MSG: &str = "Asset not loaded";
1036fn path_is_metadata(path: &Path) -> bool {
1037 let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else {
1038 return false;
1039 };
1040 ext == "yaml" || ext == "yml" || ext == "json"
1041}
1042
1043pub use metadata::*;
1044mod metadata {
1045 use bones_utils::LabeledId;
1046 use serde::de::{DeserializeSeed, Error, Unexpected, VariantAccess, Visitor};
1047
1048 use super::*;
1049
1050 pub struct MetaAssetLoadCtx<'srv> {
1052 pub server: &'srv AssetServer,
1054 pub dependencies: &'srv mut Vec<UntypedHandle>,
1057 pub loc: AssetLocRef<'srv>,
1059 pub schema: &'static Schema,
1061 }
1062
1063 impl<'asset, 'de> DeserializeSeed<'de> for MetaAssetLoadCtx<'asset> {
1064 type Value = SchemaBox;
1065
1066 fn deserialize<D>(mut self, deserializer: D) -> Result<Self::Value, D::Error>
1067 where
1068 D: serde::Deserializer<'de>,
1069 {
1070 let mut ptr = SchemaBox::default(self.schema);
1072
1073 SchemaPtrLoadCtx {
1074 ctx: &mut self,
1075 ptr: ptr.as_mut(),
1076 }
1077 .deserialize(deserializer)?;
1078
1079 Ok(ptr)
1080 }
1081 }
1082
1083 pub struct SchemaPtrLoadCtx<'a, 'srv, 'ptr> {
1085 pub ctx: &'a mut MetaAssetLoadCtx<'srv>,
1087 pub ptr: SchemaRefMut<'ptr>,
1089 }
1090
1091 impl<'a, 'srv, 'ptr, 'de> DeserializeSeed<'de> for SchemaPtrLoadCtx<'a, 'srv, 'ptr> {
1092 type Value = ();
1093
1094 fn deserialize<D>(mut self, deserializer: D) -> Result<Self::Value, D::Error>
1095 where
1096 D: serde::Deserializer<'de>,
1097 {
1098 if self
1100 .ptr
1101 .schema()
1102 .type_data
1103 .get::<SchemaAssetHandle>()
1104 .is_some()
1105 {
1106 let path_string = String::deserialize(deserializer)?;
1107 let mut pack = self.ctx.loc.pack.map(|x| x.to_owned());
1108 let relative_path;
1109 if let Some((pack_prefix, path)) = path_string.split_once(':') {
1110 let pack_id = LabeledId::new(pack_prefix).map_err(|e| D::Error::custom(format!("Error parsing pack prefix while parsing asset path `{path_string}`: {e}")))?;
1111 if pack_prefix == "core" {
1112 pack = None;
1113 } else {
1114 pack = Some(self.ctx
1115 .server
1116 .store
1117 .pack_dirs
1118 .iter()
1119 .find(|x| x.value().id == pack_id)
1120 .map(|x| x.key().to_owned())
1121 .ok_or_else(|| {
1122 D::Error::custom(format!("Dependent pack {pack_id} not loaded when trying to load asset with path: {path_string}."))
1123 })?);
1124 }
1125 relative_path = path;
1126 } else {
1127 relative_path = &path_string;
1128 };
1129 let relative_path = PathBuf::from(relative_path);
1130 let path = relative_path
1131 .absolutize_from(self.ctx.loc.path.parent().unwrap())
1132 .unwrap();
1133 let handle = self.ctx.server.load_asset((&*path, pack.as_deref()).into());
1134 self.ctx.dependencies.push(handle);
1135 *self
1136 .ptr
1137 .try_cast_mut()
1138 .map_err(|e| D::Error::custom(e.to_string()))? = handle;
1139 return Ok(());
1140 }
1141
1142 if let Some(custom_loader) = self.ptr.schema().type_data.get::<SchemaMetaAssetLoader>()
1144 {
1145 return custom_loader
1146 .load(self.ctx, self.ptr, deserializer)
1147 .map_err(|e| D::Error::custom(e.to_string()));
1148 } else if let Some(schema_deserialize) =
1149 self.ptr.schema().type_data.get::<SchemaDeserialize>()
1150 {
1151 return schema_deserialize.deserialize(self.ptr, deserializer);
1152 }
1153
1154 match &self.ptr.schema().kind {
1155 SchemaKind::Struct(s) => {
1156 if s.fields.len() == 1 && s.fields[0].name.is_none() {
1158 let ptr = unsafe {
1161 SchemaRefMut::from_ptr_schema(self.ptr.as_ptr(), s.fields[0].schema)
1162 };
1163 SchemaPtrLoadCtx { ptr, ctx: self.ctx }.deserialize(deserializer)?
1164 } else {
1165 deserializer.deserialize_any(StructVisitor {
1166 ptr: self.ptr,
1167 ctx: self.ctx,
1168 })?
1169 }
1170 }
1171 SchemaKind::Vec(_) => deserializer.deserialize_seq(VecVisitor {
1172 ptr: self.ptr,
1173 ctx: self.ctx,
1174 })?,
1175 SchemaKind::Map { .. } => deserializer.deserialize_map(MapVisitor {
1176 ptr: self.ptr,
1177 ctx: self.ctx,
1178 })?,
1179 SchemaKind::Enum(_) => deserializer.deserialize_any(EnumVisitor {
1180 ptr: self.ptr,
1181 ctx: self.ctx,
1182 })?,
1183 SchemaKind::Box(_) => SchemaPtrLoadCtx {
1184 ctx: self.ctx,
1185 ptr: self.ptr.into_box().unwrap(),
1186 }
1187 .deserialize(deserializer)?,
1188 SchemaKind::Primitive(p) => {
1189 match p {
1190 Primitive::Bool => *self.ptr.cast_mut() = bool::deserialize(deserializer)?,
1191 Primitive::U8 => *self.ptr.cast_mut() = u8::deserialize(deserializer)?,
1192 Primitive::U16 => *self.ptr.cast_mut() = u16::deserialize(deserializer)?,
1193 Primitive::U32 => *self.ptr.cast_mut() = u32::deserialize(deserializer)?,
1194 Primitive::U64 => *self.ptr.cast_mut() = u64::deserialize(deserializer)?,
1195 Primitive::U128 => *self.ptr.cast_mut() = u128::deserialize(deserializer)?,
1196 Primitive::I8 => *self.ptr.cast_mut() = i8::deserialize(deserializer)?,
1197 Primitive::I16 => *self.ptr.cast_mut() = i16::deserialize(deserializer)?,
1198 Primitive::I32 => *self.ptr.cast_mut() = i32::deserialize(deserializer)?,
1199 Primitive::I64 => *self.ptr.cast_mut() = i64::deserialize(deserializer)?,
1200 Primitive::I128 => *self.ptr.cast_mut() = i128::deserialize(deserializer)?,
1201 Primitive::F32 => *self.ptr.cast_mut() = f32::deserialize(deserializer)?,
1202 Primitive::F64 => *self.ptr.cast_mut() = f64::deserialize(deserializer)?,
1203 Primitive::String => {
1204 *self.ptr.cast_mut() = String::deserialize(deserializer)?
1205 }
1206 Primitive::Opaque { .. } => {
1207 return Err(D::Error::custom(
1208 "Opaque types must be #[repr(C)] or have `SchemaDeserialize` type data in order \
1209 to be loaded in a metadata asset.",
1210 ));
1211 }
1212 };
1213 }
1214 };
1215
1216 Ok(())
1217 }
1218 }
1219
1220 struct StructVisitor<'a, 'srv, 'ptr> {
1221 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1222 ptr: SchemaRefMut<'ptr>,
1223 }
1224
1225 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for StructVisitor<'a, 'srv, 'ptr> {
1226 type Value = ();
1227
1228 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1229 write!(
1234 formatter,
1235 "asset metadata matching the schema: {}",
1236 self.ptr.schema().full_name
1237 )
1238 }
1239
1240 fn visit_seq<A>(mut self, mut seq: A) -> Result<Self::Value, A::Error>
1241 where
1242 A: serde::de::SeqAccess<'de>,
1243 {
1244 let field_count = self.ptr.schema().kind.as_struct().unwrap().fields.len();
1245
1246 for i in 0..field_count {
1247 let field = self.ptr.access_mut().field(i).unwrap();
1248 if seq
1249 .next_element_seed(SchemaPtrLoadCtx {
1250 ctx: self.ctx,
1251 ptr: field.into_schema_ref_mut(),
1252 })?
1253 .is_none()
1254 {
1255 break;
1256 }
1257 }
1258
1259 Ok(())
1260 }
1261
1262 fn visit_map<A>(mut self, mut map: A) -> Result<Self::Value, A::Error>
1263 where
1264 A: serde::de::MapAccess<'de>,
1265 {
1266 while let Some(key) = map.next_key::<String>()? {
1267 match self.ptr.access_mut().field(&key) {
1268 Ok(field) => {
1269 map.next_value_seed(SchemaPtrLoadCtx {
1270 ctx: self.ctx,
1271 ptr: field.into_schema_ref_mut(),
1272 })?;
1273 }
1274 Err(_) => {
1275 let fields = &self.ptr.schema().kind.as_struct().unwrap().fields;
1276 let mut msg = format!("unknown field `{key}`, ");
1277 if !fields.is_empty() {
1278 msg += "expected one of ";
1279 for (i, field) in fields.iter().enumerate() {
1280 msg += &field
1281 .name
1282 .as_ref()
1283 .map(|x| format!("`{x}`"))
1284 .unwrap_or_else(|| format!("`{i}`"));
1285 if i < fields.len() - 1 {
1286 msg += ", "
1287 }
1288 }
1289 } else {
1290 msg += "there are no fields"
1291 }
1292 return Err(A::Error::custom(msg));
1293 }
1294 }
1295 }
1296
1297 Ok(())
1298 }
1299 }
1300
1301 struct VecVisitor<'a, 'srv, 'ptr> {
1302 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1303 ptr: SchemaRefMut<'ptr>,
1304 }
1305
1306 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for VecVisitor<'a, 'srv, 'ptr> {
1307 type Value = ();
1308
1309 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1310 write!(
1312 formatter,
1313 "asset metadata matching the schema: {}",
1314 self.ptr.schema().full_name
1315 )
1316 }
1317
1318 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1319 where
1320 A: serde::de::SeqAccess<'de>,
1321 {
1322 let v = unsafe { &mut *(self.ptr.as_ptr() as *mut SchemaVec) };
1324 let item_schema = v.schema();
1325 loop {
1326 let mut item = SchemaBox::default(item_schema);
1327 if seq
1328 .next_element_seed(SchemaPtrLoadCtx {
1329 ctx: self.ctx,
1330 ptr: item.as_mut(),
1331 })?
1332 .is_none()
1333 {
1334 break;
1335 }
1336 v.push_box(item);
1337 }
1338
1339 Ok(())
1340 }
1341 }
1342
1343 struct MapVisitor<'a, 'srv, 'ptr> {
1344 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1345 ptr: SchemaRefMut<'ptr>,
1346 }
1347
1348 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for MapVisitor<'a, 'srv, 'ptr> {
1349 type Value = ();
1350
1351 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1352 write!(
1354 formatter,
1355 "asset metadata matching the schema: {}",
1356 self.ptr.schema().full_name
1357 )
1358 }
1359
1360 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1361 where
1362 A: serde::de::MapAccess<'de>,
1363 {
1364 let v = unsafe { &mut *(self.ptr.as_ptr() as *mut SchemaMap) };
1366
1367 let key_schema = v.key_schema();
1368 let value_schema = v.value_schema();
1369 loop {
1370 let mut key = SchemaBox::default(key_schema);
1371 if map
1372 .next_key_seed(SchemaPtrLoadCtx {
1373 ctx: self.ctx,
1374 ptr: key.as_mut(),
1375 })?
1376 .is_none()
1377 {
1378 break;
1379 }
1380 let mut value = SchemaBox::default(value_schema);
1381 map.next_value_seed(SchemaPtrLoadCtx {
1382 ctx: self.ctx,
1383 ptr: value.as_mut(),
1384 })?;
1385
1386 v.insert_box(key, value);
1387 }
1388 Ok(())
1389 }
1390 }
1391
1392 struct EnumVisitor<'a, 'srv, 'ptr> {
1393 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1394 ptr: SchemaRefMut<'ptr>,
1395 }
1396
1397 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for EnumVisitor<'a, 'srv, 'ptr> {
1398 type Value = ();
1399
1400 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1401 write!(
1403 formatter,
1404 "asset metadata matching the schema: {}",
1405 self.ptr.schema().full_name
1406 )
1407 }
1408
1409 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1410 where
1411 E: Error,
1412 {
1413 let enum_info = self.ptr.schema().kind.as_enum().unwrap();
1414 let var_idx = enum_info
1415 .variants
1416 .iter()
1417 .position(|x| x.name == v)
1418 .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &self))?;
1419
1420 if !enum_info.variants[var_idx]
1421 .schema
1422 .kind
1423 .as_struct()
1424 .unwrap()
1425 .fields
1426 .is_empty()
1427 {
1428 return Err(E::custom(format!(
1429 "Cannot deserialize enum variant with fields from string: {v}"
1430 )));
1431 }
1432
1433 unsafe {
1435 match enum_info.tag_type {
1436 EnumTagType::U8 => self.ptr.as_ptr().cast::<u8>().write(var_idx as u8),
1437 EnumTagType::U16 => self.ptr.as_ptr().cast::<u16>().write(var_idx as u16),
1438 EnumTagType::U32 => self.ptr.as_ptr().cast::<u32>().write(var_idx as u32),
1439 }
1440 }
1441
1442 Ok(())
1443 }
1444
1445 fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
1446 where
1447 A: serde::de::EnumAccess<'de>,
1448 {
1449 let (value_ptr, var_access) = data.variant_seed(EnumPtrLoadCtx { ptr: self.ptr })?;
1450
1451 var_access.newtype_variant_seed(SchemaPtrLoadCtx {
1452 ctx: self.ctx,
1453 ptr: value_ptr,
1454 })?;
1455
1456 Ok(())
1457 }
1458 }
1459
1460 struct EnumPtrLoadCtx<'ptr> {
1461 ptr: SchemaRefMut<'ptr>,
1462 }
1463
1464 impl<'ptr, 'de> DeserializeSeed<'de> for EnumPtrLoadCtx<'ptr> {
1465 type Value = SchemaRefMut<'ptr>;
1466
1467 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1468 where
1469 D: serde::Deserializer<'de>,
1470 {
1471 let var_name = String::deserialize(deserializer)?;
1472 let enum_info = self.ptr.schema().kind.as_enum().unwrap();
1473 let value_offset = self.ptr.schema().field_offsets()[0].1;
1474 let (var_idx, var_schema) = enum_info
1475 .variants
1476 .iter()
1477 .enumerate()
1478 .find_map(|(idx, info)| (info.name == var_name).then_some((idx, info.schema)))
1479 .ok_or_else(|| {
1480 D::Error::custom(format!(
1481 "Unknown enum variant `{var_name}`, expected one of: {}",
1482 enum_info
1483 .variants
1484 .iter()
1485 .map(|x| format!("`{}`", x.name))
1486 .collect::<Vec<_>>()
1487 .join(", ")
1488 ))
1489 })?;
1490
1491 match enum_info.tag_type {
1494 EnumTagType::U8 => unsafe { self.ptr.as_ptr().cast::<u8>().write(var_idx as u8) },
1495 EnumTagType::U16 => unsafe {
1496 self.ptr.as_ptr().cast::<u16>().write(var_idx as u16)
1497 },
1498 EnumTagType::U32 => unsafe {
1499 self.ptr.as_ptr().cast::<u32>().write(var_idx as u32)
1500 },
1501 }
1502
1503 if var_schema.kind.as_struct().is_none() {
1504 return Err(D::Error::custom(
1505 "All enum variant types must have a struct Schema",
1506 ));
1507 }
1508
1509 unsafe {
1510 Ok(SchemaRefMut::from_ptr_schema(
1511 self.ptr.as_ptr().add(value_offset),
1512 var_schema,
1513 ))
1514 }
1515 }
1516 }
1517}