Files
bim_engine/.sisyphus/drafts/framework-audit-report.md
yuding 4a09d52283 feat(clipping): implement hide/recover toggle for all section dialogs
Update all three section dialogs to support hide/show toggle:

SectionAxisDialogManager:
- onHideToggle now calls hideSection()/recoverSection()

SectionBoxDialogManager:
- onHideToggle now calls hideSection()/recoverSection()

SectionPlanePanel:
- Add isHidden state tracking
- Change onHide to onHideToggle(isHidden)
- Add setHiddenState/getHiddenState methods
- Update button to toggle active state

SectionPlaneDialogManager:
- Switch to onHideToggle callback
- Call hideSection()/recoverSection() based on toggle state

Behavior: Click hide button to hide section, click again to recover.
2026-02-02 16:36:17 +08:00

12 KiB
Raw Permalink Blame History

iflow-engine 框架架构审核(代码 + 文档)

日期2026-01-29

范围

本审核仅评估 iflow-engine SDK 的内部框架/架构设计,依据:

  • src/ 下的源代码
  • docs/ 下的架构与模块文档

不在本次范围内:

  • 功能完整性
  • UI/交互体验优劣
  • 重构路线/落地方案/实现细节(按你的要求不输出)

已确认的前提(来自你的要求):

  • 保留 ManagerRegistry 作为核心设计(单例 + 服务定位器)。
  • SDK 需要支持同一页面多个 viewer 实例。
  • 主题/语言是全局共享。
  • 事件应当按 BimEngine 实例隔离。
  • Components 设计上应当严格纯 UI不触达 registry、不发布全局事件。
  • Components 也不应直接订阅/读取全局主题/国际化服务theme/locale 应由 manager 注入或推送更新)。

严重级别说明

  • Critical致命:架构层面存在矛盾或极易引发跨实例破坏。
  • High:高概率造成长期维护痛点/难追踪 bug。
  • Medium:明显增加认知负担或存在非小风险的不一致。
  • Low:质量/风格问题,非结构性,但建议修整。

发现的问题清单

1) Critical多实例事件隔离诉求与全局单例事件总线结构矛盾

框架把所有事件集中在 ManagerRegistry 内部的一个全局 EventEmitter 上。 这从结构上决定了:多个 BimEngine 实例之间无法做到事件天然隔离。

证据:

  • src/core/manager-registry.ts:31-38private eventEmitter: EventEmitter = new EventEmitter()
  • src/core/manager-registry.ts:95-127emit/on/off 都委托给这一个 emitter
  • src/core/base-manager.ts:19-34(所有 Manager 的订阅都通过单例 registry 完成)

影响:

  • 多个 viewer 共存时,任一实例发出的事件都可能被所有订阅者收到。
  • 事件域是全局的,不是 per-engine 的;如果要隔离,必须在别处“人为做隔离”,但当前框架没有内建边界。

2) Critical销毁任一实例会重置全局 registry破坏其他实例

BimEngine.destroy() 会调用 ManagerRegistry.reset():清空事件监听并把单例置空。 当页面存在多个引擎实例时,销毁其中一个就可能让其他实例失效。

证据:

  • src/bim-engine.ts:150-164(调用 ManagerRegistry.reset()
  • src/core/manager-registry.ts:82-88reset()clear() emitter 并 instance = null

影响:

  • 跨实例“断电式”故障;而且故障源头非局部,排查成本高。

3) High全局 registry 存放实例态container/wrapper/各类 manager 实例)

ManagerRegistry 不只是 locator它持有 container/wrapper DOM 节点和大量 manager 实例。 这使它变成一个全局可变的“状态袋”。

证据:

  • src/core/manager-registry.ts:35-70container/wrapper 与各 manager 字段)
  • src/bim-engine.ts:101-136(把容器与所有 manager 写入单例)

影响:

  • 多实例情况下,“最后初始化的实例”会覆盖 registry.container/wrapper 与 manager 引用。
  • 排查问题时必须追踪“最后是谁写入了全局状态”。

4) High服务定位器边界未被约束叶子层大量直接 getInstance

框架想表达分层,但叶子层代码频繁直接调用 ManagerRegistry.getInstance()。 这会扩大隐式依赖并增加跳转/追踪成本。

