Files
HomeAgent/tools/starmap-sync.mjs
JianFeeeee cf3d3769b1 perf(starmap): WebUI/GUI 星图卡顿修复 + 抽出共享「布局数学」单一源
## 症状与实测

WebUI 星图建图阻塞主线程 9339ms、draw calls 7514;GUI 建图 2466ms、
draw calls 3477。两者在空闲时仍在空转渲染。

## WebUI(internal/plugins/webui/dashboard.js)

- 力导向布局 O(n²) 两两互斥改**网格近邻**,并移入 **Web Worker**
  ⇒ 建图阻塞 **9339ms → 57ms**
- 每节点光晕 3753 个 draw call → 1 个 InstancedMesh
- 边 966 → 4 个 LineSegments
- 空闲帧跳过 + 星空时间驱动
- 结果:draw calls **7514 → 3760**,空闲阻塞 **99% → 3%**

## GUI(cmd/gui/renderer/app.js)

- draw calls **3477 → 1809**,建图 **2466ms → 943ms**
- 首屏可见性守卫(GUI 默认 chat 视图,星图不该在后台初始化)
- 空闲 12fps → 0.2fps

修 GUI 两个自引 bug:
  1. 抄 WebUI 的 `starmapMorphSet`(GUI 实际用 `state.starmapGrown`)⇒
     每帧 ReferenceError、星图 0 帧;
  2. `camKey` 读 `starmapCam.position` 无守卫(`switchChatPanel` 同步调
     `starmapAnimate` 时 cam 为 nil)。**同一缺陷 WebUI 也存在,两处均已补守卫。**

## 共享源(方案 A:只共享「布局数学」这一层)

新增 `shared/starmap/layout.js`(3 个纯函数,零 DOM/全局引用)+
生成器 `tools/starmap-sync.mjs` + 门禁 `make check-starmap-shared`(已进
make test)。三处(共享源 / dashboard.js / app.js)的标记块必须逐字节相同。

**为何只共享这一层**(实测依据):web 星图 54 个函数、gui 24 个,两者同名
函数逐字节相同数为 **0**。GUI 有设备桥/computer use/托盘/自有面板,WebUI 有
侧栏页签体系 —— 共享受众完全不同的 UI 代码是错的,能安全共享的只有布局数学。

**为何用生成器而非运行时加载**:GUI 用 file:// (main.js 的 loadFile),可能
离线;让其拉服务端 dashboard.js 会把「星图能否初始化」绑到网络与登录态上。

**为何 GUI 不用 Web Worker**:file:// 下外链与 Blob Worker 实测都能跑,但不把
「星图能否初始化」押在打包环境的 Worker 行为上。

## 判据

- `cmd/gui/starmap-perf.test.mjs`(21 条,接入 npm test)
- 扩充 `internal/plugins/webui/starmap_perf_test.go`(同源判据 +
  checkStarmapSharedBlock)
- 变异 M1/M2/M3 全部报红

## 附带

cmd/gui/renderer/app.js 同时含上一提交的 GUI `system` 事件消费(协议修复),
因同文件交织未再拆分,见提交 fix(clients)。
2026-10-13 16:21:04 +08:00

95 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// 把 shared/starmap/*.js 同步进各客户端的内联脚本标记块。
//
// ## 为什么是「生成器」而不是「运行时加载」
//
// 两个客户端的内联脚本形态不同、加载机制也不同:
// · WebUI:dashboard.js 由 Go 的 //go:embed 编进二进制,再被 init() 拼进 HTML
// · GUI :app.js 由 electron 用 file:// 直接 <script src> 加载
//
// 让 GUI 去服务端拉 dashboard.js 会把「星图能否初始化」绑到网络与登录态上
// (实测 GUI 是 file:// + 可能离线启动),而让 WebUI 内联一份 GUI 的代码则
// 要动 embed 与启动顺序。两者都比「编辑期把同一份源码写进两个文件的标记块」
// 风险大得多。
//
// 所以取生成器方案:**唯一事实源在 shared/,两份客户端文件里的相应区间由本
// 脚本重写**。判据(starmap-perf.test.mjs 一侧)钉住三处逐字节相同,
// 手改任一副本会被立刻拦下。
//
// 用法:
// node tools/starmap-sync.mjs # 同步
// node tools/starmap-sync.mjs --check # 只校验,漂移则退出 1
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
// 共享块 → 宿主文件。块名与标记名构成契约。
const SHARED = "starmap/layout";
const MARK_BEGIN = `// ===== BEGIN shared:${SHARED} =====`;
const MARK_END = `// ===== END shared:${SHARED} =====`;
const HOSTS = [
"internal/plugins/webui/dashboard.js",
"cmd/gui/renderer/app.js",
];
const CHECK = process.argv.includes("--check");
const sharedPath = join(ROOT, "shared", SHARED + ".js");
const shared = readFileSync(sharedPath, "utf8").replace(/\r\n/g, "\n");
// 标记块内容 = 共享源 + 一行来源提示。提示放在块内(而不是块外),
// 这样「块内容逐字节相同」这条判据才能同时保证「两边都带着同一提示」。
const block =
MARK_BEGIN +
"\n" +
shared.replace(/\n+$/, "") +
"\n// 本块由 tools/starmap-sync.mjs 从 shared/" +
SHARED +
".js 生成 —— 请改共享源,不要直接改这里。\n" +
MARK_END;
let drift = 0;
let changed = 0;
for (const rel of HOSTS) {
const p = join(ROOT, rel);
const src = readFileSync(p, "utf8").replace(/\r\n/g, "\n");
const b = src.indexOf(MARK_BEGIN);
const e = src.indexOf(MARK_END);
if (b < 0 || e < 0 || e < b) {
console.error(`✗ ${rel}: 找不到共享标记块(${MARK_BEGIN} / ${MARK_END})`);
drift++;
continue;
}
const cur = src.slice(b, e + MARK_END.length);
if (cur === block) {
console.log(`✓ ${rel}: 已与共享源一致`);
continue;
}
drift++;
if (CHECK) {
console.error(`✗ ${rel}: 共享块与 shared/${SHARED}.js 不一致(漂移)`);
continue;
}
writeFileSync(p, src.slice(0, b) + block + src.slice(e + MARK_END.length), "utf8");
console.log(`~ ${rel}: 已刷新共享块`);
changed++;
}
if (CHECK) {
if (drift) {
console.error(
`\n共享块漂移 ${drift} 处。修法:node tools/starmap-sync.mjs\n` +
`(若是改了算法,请只改 shared/${SHARED}.js,再跑同步。)`,
);
process.exit(1);
}
console.log("\n共享块一致");
process.exit(0);
}
console.log(changed ? `\n已同步 ${changed} 个文件` : "\n无需改动");