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 use ustr::{ustr, Ustr};
1048
1049 use super::*;
1050
1051 pub struct MetaAssetLoadCtx<'srv> {
1053 pub server: &'srv AssetServer,
1055 pub dependencies: &'srv mut Vec<UntypedHandle>,
1058 pub loc: AssetLocRef<'srv>,
1060 pub schema: &'static Schema,
1062 }
1063
1064 impl<'asset, 'de> DeserializeSeed<'de> for MetaAssetLoadCtx<'asset> {
1065 type Value = SchemaBox;
1066
1067 fn deserialize<D>(mut self, deserializer: D) -> Result<Self::Value, D::Error>
1068 where
1069 D: serde::Deserializer<'de>,
1070 {
1071 let mut ptr = SchemaBox::default(self.schema);
1073
1074 SchemaPtrLoadCtx {
1075 ctx: &mut self,
1076 ptr: ptr.as_mut(),
1077 }
1078 .deserialize(deserializer)?;
1079
1080 Ok(ptr)
1081 }
1082 }
1083
1084 pub struct SchemaPtrLoadCtx<'a, 'srv, 'ptr> {
1086 pub ctx: &'a mut MetaAssetLoadCtx<'srv>,
1088 pub ptr: SchemaRefMut<'ptr>,
1090 }
1091
1092 impl<'a, 'srv, 'ptr, 'de> DeserializeSeed<'de> for SchemaPtrLoadCtx<'a, 'srv, 'ptr> {
1093 type Value = ();
1094
1095 fn deserialize<D>(mut self, deserializer: D) -> Result<Self::Value, D::Error>
1096 where
1097 D: serde::Deserializer<'de>,
1098 {
1099 if self
1101 .ptr
1102 .schema()
1103 .type_data
1104 .get::<SchemaAssetHandle>()
1105 .is_some()
1106 {
1107 let path_string = String::deserialize(deserializer)?;
1108 let mut pack = self.ctx.loc.pack.map(|x| x.to_owned());
1109 let relative_path;
1110 if let Some((pack_prefix, path)) = path_string.split_once(':') {
1111 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}")))?;
1112 if pack_prefix == "core" {
1113 pack = None;
1114 } else {
1115 pack = Some(self.ctx
1116 .server
1117 .store
1118 .pack_dirs
1119 .iter()
1120 .find(|x| x.value().id == pack_id)
1121 .map(|x| x.key().to_owned())
1122 .ok_or_else(|| {
1123 D::Error::custom(format!("Dependent pack {pack_id} not loaded when trying to load asset with path: {path_string}."))
1124 })?);
1125 }
1126 relative_path = path;
1127 } else {
1128 relative_path = &path_string;
1129 };
1130 let relative_path = PathBuf::from(relative_path);
1131 let path = relative_path
1132 .absolutize_from(self.ctx.loc.path.parent().unwrap())
1133 .unwrap();
1134 let handle = self.ctx.server.load_asset((&*path, pack.as_deref()).into());
1135 self.ctx.dependencies.push(handle);
1136 *self
1137 .ptr
1138 .try_cast_mut()
1139 .map_err(|e| D::Error::custom(e.to_string()))? = handle;
1140 return Ok(());
1141 }
1142
1143 if let Some(custom_loader) = self.ptr.schema().type_data.get::<SchemaMetaAssetLoader>()
1145 {
1146 return custom_loader
1147 .load(self.ctx, self.ptr, deserializer)
1148 .map_err(|e| D::Error::custom(e.to_string()));
1149 } else if let Some(schema_deserialize) =
1150 self.ptr.schema().type_data.get::<SchemaDeserialize>()
1151 {
1152 return schema_deserialize.deserialize(self.ptr, deserializer);
1153 }
1154
1155 match &self.ptr.schema().kind {
1156 SchemaKind::Struct(s) => {
1157 if s.fields.len() == 1 && s.fields[0].name.is_none() {
1159 let ptr = unsafe {
1162 SchemaRefMut::from_ptr_schema(self.ptr.as_ptr(), s.fields[0].schema)
1163 };
1164 SchemaPtrLoadCtx { ptr, ctx: self.ctx }.deserialize(deserializer)?
1165 } else {
1166 deserializer.deserialize_any(StructVisitor {
1167 ptr: self.ptr,
1168 ctx: self.ctx,
1169 })?
1170 }
1171 }
1172 SchemaKind::Vec(_) => deserializer.deserialize_seq(VecVisitor {
1173 ptr: self.ptr,
1174 ctx: self.ctx,
1175 })?,
1176 SchemaKind::Map { .. } => deserializer.deserialize_map(MapVisitor {
1177 ptr: self.ptr,
1178 ctx: self.ctx,
1179 })?,
1180 SchemaKind::Enum(_) => deserializer.deserialize_any(EnumVisitor {
1181 ptr: self.ptr,
1182 ctx: self.ctx,
1183 })?,
1184 SchemaKind::Box(_) => SchemaPtrLoadCtx {
1185 ctx: self.ctx,
1186 ptr: self.ptr.into_box().unwrap(),
1187 }
1188 .deserialize(deserializer)?,
1189 SchemaKind::Primitive(p) => {
1190 match p {
1191 Primitive::Bool => *self.ptr.cast_mut() = bool::deserialize(deserializer)?,
1192 Primitive::U8 => *self.ptr.cast_mut() = u8::deserialize(deserializer)?,
1193 Primitive::U16 => *self.ptr.cast_mut() = u16::deserialize(deserializer)?,
1194 Primitive::U32 => *self.ptr.cast_mut() = u32::deserialize(deserializer)?,
1195 Primitive::U64 => *self.ptr.cast_mut() = u64::deserialize(deserializer)?,
1196 Primitive::U128 => *self.ptr.cast_mut() = u128::deserialize(deserializer)?,
1197 Primitive::I8 => *self.ptr.cast_mut() = i8::deserialize(deserializer)?,
1198 Primitive::I16 => *self.ptr.cast_mut() = i16::deserialize(deserializer)?,
1199 Primitive::I32 => *self.ptr.cast_mut() = i32::deserialize(deserializer)?,
1200 Primitive::I64 => *self.ptr.cast_mut() = i64::deserialize(deserializer)?,
1201 Primitive::I128 => *self.ptr.cast_mut() = i128::deserialize(deserializer)?,
1202 Primitive::F32 => *self.ptr.cast_mut() = f32::deserialize(deserializer)?,
1203 Primitive::F64 => *self.ptr.cast_mut() = f64::deserialize(deserializer)?,
1204 Primitive::String => {
1205 *self.ptr.cast_mut() = String::deserialize(deserializer)?
1206 }
1207 Primitive::Opaque { .. } => {
1208 return Err(D::Error::custom(
1209 "Opaque types must be #[repr(C)] or have `SchemaDeserialize` type data in order \
1210 to be loaded in a metadata asset.",
1211 ));
1212 }
1213 };
1214 }
1215 };
1216
1217 Ok(())
1218 }
1219 }
1220
1221 struct StructVisitor<'a, 'srv, 'ptr> {
1222 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1223 ptr: SchemaRefMut<'ptr>,
1224 }
1225
1226 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for StructVisitor<'a, 'srv, 'ptr> {
1227 type Value = ();
1228
1229 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1230 write!(
1235 formatter,
1236 "asset metadata matching the schema: {}",
1237 self.ptr.schema().full_name
1238 )
1239 }
1240
1241 fn visit_seq<A>(mut self, mut seq: A) -> Result<Self::Value, A::Error>
1242 where
1243 A: serde::de::SeqAccess<'de>,
1244 {
1245 let field_count = self.ptr.schema().kind.as_struct().unwrap().fields.len();
1246
1247 for i in 0..field_count {
1248 let field = self.ptr.access_mut().field(i).unwrap();
1249 if seq
1250 .next_element_seed(SchemaPtrLoadCtx {
1251 ctx: self.ctx,
1252 ptr: field.into_schema_ref_mut(),
1253 })?
1254 .is_none()
1255 {
1256 break;
1257 }
1258 }
1259
1260 Ok(())
1261 }
1262
1263 fn visit_map<A>(mut self, mut map: A) -> Result<Self::Value, A::Error>
1264 where
1265 A: serde::de::MapAccess<'de>,
1266 {
1267 while let Some(key) = map.next_key::<String>()? {
1268 match self.ptr.access_mut().field(&key) {
1269 Ok(field) => {
1270 map.next_value_seed(SchemaPtrLoadCtx {
1271 ctx: self.ctx,
1272 ptr: field.into_schema_ref_mut(),
1273 })?;
1274 }
1275 Err(_) => {
1276 let fields = &self.ptr.schema().kind.as_struct().unwrap().fields;
1277 let mut msg = format!("unknown field `{key}`, ");
1278 if !fields.is_empty() {
1279 msg += "expected one of ";
1280 for (i, field) in fields.iter().enumerate() {
1281 msg += &field
1282 .name
1283 .as_ref()
1284 .map(|x| format!("`{x}`"))
1285 .unwrap_or_else(|| format!("`{i}`"));
1286 if i < fields.len() - 1 {
1287 msg += ", "
1288 }
1289 }
1290 } else {
1291 msg += "there are no fields"
1292 }
1293 return Err(A::Error::custom(msg));
1294 }
1295 }
1296 }
1297
1298 Ok(())
1299 }
1300 }
1301
1302 struct VecVisitor<'a, 'srv, 'ptr> {
1303 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1304 ptr: SchemaRefMut<'ptr>,
1305 }
1306
1307 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for VecVisitor<'a, 'srv, 'ptr> {
1308 type Value = ();
1309
1310 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1311 write!(
1313 formatter,
1314 "asset metadata matching the schema: {}",
1315 self.ptr.schema().full_name
1316 )
1317 }
1318
1319 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1320 where
1321 A: serde::de::SeqAccess<'de>,
1322 {
1323 let v = unsafe { &mut *(self.ptr.as_ptr() as *mut SchemaVec) };
1325 loop {
1326 let item_schema = v.schema();
1327 let mut item = SchemaBox::default(item_schema);
1328 let item_ref = item.as_mut();
1329 if seq
1330 .next_element_seed(SchemaPtrLoadCtx {
1331 ctx: self.ctx,
1332 ptr: item_ref,
1333 })?
1334 .is_none()
1335 {
1336 break;
1337 }
1338 v.push_box(item);
1339 }
1340
1341 Ok(())
1342 }
1343 }
1344
1345 struct MapVisitor<'a, 'srv, 'ptr> {
1346 ctx: &'a mut MetaAssetLoadCtx<'srv>,
1347 ptr: SchemaRefMut<'ptr>,
1348 }
1349
1350 impl<'a, 'srv, 'ptr, 'de> Visitor<'de> for MapVisitor<'a, 'srv, 'ptr> {
1351 type Value = ();
1352
1353 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1354 write!(
1356 formatter,
1357 "asset metadata matching the schema: {}",
1358 self.ptr.schema().full_name
1359 )
1360 }
1361
1362 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1363 where
1364 A: serde::de::MapAccess<'de>,
1365 {
1366 let v = unsafe { &mut *(self.ptr.as_ptr() as *mut SchemaMap) };
1368 let is_ustr = v.key_schema() == Ustr::schema();
1369 if v.key_schema() != String::schema() && !is_ustr {
1370 return Err(A::Error::custom(
1371 "Can only deserialize maps with `String` or `Ustr` keys.",
1372 ));
1373 }
1374 while let Some(key) = map.next_key::<String>()? {
1375 let key = if is_ustr {
1376 SchemaBox::new(ustr(&key))
1377 } else {
1378 SchemaBox::new(key)
1379 };
1380 let mut value = SchemaBox::default(v.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}