证据(示例,不完全):

  • src/components/button-group/toolbar/buttons/home/index.ts:15-16
  • src/components/button-group/toolbar/buttons/measure/index.ts:14-18
  • src/components/button-group/toolbar/buttons/map/index.ts:8-28
  • src/components/button-group/index.ts:65-68(组件通过单例 registry 发事件)
  • src/components/engine/index.ts:131-144Engine 组件通过单例 registry 发事件)
  • src/managers/engine-manager.ts:64-68(即便继承 BaseManager,回调里仍 getInstance()

影响:

  • 依赖关系在 API/构造函数层面不可见,只能靠全局搜索发现。
  • 实际改动往往需要跨层与跨全局状态跳转。

5) High组件层通过 registry 直接参与框架通信,削弱了文档里的分层承诺

文档里写明“组件不直接依赖 Manager由 Manager 创建和管理组件实例”。 但代码中组件通过全局 registry 发布事件。

证据:

  • 文档承诺:docs/MODULES/组件模块.md:595-602
  • 反例:src/components/engine/index.ts:131-144ManagerRegistry.getInstance().emit(...)
  • 反例:src/components/button-group/index.ts:65-68(组件通过 registry 发事件)

影响:

  • 组件层通过全局 locator 变得“半业务化/半框架化”。
  • 耦合上升,测试与复用更困难。

6) HighRightKeyManager 出现重复创建/归属不清

右键管理器至少在两处被创建。 这会导致职责重复,并引入“到底谁是权威实例”的歧义。

证据:

  • src/bim-engine.ts:108this.rightKey = new RightKeyManager(this.wrapper)
  • src/managers/engine-manager.ts:50-51this.rightKey = new RightKeyManager(this.container)

影响:

  • 更高概率出现重复监听、hide/show 不一致、销毁责任不清。

7) HighEngine 组件依赖仓库相对路径的底层引擎产物(工程耦合)

代码没有从外部依赖(iflow-engine-base)导入,而是通过深层相对路径引入本仓库的 dist 文件。 这会把构建/运行绑定到特定仓库布局。

证据:

  • src/components/engine/index.ts:8-11(从 ../../../../bim_engine_base/dist/... 导入 createEngine

影响:

  • CI/发布构建对目录结构高度敏感。
  • 线上发布包与本地开发行为可能不一致。

8) MediumBimEngine 订阅主题变化但未保存取消订阅句柄

BimEngine.init()themeManager 订阅后没有保存返回的 unsubscribe

证据:

  • src/bim-engine.ts:139-142themeManager.subscribe(...) 返回值未保存)

影响:

  • 在 SPA 场景反复创建/销毁引擎时可能累积订阅,形成隐性泄漏。

9) Medium对话框定位依赖全局 registry.container非实例局部上下文

BaseDialogManager 的默认位置计算基于 this.registry.container。 多实例时这隐含了“全局只有一个有效 container”。

证据:

  • src/core/base-dialog-manager.ts:60-73(读取 this.registry.container 计算位置)

影响:

  • 多实例下对话框可能相对“最后写入 registry 的容器”定位,表现不一致。

10) Medium分层调用链本身会导致“改动面扩大”层级跳转多

文档明确了 5 层调用链Button → DialogManager → EngineManager → Engine 组件 → 底层引擎)。 哪怕每层都写得干净,累计的间接层也会扩大改动时的触达面。

证据:

  • docs/API调用链.md:714-748(层级说明)
  • docs/引擎API对接.md:18-66(架构概览与层级表)

影响:

  • 一个功能的内部改动往往需要同步修改 3-5 个位置。
  • 这会被外部感知为“框架复杂”,即使逻辑上自洽。

11) Medium构建配置禁用代码分割可能削弱 ESM tree-shaking 效果)

库构建使用 inlineDynamicImports: true,强制输出单文件 bundle。

证据:

  • vite.config.ts:26-38

影响:

  • 使用方更难获得按需使用/裁剪的收益。
  • 打包策略会进一步强化“框架很重”的印象。

12) LowEngine.setTheme 为空实现(抽象与实现不一致)

Engine 组件暴露了 setTheme,但实现为空。

