Appearance
ADR-236: 循环依赖消解 — scene/render ↔ scene/manager 互依赖拆解
状态: ✅ 核心已实施 — render↔manager 互依赖已拆解(2026-08-03,texture-lru 下沉 scene/shared,互环归零);剩余 21 个新增环由 ADR-238 承接并已拆至 11 环(2026-08-03 收尾)。订正:texture-lru 下沉 scene/shared 引入
scene/shared → core边,是 ADR-238 环 ①(core→scene→motion-algos→scene/env→scene/render→scene/shared→core)的成因之一,已在 ADR-238 §6 记录,勿误判为「剩余环不含 render」。门禁转正待剩余 11 环合理保留确认。 日期: 2026-08-03关联: ADR-093(模块分层)、check-circular 架构环守护(
scripts/check-circular.mjs)
背景
npm run check:circular -- --strict 扫描出 27 个白名单外的新增循环依赖(2026-08-03 实测,scripts/circular-allowlist.json 已收紧为 14 个现存已知环后仍全量命中)。全部 27 条环的公共根因是 scene/render ↔ scene/manager 的双向模块依赖:
| 方向 | 证据(源码 import) |
|---|---|
render → manager | frontend/src/scene/render/renderer.ts:21 → import { clearTextureLRU } from '../manager/texture-lru' |
manager → render | frontend/src/scene/manager/model-loader.ts:49 → import { rebuildShadowCasters } from '../render/lighting' |
manager → render | frontend/src/scene/manager/model-ops.ts:42 → import { ... } from '../render/lighting-follow' |
最短环示例:scene/render → scene/manager → scene/render、scene/env → scene/render → scene/manager → scene/env。该环使 check-circular --strict 永远红(CI 若挂门禁即全量失败),且运行时存在模块初始化顺序隐患。
核心判断:这是真实的架构债务,不是白名单误判——三个 import 都指向真实存在且被消费的符号(clearTextureLRU / rebuildShadowCasters / lighting-follow 导出)。此前未挂 check-circular 门禁(2026-08-02 审核结论:存量环属重构范畴),本次立项正式拆解。
决策
分三步拆解互依赖,最终目标:scene/render 与 scene/manager 之间单向依赖(render → manager 或 manager → render 二选一,由依赖方向分析定):
- 下沉共享叶模块:
texture-lru.ts(含clearTextureLRU)与lighting.ts/lighting-follow.ts中的纯函数/无副作用部分下沉到scene/shared/(或既有core/叶模块),两侧均引用下沉后的模块,切断中间层互引。- 候选:
clearTextureLRU(LRU 清空,无 scene 依赖)→scene/shared/texture-lru.ts;rebuildShadowCasters(灯光→阴影投影者重建)需评估对scene/manager的依赖面后决定归属。
- 候选:
- 依赖方向收敛:若下沉不足以完全解环,则收敛为单一方向——按「manager 是资源生命周期持有者、render 是渲染消费者」的分层,倾向 manager → render(render 可感知 manager 的模型集合,manager 不反向感知 render 细节);
model-loader.ts:49的rebuildShadowCasters调用改为经事件/回调注入,或移入 render 侧由加载完成事件触发。 - 收紧门禁:环数归零后,运行
node scripts/check-circular.mjs --update-allowlist确认白名单无幽灵环,并将check-circular --strict挂入check:docs链(此前因存量环未挂,拆解后应可转正)。 - 顺序约束:拆解按「先下沉无依赖叶函数 → 再收敛方向 → 最后挂门禁」推进,每步保持
tsc --noEmit+vitest全绿。
备选方案
| 方案 | 评估 |
|---|---|
A. 仅更新白名单(--update-allowlist 把 27 环吞入) | ❌ 掩盖真实互依赖;环仍存在,运行时隐患与门禁失效照旧——被拒(2026-08-02 审核已定性为「不吞白名单」) |
B. 延迟加载绕环(动态 import() 断编译期依赖) | ⚠️ 可行但引入异步加载路径,renderer.ts 的 clearTextureLRU 是同步清理语义,改造后语义变弱;作为局部兜底保留,非首选 |
| C. 保持现状 | ❌ 27 环常驻,门禁无法转正,架构债无收敛路径 |
影响
frontend/src/scene/render/renderer.ts:clearTextureLRU引用改指下沉模块frontend/src/scene/manager/model-loader.ts:rebuildShadowCasters调用改事件/回调注入或迁至 render 侧frontend/src/scene/manager/model-ops.ts:lighting-follow 引用同步调整- 新增
scene/shared/目录(或并入既有core/叶模块,按 ADR-191 纯叶子约束) scripts/circular-allowlist.json:环数归零后清理package.json:check:circular --strict挂入check:docs链- 回归:
tsc --noEmit、vitest全量、check:docs
相关文档
- 环守护:
scripts/check-circular.mjs+scripts/circular-allowlist.json(2026-08-03 收紧 37→14→12,render↔manager 相关环已清零) - 环归因工具(check-circular 扩展,2026-08-03):
--edges:报告环时附文件级 import 边(前 5 条 + 总数),直接看到环由哪些具体 import 构成--snapshot <file>:保存当前环 key + 模块对→边 为基线快照--diff <file>:与基线对比,标出新增环、已消失环、以及每个新增环路径上基线中不存在的模块对边([新增边]标注)——定位「哪条新 import 引入了环」;--diff --strict按是否有新增环决定退出码- 用法示例:
node scripts/check-circular.mjs --snapshot /tmp/base.json(改动前基线)→ 改动后node scripts/check-circular.mjs --diff /tmp/base.json --edges
- 分层约束:ADR-093、ADR-191(纯叶子模块约束,禁止整桶 import 拖起应用层)
- 代码:
scene/render/*、scene/manager/*、scene/shared/(新增)
实施记录(2026-08-03,Phase 1)
拆解结果:scene/render ↔ scene/manager 互依赖环已全部归零。
- 根因定位:render→manager 唯一边是
renderer.ts:21 → manager/texture-lru(仅用clearTextureLRU);manager→render 为单向(model-loader→lighting、model-ops→lighting-follow,均不反向依赖 manager)。 - 落地:
texture-lru.ts从scene/manager/移至scene/shared/(仅依赖@/core/wails-bindings,天然共享叶),4 处 import 更新(renderer / model-loader / outfit / 测试)。 - 效果:check-circular 同时含 render+manager 的环 27 → 0;剩余 21 个新增环属 core / motion-algos / scene/motion / menus / scene/manager / outfit 等其他模块,另立后续项(ADR-238)。订正:其中环①
core → scene → motion-algos → scene/env → scene/render → scene/shared → core含 render/shared,且其scene/shared → core边正是本 Phase 1 把texture-lru下沉scene/shared时引入(scene/shared/texture-lru.ts → core/wails-bindings.ts)——即本修复自身制造了环①,将由 ADR-238 Phase 1(消除core→scene)一并消除;原「剩余 21 环全部不含 render」表述有误。 - 验证:
tsc --noEmitEXIT=0;texture-lru.test.ts9/9 通过。 - 门禁转正暂缓:ADR 原决策「环数归零后挂 check-circular 进 check:docs」——当前整体环未归零(21 个其他模块环仍在白名单外),立即挂门禁会让 check:docs 常红;待剩余环消解后转正(ADR-236 范围限定为 render↔manager,其余环需各自立项)。