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.
231 lines
12 KiB
Markdown
231 lines
12 KiB
Markdown
# 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-38`(`private eventEmitter: EventEmitter = new EventEmitter()`)
|
||
- `src/core/manager-registry.ts:95-127`(`emit/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-88`(`reset()` 会 `clear()` emitter 并 `instance = null`)
|
||
|
||
影响:
|
||
- 跨实例“断电式”故障;而且故障源头非局部,排查成本高。
|
||
|
||
### 3) High:全局 registry 存放实例态(container/wrapper/各类 manager 实例)
|
||
|
||
`ManagerRegistry` 不只是 locator:它持有 `container/wrapper` DOM 节点和大量 manager 实例。
|
||
这使它变成一个全局可变的“状态袋”。
|
||
|
||
证据:
|
||
- `src/core/manager-registry.ts:35-70`(`container`/`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-144`(Engine 组件通过单例 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-144`(`ManagerRegistry.getInstance().emit(...)`)
|
||
- 反例:`src/components/button-group/index.ts:65-68`(组件通过 registry 发事件)
|
||
|
||
影响:
|
||
- 组件层通过全局 locator 变得“半业务化/半框架化”。
|
||
- 耦合上升,测试与复用更困难。
|
||
|
||
### 6) High:RightKeyManager 出现重复创建/归属不清
|
||
|
||
右键管理器至少在两处被创建。
|
||
这会导致职责重复,并引入“到底谁是权威实例”的歧义。
|
||
|
||
证据:
|
||
- `src/bim-engine.ts:108`(`this.rightKey = new RightKeyManager(this.wrapper)`)
|
||
- `src/managers/engine-manager.ts:50-51`(`this.rightKey = new RightKeyManager(this.container)`)
|
||
|
||
影响:
|
||
- 更高概率出现重复监听、hide/show 不一致、销毁责任不清。
|
||
|
||
### 7) High:Engine 组件依赖仓库相对路径的底层引擎产物(工程耦合)
|
||
|
||
代码没有从外部依赖(`iflow-engine-base`)导入,而是通过深层相对路径引入本仓库的 dist 文件。
|
||
这会把构建/运行绑定到特定仓库布局。
|
||
|
||
证据:
|
||
- `src/components/engine/index.ts:8-11`(从 `../../../../bim_engine_base/dist/...` 导入 `createEngine`)
|
||
|
||
影响:
|
||
- CI/发布构建对目录结构高度敏感。
|
||
- 线上发布包与本地开发行为可能不一致。
|
||
|
||
### 8) Medium:BimEngine 订阅主题变化但未保存取消订阅句柄
|
||
|
||
`BimEngine.init()` 对 `themeManager` 订阅后没有保存返回的 `unsubscribe`。
|
||
|
||
证据:
|
||
- `src/bim-engine.ts:139-142`(`themeManager.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) Low:Engine.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-16`、`src/components/button-group/toolbar/buttons/measure/index.ts:14-18`、`src/components/button-group/toolbar/buttons/map/index.ts:8-28`
|
||
- **状态来源不集中**:registry 同时存放 DOM 容器(`container/wrapper`)与大量 manager 实例,读代码时需要先搞清楚“谁在什么时候写入了 registry”。
|
||
- 证据:`src/core/manager-registry.ts:35-70`、`src/bim-engine.ts:95-136`
|
||
- **层级跳转多且链路分散**:你在文档里明确分层(L1-L5),这使新同学能理解“应该去哪里找”,但也意味着定位一个行为经常要横跨 3-5 个文件。
|
||
- 证据:`docs/引擎API对接.md:18-66`、`docs/API调用链.md:714-748`
|
||
|
||
### B. 可改动性(改一个点波及范围)
|
||
|
||
结论:**改动面容易扩大**,尤其是新增/调整一个功能时,按规范往往需要在多个层同时补齐。
|
||
|
||
主要原因:
|
||
- **强制的多层落点**:文档对“新增功能对接步骤”规定了 Button→Toolbar→DialogManager→Registry→BimEngine init→EngineManager→Engine 组件的连锁改动;这对一致性有好处,但会让小需求也要跨层改多个位置。
|
||
- 证据:`docs/引擎API对接.md:326-517`(Step 1-7)
|
||
- **全局注册表字段扩散**:新增一个 manager 往往意味着在 registry 增字段、在 BimEngine 填充引用;随着功能增长,registry 越来越像“全局大对象”,改动与回归风险随之增加。
|
||
- 证据:`docs/引擎API对接.md:416-446`、`src/core/manager-registry.ts:35-70`、`src/bim-engine.ts:120-136`
|
||
- **重复实例/归属不清会放大改动成本**:同一能力被多处创建/持有,会导致改动时需要同时修改多个入口,且更容易漏。
|
||
- 证据:`src/bim-engine.ts:108`、`src/managers/engine-manager.ts:50-51`
|
||
|
||
### C. 可测试性(隔离依赖/写单测的难度)
|
||
|
||
结论:**偏难**(不是因为 TypeScript/DOM,而是因为全局单例与隐式依赖)。
|
||
|
||
主要原因:
|
||
- **全局单例 + 全局事件域**:测试用例之间需要非常谨慎地清理/隔离 registry 状态与事件订阅,否则容易互相污染。
|
||
- 证据:`src/core/manager-registry.ts:31-38`、`src/core/manager-registry.ts:82-88`
|
||
- **组件/manager 直接触达全局状态**:例如 Engine 组件在底层事件回调里直接拿单例 registry 发事件,使得组件测试必须考虑全局副作用。
|
||
- 证据:`src/components/engine/index.ts:131-144`
|
||
- **订阅/事件监听的释放不完全**:存在订阅未保存/未释放的迹象,会导致测试环境(或 SPA)出现隐性泄漏与用例间干扰。
|
||
- 证据:`src/bim-engine.ts:139-142`、`src/components/button-group/index.ts:97-112`、`src/components/button-group/index.ts:804-817`
|
||
|
||
### 维护成本总结(一句话)
|
||
|
||
你的架构“按分层组织代码、让功能落点清晰”的方向是对的;维护成本主要来自 **registry 单例让依赖与状态隐式化**,再叠加 **多层落点规范** 与 **少量重复实例/资源释放不全**,使得定位、改动、测试都会更依赖经验与全局搜索。
|