Skip to content

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 → managerfrontend/src/scene/render/renderer.ts:21import { clearTextureLRU } from '../manager/texture-lru'
manager → renderfrontend/src/scene/manager/model-loader.ts:49import { rebuildShadowCasters } from '../render/lighting'
manager → renderfrontend/src/scene/manager/model-ops.ts:42import { ... } from '../render/lighting-follow'

最短环示例:scene/render → scene/manager → scene/renderscene/env → scene/render → scene/manager → scene/env。该环使 check-circular --strict 永远红(CI 若挂门禁即全量失败),且运行时存在模块初始化顺序隐患。

核心判断:这是真实的架构债务,不是白名单误判——三个 import 都指向真实存在且被消费的符号(clearTextureLRU / rebuildShadowCasters / lighting-follow 导出)。此前未挂 check-circular 门禁(2026-08-02 审核结论:存量环属重构范畴),本次立项正式拆解。

决策

分三步拆解互依赖,最终目标:scene/renderscene/manager 之间单向依赖(render → manager 或 manager → render 二选一,由依赖方向分析定)

  1. 下沉共享叶模块texture-lru.ts(含 clearTextureLRU)与 lighting.ts/lighting-follow.ts 中的纯函数/无副作用部分下沉到 scene/shared/(或既有 core/ 叶模块),两侧均引用下沉后的模块,切断中间层互引。
    • 候选:clearTextureLRU(LRU 清空,无 scene 依赖)→ scene/shared/texture-lru.tsrebuildShadowCasters(灯光→阴影投影者重建)需评估对 scene/manager 的依赖面后决定归属。
  2. 依赖方向收敛:若下沉不足以完全解环,则收敛为单一方向——按「manager 是资源生命周期持有者、render 是渲染消费者」的分层,倾向 manager → render(render 可感知 manager 的模型集合,manager 不反向感知 render 细节);model-loader.ts:49rebuildShadowCasters 调用改为经事件/回调注入,或移入 render 侧由加载完成事件触发。
  3. 收紧门禁:环数归零后,运行 node scripts/check-circular.mjs --update-allowlist 确认白名单无幽灵环,并将 check-circular --strict 挂入 check:docs 链(此前因存量环未挂,拆解后应可转正)。
  4. 顺序约束:拆解按「先下沉无依赖叶函数 → 再收敛方向 → 最后挂门禁」推进,每步保持 tsc --noEmit + vitest 全绿。

备选方案

方案评估
A. 仅更新白名单--update-allowlist 把 27 环吞入)❌ 掩盖真实互依赖;环仍存在,运行时隐患与门禁失效照旧——被拒(2026-08-02 审核已定性为「不吞白名单」)
B. 延迟加载绕环(动态 import() 断编译期依赖)⚠️ 可行但引入异步加载路径,renderer.tsclearTextureLRU 是同步清理语义,改造后语义变弱;作为局部兜底保留,非首选
C. 保持现状❌ 27 环常驻,门禁无法转正,架构债无收敛路径

影响

  • frontend/src/scene/render/renderer.tsclearTextureLRU 引用改指下沉模块
  • frontend/src/scene/manager/model-loader.tsrebuildShadowCasters 调用改事件/回调注入或迁至 render 侧
  • frontend/src/scene/manager/model-ops.ts:lighting-follow 引用同步调整
  • 新增 scene/shared/ 目录(或并入既有 core/ 叶模块,按 ADR-191 纯叶子约束)
  • scripts/circular-allowlist.json:环数归零后清理
  • package.jsoncheck:circular --strict 挂入 check:docs
  • 回归:tsc --noEmitvitest 全量、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-093ADR-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.tsscene/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 --noEmit EXIT=0;texture-lru.test.ts 9/9 通过。
  • 门禁转正暂缓:ADR 原决策「环数归零后挂 check-circular 进 check:docs」——当前整体环未归零(21 个其他模块环仍在白名单外),立即挂门禁会让 check:docs 常红;待剩余环消解后转正(ADR-236 范围限定为 render↔manager,其余环需各自立项)。