diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-08 19:47:17 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-08 19:47:17 +0800 |
| commit | dfbc80098c2b46adb14a571947fb7ac8f0169224 (patch) | |
| tree | 117051fb612b0fd720f7973f84d8d6823e25e3cb | |
| parent | 674879d7676f8a76d728c1fa4c739f9bbbe98b27 (diff) | |
feat(build-cache): write uid map to JSON cache file
| -rw-r--r-- | tools/tscn-to-bsn/Cargo.lock | 26 | ||||
| -rw-r--r-- | tools/tscn-to-bsn/Cargo.toml | 1 | ||||
| -rw-r--r-- | tools/tscn-to-bsn/src/cmd/cmd_build_cache.rs | 170 | ||||
| -rw-r--r-- | tools/tscn-to-bsn/src/err/json.rs | 8 | ||||
| -rw-r--r-- | tools/tscn-to-bsn/src/err/mod.rs | 1 |
5 files changed, 202 insertions, 4 deletions
diff --git a/tools/tscn-to-bsn/Cargo.lock b/tools/tscn-to-bsn/Cargo.lock index 20515f9..70a3d6e 100644 --- a/tools/tscn-to-bsn/Cargo.lock +++ b/tools/tscn-to-bsn/Cargo.lock @@ -53,6 +53,12 @@ dependencies = [ ] [[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] name = "just_fmt" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -186,6 +192,19 @@ dependencies = [ ] [[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] name = "serde_spanned" version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -264,6 +283,7 @@ dependencies = [ "mingling", "ron", "serde", + "serde_json", ] [[package]] @@ -286,3 +306,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/tools/tscn-to-bsn/Cargo.toml b/tools/tscn-to-bsn/Cargo.toml index 295e7fb..eaa4e07 100644 --- a/tools/tscn-to-bsn/Cargo.toml +++ b/tools/tscn-to-bsn/Cargo.toml @@ -9,6 +9,7 @@ path = "src/bin/tscn2bsn.rs" [dependencies] serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" ron = "0.12.2" [dependencies.mingling] diff --git a/tools/tscn-to-bsn/src/cmd/cmd_build_cache.rs b/tools/tscn-to-bsn/src/cmd/cmd_build_cache.rs index 28e6334..660caf1 100644 --- a/tools/tscn-to-bsn/src/cmd/cmd_build_cache.rs +++ b/tools/tscn-to-bsn/src/cmd/cmd_build_cache.rs @@ -1,16 +1,178 @@ -use mingling::macros::{buffer, command, empty_result, pack_err, r_println, renderer}; +use std::collections::BTreeMap; +use std::io; +use std::path::{Path, PathBuf}; -use crate::{Next, res::gd_proj_dir::ResGodotProjectDirectory}; +use mingling::macros::routeify; +use mingling::{ + Grouped, + macros::{buffer, command, pack_err, r_println, renderer}, +}; + +use crate::{ + Next, + gd_res_structure::{TscnValue, parse}, + res::gd_proj_dir::ResGodotProjectDirectory, +}; pack_err!(ErrorGodotProjectNotFound); -#[command(node = "build-cache")] +/// Build result +#[derive(Grouped)] +pub struct ResultCacheBuilt { + path: PathBuf, + len: usize, +} + +#[command(node = "build-cache", routeify)] pub fn build_cache(proj: &ResGodotProjectDirectory) -> Next { let Some(ref dir) = proj.dir else { return ErrorGodotProjectNotFound::default().into(); }; - empty_result!() + // 1. Scan uid → res:// paths + let map = scan_project(dir)?; + + // 2. Write .godot/tscn2bsn.cache.json + let cache_path = dir.join(".godot").join("tscn2bsn.cache.json"); + if let Some(parent) = cache_path.parent() { + std::fs::create_dir_all(parent)?; + } + let json = serde_json::to_string_pretty(&map)?; + std::fs::write(&cache_path, json)?; + + ResultCacheBuilt { + path: cache_path, + len: map.len(), + } + .into() +} + +/// Recursively collect all files in the project, skipping `.godot` / `.git` +fn collect_files(dir: &Path, out: &mut Vec<PathBuf>) -> io::Result<()> { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + let name = entry.file_name().to_string_lossy().into_owned(); + if name == ".godot" || name == ".git" { + continue; + } + collect_files(&path, out)?; + } else if path.is_file() { + out.push(path); + } + } + Ok(()) +} + +/// Project relative path → `res://` form (Godot's standard in-project path) +fn to_res_path(project_dir: &Path, file: &Path) -> String { + let rel = file.strip_prefix(project_dir).unwrap_or(file); + let rel = rel.to_string_lossy().replace('\\', "/"); + format!("res://{rel}") +} + +/// Scan all uid sources, aggregate the mapping +fn scan_project(project_dir: &Path) -> io::Result<BTreeMap<String, String>> { + let mut files = Vec::new(); + collect_files(project_dir, &mut files)?; + + let mut map = BTreeMap::new(); + for file in files { + let ext = file + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_lowercase(); + match ext.as_str() { + // scenes/foo.glb.import → resource path res://scenes/foo.glb + "import" => { + if let Some(uid) = parse_import_uid(&file) { + let res_path = to_res_path(project_dir, &file); + insert_unique( + &mut map, + uid, + res_path.trim_end_matches(".import").to_string(), + ); + } + } + "tscn" | "tres" => { + if let Some(uid) = parse_scene_uid(&file) { + insert_unique(&mut map, uid, to_res_path(project_dir, &file)); + } + } + // foo.gd.uid → resource path res://foo.gd + "uid" => { + if let Some(uid) = parse_sidecar_uid(&file) { + let res_path = to_res_path(project_dir, &file); + insert_unique(&mut map, uid, res_path.trim_end_matches(".uid").to_string()); + } + } + _ => {} + } + } + Ok(map) +} + +fn insert_unique(map: &mut BTreeMap<String, String>, uid: String, res_path: String) { + match map.entry(uid.clone()) { + std::collections::btree_map::Entry::Vacant(v) => { + v.insert(res_path); + } + std::collections::btree_map::Entry::Occupied(o) => { + eprintln!("Warning: duplicate uid {uid}: {} vs {res_path}", o.get()); + } + } +} + +/// `.import` file: `uid="uid://..."` in the `[remap]` section (writer: editor_file_system.cpp L2693) +fn parse_import_uid(file: &Path) -> Option<String> { + let content = std::fs::read_to_string(file).ok()?; + let mut in_remap = false; + for line in content.lines() { + let line = line.trim(); + if line.starts_with('[') { + in_remap = line == "[remap]"; + continue; + } + if in_remap && let Some(v) = line.strip_prefix("uid=") { + let uid = v.trim().trim_matches('"'); + if uid.starts_with("uid://") { + return Some(uid.to_string()); + } + } + } + None +} + +/// `.tscn` / `.tres`: reuse the tscn parser, extract the `uid` field from the header tag +fn parse_scene_uid(file: &Path) -> Option<String> { + let content = std::fs::read_to_string(file).ok()?; + let doc = parse(&content).ok()?; + for (key, value) in &doc.header.fields { + if key == "uid" + && let TscnValue::Str(s) = value + { + return Some(s.clone()); + } + } + None +} + +/// `.uid` sidecar file: the first line is `uid://...` (writer: editor_file_system.cpp L939) +fn parse_sidecar_uid(file: &Path) -> Option<String> { + let content = std::fs::read_to_string(file).ok()?; + let first = content.lines().next()?.trim(); + if first.starts_with("uid://") { + Some(first.to_string()) + } else { + None + } +} + +#[renderer(buffer)] +pub fn render_cache_built(r: ResultCacheBuilt) { + r_println!("OK: wrote {} ({} uids)", r.path.display(), r.len); } #[renderer(buffer)] diff --git a/tools/tscn-to-bsn/src/err/json.rs b/tools/tscn-to-bsn/src/err/json.rs new file mode 100644 index 0000000..b0f240e --- /dev/null +++ b/tools/tscn-to-bsn/src/err/json.rs @@ -0,0 +1,8 @@ +use mingling::macros::{buffer, group, r_println, renderer}; + +group!(ErrorJson = serde_json::Error); + +#[renderer(buffer)] +pub fn handle_error_json(err: ErrorJson) { + r_println!("{}", err.to_string()) +} diff --git a/tools/tscn-to-bsn/src/err/mod.rs b/tools/tscn-to-bsn/src/err/mod.rs index 29f6fd0..4721ea1 100644 --- a/tools/tscn-to-bsn/src/err/mod.rs +++ b/tools/tscn-to-bsn/src/err/mod.rs @@ -1,3 +1,4 @@ pub mod io; +pub mod json; pub mod ron; pub mod tscn_parse; |