证据:

  • src/components/engine/index.ts:153-160

影响:

  • 虽然不是典型“架构问题”,但会加重抽象与行为不一致,长期会影响信任度与维护效率。

文档一致性备注

整体上文档很完整、意图也清晰(门面 + registry + 分层)。主要不一致/落差集中在:

  • 一些分层边界(如“组件不依赖 Manager”在代码里被全局 registry 使用削弱。
  • “多实例 + 事件隔离”的需求与当前“全局单例事件域”存在硬冲突。

维护成本视角的客观评价(你选的侧重点)

这里把“维护成本”拆成三类:可读性(读懂/定位)、可改动性(改一个点波及范围)、可测试性(写测试/隔离依赖的难度)。

A. 可读性(读懂/定位)

结论:中等偏高的认知负担。架构意图清晰,但“全局可取用 registry”让依赖与调用链变得隐式。

主要原因(按影响排序):

  • 隐式依赖:叶子层普遍 ManagerRegistry.getInstance(),使得“一个文件真正依赖哪些子系统”无法从构造函数/参数判断,只能靠全局搜索。
    • 证据:src/components/button-group/toolbar/buttons/home/index.ts:15-16src/components/button-group/toolbar/buttons/measure/index.ts:14-18src/components/button-group/toolbar/buttons/map/index.ts:8-28
  • 状态来源不集中registry 同时存放 DOM 容器(container/wrapper)与大量 manager 实例,读代码时需要先搞清楚“谁在什么时候写入了 registry”。
    • 证据:src/core/manager-registry.ts:35-70src/bim-engine.ts:95-136
  • 层级跳转多且链路分散你在文档里明确分层L1-L5这使新同学能理解“应该去哪里找”但也意味着定位一个行为经常要横跨 3-5 个文件。
    • 证据:docs/引擎API对接.md:18-66docs/API调用链.md:714-748

B. 可改动性(改一个点波及范围)

结论:改动面容易扩大,尤其是新增/调整一个功能时,按规范往往需要在多个层同时补齐。

主要原因:

  • 强制的多层落点:文档对“新增功能对接步骤”规定了 Button→Toolbar→DialogManager→Registry→BimEngine init→EngineManager→Engine 组件的连锁改动;这对一致性有好处,但会让小需求也要跨层改多个位置。
    • 证据:docs/引擎API对接.md:326-517Step 1-7
  • 全局注册表字段扩散:新增一个 manager 往往意味着在 registry 增字段、在 BimEngine 填充引用随着功能增长registry 越来越像“全局大对象”,改动与回归风险随之增加。
    • 证据:docs/引擎API对接.md:416-446src/core/manager-registry.ts:35-70src/bim-engine.ts:120-136
  • 重复实例/归属不清会放大改动成本:同一能力被多处创建/持有,会导致改动时需要同时修改多个入口,且更容易漏。
    • 证据:src/bim-engine.ts:108src/managers/engine-manager.ts:50-51

C. 可测试性(隔离依赖/写单测的难度)

结论:偏难(不是因为 TypeScript/DOM而是因为全局单例与隐式依赖

主要原因:

  • 全局单例 + 全局事件域:测试用例之间需要非常谨慎地清理/隔离 registry 状态与事件订阅,否则容易互相污染。
    • 证据:src/core/manager-registry.ts:31-38src/core/manager-registry.ts:82-88
  • 组件/manager 直接触达全局状态:例如 Engine 组件在底层事件回调里直接拿单例 registry 发事件,使得组件测试必须考虑全局副作用。
    • 证据:src/components/engine/index.ts:131-144
  • 订阅/事件监听的释放不完全:存在订阅未保存/未释放的迹象,会导致测试环境(或 SPA出现隐性泄漏与用例间干扰。
    • 证据:src/bim-engine.ts:139-142src/components/button-group/index.ts:97-112src/components/button-group/index.ts:804-817

维护成本总结(一句话)

你的架构“按分层组织代码、让功能落点清晰”的方向是对的;维护成本主要来自 registry 单例让依赖与状态隐式化,再叠加 多层落点规范少量重复实例/资源释放不全,使得定位、改动、测试都会更依赖经验与全局搜索。