diff --git a/Cargo.lock b/Cargo.lock index 123e7d01b9..92c604e033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "async-task" version = "4.7.1" @@ -1357,6 +1368,7 @@ dependencies = [ "anyhow", "append-only-vec", "async-channel 1.9.0", + "async-recursion", "bevy_tasks 0.11.3", "bones_schema", "bones_utils", diff --git a/framework_crates/bones_asset/Cargo.toml b/framework_crates/bones_asset/Cargo.toml index ebd115c260..2d99280a17 100644 --- a/framework_crates/bones_asset/Cargo.toml +++ b/framework_crates/bones_asset/Cargo.toml @@ -43,6 +43,7 @@ sha2 = "0.10" tracing = "0.1" ulid = "1.0" ustr = { workspace = true } +async-recursion = "1.1.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] notify = "6.0" diff --git a/framework_crates/bones_asset/src/server.rs b/framework_crates/bones_asset/src/server.rs index ed974d9a82..e9dd48e59a 100644 --- a/framework_crates/bones_asset/src/server.rs +++ b/framework_crates/bones_asset/src/server.rs @@ -22,8 +22,8 @@ use serde::{de::DeserializeSeed, Deserialize}; #[allow(unused_imports)] use tracing::info; use ulid::Ulid; - use crate::prelude::*; +use async_recursion::async_recursion; mod schema_loader; @@ -639,6 +639,8 @@ impl AssetServer { schema, dependencies: &mut dependencies, }; + let linked_files = loader.collect_and_load_linked_files(contents).await?; + let data = if loc.path.extension().unwrap().to_str().unwrap() == "json" { let mut deserializer = serde_json::Deserializer::from_slice(contents); loader.deserialize(&mut deserializer)? @@ -1040,6 +1042,167 @@ mod metadata { pub schema: &'static Schema, } + impl<'srv> MetaAssetLoadCtx<'srv> { + /// Collects and loads all path-based value + /// TODO: Collection doesn't work yet + pub async fn collect_and_load_linked_files( + &self, + contents: &[u8], + ) -> anyhow::Result> { + let mut linked_files = DashMap::new(); + self.recursively_collect_linked_files(contents, &mut linked_files) + .await?; + Ok(linked_files) + } + + async fn recursively_collect_linked_files( + &self, + contents: &[u8], + linked_files: &mut DashMap, + ) -> anyhow::Result<()> { + let yaml_docs = serde_yaml::from_slice::(contents)?; + self.process_yaml_value(&yaml_docs, linked_files).await?; + Ok(()) + } + + #[async_recursion] + async fn process_yaml_value( + &self, + value: &serde_yaml::Value, + linked_files: &mut DashMap, + ) -> anyhow::Result<()> { + println!("Processing YAML value: {:?}", value); + match value { + serde_yaml::Value::String(s) => { + println!("Found string value: {}", s); + let path = self.resolve_relative_path(s); + if path.exists() { + println!("Valid file path found: {:?}", path); + self.load_and_add_linked_file(path, linked_files).await?; + } else { + println!("Not a valid file path: {:?}", path); + } + } + serde_yaml::Value::Sequence(seq) => { + println!("Processing sequence with {} items", seq.len()); + for (index, item) in seq.iter().enumerate() { + println!("Processing sequence item {}", index); + self.process_yaml_value(item, linked_files).await?; + } + } + serde_yaml::Value::Mapping(map) => { + println!("Processing mapping with {} items", map.len()); + for (key, v) in map { + println!("Processing mapping key: {:?}", key); + self.process_yaml_value(v, linked_files).await?; + } + } + _ => { + println!("Ignoring YAML value type: {:?}", value); + } + } + Ok(()) + } + + fn resolve_relative_path(&self, s: &str) -> PathBuf { + println!("Resolving relative path: {}", s); + let relative_path = PathBuf::from(s); + let base_dir = self.loc.path.parent().unwrap_or(Path::new("/")); + let resolved_path = base_dir.join(relative_path); + println!("Resolved path: {:?}", resolved_path); + resolved_path + } + + #[async_recursion] + async fn load_and_add_linked_file( + &self, + path: PathBuf, + linked_files: &mut DashMap, + ) -> anyhow::Result<()> { + println!("Loading and adding linked file: {:?}", path); + if linked_files.contains_key(&path) { + println!("File already loaded: {:?}", path); + return Ok(()); + } + + let contents = self + .server + .io + .load_file(AssetLocRef { + path: &path, + pack: self.loc.pack, + }) + .await?; + println!("File contents loaded, size: {} bytes", contents.len()); + let schema_box = self.load_file_contents(&path, &contents).await?; + println!("File contents loaded into SchemaBox"); + linked_files.insert(path.clone(), schema_box); + println!("SchemaBox inserted into linked_files"); + + println!("Recursively processing loaded file"); + self.recursively_collect_linked_files(&contents, linked_files) + .await?; + + Ok(()) + } + + async fn load_file_contents( + &self, + path: &Path, + contents: &[u8], + ) -> anyhow::Result { + println!("Loading file contents for: {:?}", path); + let file_extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or(""); + println!("File extension: {}", file_extension); + let schema = SCHEMA_REGISTRY + .schemas + .iter() + .find(|schema| { + schema + .type_data + .get::() + .and_then(|asset_kind| match asset_kind { + AssetKind::Custom { extensions, .. } => { + Some(extensions.contains(&file_extension.to_string())) + } + _ => Some(false), + }) + .unwrap_or(false) + }) + .ok_or_else(|| { + anyhow::anyhow!("No schema found for file extension: {}", file_extension) + })?; + println!("Schema found: {:?}", schema.name); + + let loader = schema + .type_data + .get::() + .and_then(|asset_kind| { + if let AssetKind::Custom { loader, .. } = asset_kind { + Some(loader) + } else { + None + } + }) + .ok_or_else(|| anyhow::anyhow!("No loader found for file type"))?; + println!("Loader found for file type"); + + let ctx = AssetLoadCtx { + asset_server: self.server.clone(), + loc: AssetLoc { + path: path.to_path_buf(), + pack: self.loc.pack.map(|s| s.to_string()), + }, + dependencies: Arc::new(AppendOnlyVec::new()), + }; + + println!("Loading file with custom loader"); + let schema_box = loader.load(ctx, contents).await?; + println!("File loaded into SchemaBox"); + Ok(schema_box) + } + } + impl<'asset, 'de> DeserializeSeed<'de> for MetaAssetLoadCtx<'asset> { type Value = SchemaBox;