aboutsummaryrefslogtreecommitdiff
path: root/tools/tscn-to-bsn
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-08 19:47:17 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-08 19:47:17 +0800
commitdfbc80098c2b46adb14a571947fb7ac8f0169224 (patch)
tree117051fb612b0fd720f7973f84d8d6823e25e3cb /tools/tscn-to-bsn
parent674879d7676f8a76d728c1fa4c739f9bbbe98b27 (diff)
feat(build-cache): write uid map to JSON cache file
Diffstat (limited to 'tools/tscn-to-bsn')
-rw-r--r--tools/tscn-to-bsn/Cargo.lock26
-rw-r--r--tools/tscn-to-bsn/Cargo.toml1
-rw-r--r--tools/tscn-to-bsn/src/cmd/cmd_build_cache.rs170
-rw-r--r--tools/tscn-to-bsn/src/err/json.rs8
-rw-r--r--tools/tscn-to-bsn/src/err/mod.rs1
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;