refactor: slim down EngineManager from 861 to 290 lines by removing passthrough proxy pattern
- EngineManager now only exposes public SDK API (initialize, loadModel, pause/resumeRendering, getEngineComponent, destroy) - Internal managers access Engine component directly via this.engineComponent getter on BaseManager - Non-manager components use registry.engine3d.getEngineComponent() for direct Engine access - Replaced getEngine() with onRawEvent()/offRawEvent() for raw engine event access - Migrated 62 call sites across 13 files (9 managers, 1 panel, 3 toolbar buttons) - Updated all architecture docs, API docs, and README to reflect new patterns
This commit is contained in:
716
docs/引擎API对接.md
716
docs/引擎API对接.md
@@ -1,679 +1,173 @@
|
||||
# Engine API 接口对接指南
|
||||
# Engine API 对接指南(2026-03)
|
||||
|
||||
本文档详细说明 iflow-engine SDK 中 Toolbar 按钮如何调用底层 3D 引擎 API 的完整调用链。
|
||||
本文档说明 SDK 层与底层 `iflow-engine-base` 的最新对接方式。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. `Engine` 组件是底层能力封装层(状态管理 + 参数转换 + 底层调用)
|
||||
2. `EngineManager` 只保留少量对外公共 API,不再做全量透传
|
||||
3. 内部 Manager 直接通过 `this.engineComponent` 调 `Engine`
|
||||
4. 非 Manager 组件通过 `registry.engine3d?.getEngineComponent()` 调 `Engine`
|
||||
5. 不暴露 raw engine 实例,事件订阅走 `onRawEvent/offRawEvent`
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
## 对接层级
|
||||
|
||||
1. [架构概览](#架构概览)
|
||||
2. [调用链层级](#调用链层级)
|
||||
3. [完整调用链示例:剖切盒](#完整调用链示例剖切盒)
|
||||
4. [各层职责说明](#各层职责说明)
|
||||
5. [新增功能对接步骤](#新增功能对接步骤)
|
||||
6. [数据流向](#数据流向)
|
||||
|
||||
---
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 用户交互层 │
|
||||
│ Toolbar Button (src/components/button-group/toolbar/buttons/*) │
|
||||
└────────────────────────────────┬────────────────────────────────────┘
|
||||
│ onClick → registry.xxxManager.show()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 对话框管理层 (DialogManager) │
|
||||
│ src/managers/*-dialog-manager.ts │
|
||||
│ 继承自 BaseDialogManager,管理 UI 面板生命周期 │
|
||||
└────────────────────────────────┬────────────────────────────────────┘
|
||||
│ Panel 回调 → registry.engine3d.xxx()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 引擎管理层 (EngineManager) │
|
||||
│ src/managers/engine-manager.ts │
|
||||
│ 封装 Engine 组件,提供统一 API │
|
||||
└────────────────────────────────┬────────────────────────────────────┘
|
||||
│ 调用 this.engineInstance.xxx()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 引擎组件层 (Engine Component) │
|
||||
│ src/components/engine/index.ts │
|
||||
│ 封装底层 3D 引擎,处理状态管理和数据转换 │
|
||||
└────────────────────────────────┬────────────────────────────────────┘
|
||||
│ 调用 this.engine.xxx.xxx()
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 底层 3D 引擎 (iflow-engine-base) │
|
||||
│ 第三方 SDK,通过 createEngine() 创建实例 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```text
|
||||
L1 UI 层(Button/Panel/Dialog)
|
||||
-> L2 Manager 层(业务编排)
|
||||
-> L3 Engine 组件(统一封装)
|
||||
-> L4 iflow-engine-base(底层能力)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调用链层级
|
||||
## A. 对外 API(EngineManager)
|
||||
|
||||
| 层级 | 文件位置 | 职责 |
|
||||
|------|----------|------|
|
||||
| **L1: Toolbar Button** | `src/components/button-group/toolbar/buttons/` | 定义按钮配置,处理点击事件 |
|
||||
| **L2: DialogManager** | `src/managers/*-dialog-manager.ts` | 管理对话框/面板 UI,绑定回调 |
|
||||
| **L3: EngineManager** | `src/managers/engine-manager.ts` | 封装引擎组件,提供公共 API |
|
||||
| **L4: Engine Component** | `src/components/engine/index.ts` | 封装底层引擎,处理状态和转换 |
|
||||
| **L5: 底层引擎** | `iflow-engine-base` (npm 包) | 实际 3D 渲染和功能实现 |
|
||||
对外稳定入口:`bimEngine.engine`
|
||||
|
||||
---
|
||||
|
||||
## 完整调用链示例:剖切盒
|
||||
|
||||
以剖切盒(Section Box)功能为例,展示完整的调用链:
|
||||
|
||||
### 1. Toolbar Button 定义
|
||||
|
||||
**文件**: `src/components/button-group/toolbar/buttons/section/section-box/index.ts`
|
||||
|
||||
```typescript
|
||||
export const createSectionBoxButton = (): ButtonConfig => {
|
||||
return {
|
||||
id: 'section-box',
|
||||
groupId: 'group-1',
|
||||
parentId: 'section', // 父菜单 ID
|
||||
type: 'button',
|
||||
keepActive: true, // 保持激活状态
|
||||
exclusive: true, // 互斥(同组只能激活一个)
|
||||
label: 'toolbar.sectionBox', // 国际化 key
|
||||
icon: getIcon('剖切盒'),
|
||||
onClick: (button) => {
|
||||
const registry = ManagerRegistry.getInstance();
|
||||
if (button.isActive) {
|
||||
registry.sectionBox?.show(); // ← 调用 DialogManager
|
||||
} else {
|
||||
registry.sectionBox?.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 通过 `ManagerRegistry.getInstance()` 获取全局管理器实例
|
||||
- `button.isActive` 表示当前激活状态
|
||||
- 调用 `registry.sectionBox.show()` 打开对话框
|
||||
|
||||
### 2. DialogManager 处理
|
||||
|
||||
**文件**: `src/managers/section-box-dialog-manager.ts`
|
||||
|
||||
```typescript
|
||||
export class SectionBoxDialogManager extends BaseDialogManager {
|
||||
private panel: SectionBoxPanel | null = null;
|
||||
|
||||
// 创建对话框内容,绑定 Panel 回调
|
||||
protected createContent(): HTMLElement {
|
||||
this.panel = new SectionBoxPanel({
|
||||
onFitToModel: () => {
|
||||
console.log('[SectionBoxDialogManager] Fit to model not supported');
|
||||
},
|
||||
onReset: () => {
|
||||
console.log('[SectionBoxDialogManager] Reset not supported');
|
||||
},
|
||||
onRangeChange: (range) => {
|
||||
this.registry.engine3d?.setSectionBoxRange(range);
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
return this.panel.element;
|
||||
}
|
||||
|
||||
// 对话框创建后,激活剖切盒
|
||||
protected onDialogCreated(): void {
|
||||
this.registry.engine3d?.activeSection('box'); // ← 调用 EngineManager 统一 API
|
||||
this.dialog?.fitHeight(false);
|
||||
}
|
||||
|
||||
// 对话框销毁前,停用剖切
|
||||
protected onBeforeDestroy(): void {
|
||||
this.registry.engine3d?.deactivateSection(); // ← 统一停用方法
|
||||
// ... 清理
|
||||
}
|
||||
|
||||
// 对话框关闭时,取消工具栏按钮激活
|
||||
protected onDialogClose(): void {
|
||||
this.registry.toolbar?.setBtnActive('section-box', false);
|
||||
}
|
||||
```ts
|
||||
interface EngineManagerPublicApi {
|
||||
initialize(options?: Omit<EngineOptions, 'container'>): boolean;
|
||||
isInitialized(): boolean;
|
||||
loadModel(urls: string[], options?: ModelLoadOptions): void;
|
||||
pauseRendering(): void;
|
||||
resumeRendering(): void;
|
||||
getEngineComponent(): Engine | null;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 继承 `BaseDialogManager`,自动获得 `show()/hide()/toggle()` 方法
|
||||
- 通过 `this.registry.engine3d` 访问 EngineManager
|
||||
- Panel 的回调函数中调用 EngineManager 方法
|
||||
- 生命周期钩子:`onDialogCreated` / `onBeforeDestroy` / `onDialogClose`
|
||||
`EngineManager` 内部仍负责:
|
||||
|
||||
### 3. EngineManager 封装
|
||||
- 创建 `Engine` 实例
|
||||
- 创建并持有 `RightKeyManager`
|
||||
- 组装右键菜单逻辑
|
||||
|
||||
**文件**: `src/managers/engine-manager.ts`
|
||||
---
|
||||
|
||||
```typescript
|
||||
export class EngineManager extends BaseManager {
|
||||
private engineInstance: Engine | null = null;
|
||||
## B. 内部 Manager 对接(推荐)
|
||||
|
||||
// 激活剖切(统一入口)
|
||||
public activeSection(mode: 'x' | 'y' | 'z' | 'box' | 'face'): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activeSection(mode); // ← 调用 Engine 组件
|
||||
}
|
||||
`BaseManager` 已提供:
|
||||
|
||||
// 获取当前剖切模式
|
||||
public getCurrentSectionMode(): 'x' | 'y' | 'z' | 'box' | 'face' | null {
|
||||
if (!this.engineInstance) {
|
||||
return null;
|
||||
}
|
||||
return this.engineInstance.getCurrentSectionMode();
|
||||
}
|
||||
|
||||
// 停用剖切(统一出口)
|
||||
public deactivateSection(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.deactivateSection();
|
||||
}
|
||||
|
||||
// 设置剖切盒范围
|
||||
public setSectionBoxRange(range: SectionBoxRange): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.setSectionBoxRange(range);
|
||||
}
|
||||
|
||||
// ... 其他方法
|
||||
}
|
||||
```ts
|
||||
protected get engineComponent(): Engine | null;
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 每个方法都要检查 `engineInstance` 是否存在
|
||||
- 方法签名与 Engine 组件一致,起到代理作用
|
||||
- 负责错误处理和日志输出
|
||||
### 示例:剖切盒
|
||||
|
||||
### 4. Engine 组件实现
|
||||
|
||||
**文件**: `src/components/engine/index.ts`
|
||||
|
||||
```typescript
|
||||
export class Engine implements IBimComponent {
|
||||
private engine: any = null; // 底层引擎实例
|
||||
private currentSectionMode: 'x' | 'y' | 'z' | 'box' | 'face' | null = null; // 当前剖切模式
|
||||
|
||||
// 激活剖切(统一入口)
|
||||
public activeSection(mode: 'x' | 'y' | 'z' | 'box' | 'face'): void {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
console.error('[Engine] Cannot activate section box: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.engine.clipping?.sectionBox) {
|
||||
console.error('[Engine] Section box module not available.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSectionBoxActive) {
|
||||
return; // 幂等操作
|
||||
}
|
||||
|
||||
// 保存模型包围盒(用于百分比计算)
|
||||
this.sectionBoxFullBounds = this.engine.octreeBox?.getBoundingBox()?.clone();
|
||||
|
||||
// 调用底层 API
|
||||
this.engine.clipping.sectionBox.active(); // ← 底层引擎 API
|
||||
this.isSectionBoxActive = true;
|
||||
}
|
||||
|
||||
// 设置剖切盒范围(百分比 → 坐标转换)
|
||||
public setSectionBoxRange(range: SectionBoxRange): void {
|
||||
if (!this.sectionBoxFullBounds) {
|
||||
console.error('[Engine] Cannot set section box range: full bounds not available.');
|
||||
return;
|
||||
}
|
||||
|
||||
const full = this.sectionBoxFullBounds;
|
||||
|
||||
// 百分比转实际坐标
|
||||
const toCoord = (percent: number, min: number, max: number): number => {
|
||||
return min + (max - min) * (percent / 100);
|
||||
};
|
||||
|
||||
const xyz = {
|
||||
minX: toCoord(range.x.min, full.min.x, full.max.x),
|
||||
maxX: toCoord(range.x.max, full.min.x, full.max.x),
|
||||
minY: toCoord(range.y.min, full.min.y, full.max.y),
|
||||
maxY: toCoord(range.y.max, full.min.y, full.max.y),
|
||||
minZ: toCoord(range.z.min, full.min.z, full.max.z),
|
||||
maxZ: toCoord(range.z.max, full.min.z, full.max.z),
|
||||
};
|
||||
|
||||
// 调用底层 API
|
||||
this.engine.clipping.sectionBox.setboxXyz(xyz); // ← 底层引擎 API
|
||||
}
|
||||
}
|
||||
```ts
|
||||
this.engineComponent?.activeSection('box');
|
||||
this.engineComponent?.setSectionBoxRange(range);
|
||||
this.engineComponent?.deactivateSection();
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 维护功能状态(`isSectionBoxActive`)
|
||||
- 缓存必要数据(`sectionBoxFullBounds`)
|
||||
- 进行数据转换(百分比 → 坐标)
|
||||
- 调用底层引擎的实际 API
|
||||
### 示例:漫游
|
||||
|
||||
### 5. 底层引擎 API
|
||||
|
||||
**来源**: `iflow-engine-base` (第三方 SDK)
|
||||
|
||||
```typescript
|
||||
// 剖切盒相关 API
|
||||
engine.clipping.sectionBox.active() // 激活剖切盒
|
||||
engine.clipping.sectionBox.disActive() // 停用剖切盒
|
||||
engine.clipping.sectionBox.getboxXyz() // 获取范围 { minX, maxX, minY, maxY, minZ, maxZ }
|
||||
engine.clipping.sectionBox.setboxXyz(xyz) // 设置范围
|
||||
engine.clipping.sectionBox.setBox(box) // 设置 THREE.Box3 格式范围
|
||||
engine.clipping.sectionBox.reverseBox() // 反向剖切(当前为空实现)
|
||||
|
||||
// 获取模型包围盒
|
||||
engine.octreeBox.getBoundingBox() // 返回 THREE.Box3
|
||||
```ts
|
||||
this.engineComponent?.activateFirstPersonMode();
|
||||
this.engineComponent?.setWalkSpeed(speed);
|
||||
this.engineComponent?.deactivateFirstPersonMode();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 各层职责说明
|
||||
## C. 非 Manager 组件对接
|
||||
|
||||
### L1: Toolbar Button
|
||||
适用于 toolbar 按钮、独立组件(如 `WalkPathPanel`):
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 定义按钮配置 | `id`, `label`, `icon`, `groupId`, `parentId` 等 |
|
||||
| 处理点击事件 | 在 `onClick` 中调用 DialogManager 的 `show()`/`hide()` |
|
||||
| 控制激活状态 | `keepActive`, `exclusive` 等属性控制按钮行为 |
|
||||
|
||||
### L2: DialogManager
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 管理对话框生命周期 | `show()` / `hide()` / `toggle()` |
|
||||
| 创建 UI 面板 | 在 `createContent()` 中实例化 Panel 组件 |
|
||||
| 绑定回调函数 | 将 Panel 的事件回调连接到 EngineManager |
|
||||
| 处理生命周期钩子 | `onDialogCreated` / `onBeforeDestroy` / `onDialogClose` |
|
||||
| 同步工具栏状态 | 关闭时调用 `toolbar.setBtnActive(id, false)` |
|
||||
|
||||
### L3: EngineManager
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 代理 Engine 组件 | 提供统一的公共 API |
|
||||
| 检查初始化状态 | 每个方法都检查 `engineInstance` 是否存在 |
|
||||
| 错误处理 | 输出警告日志 |
|
||||
| 暴露给 Registry | 通过 `registry.engine3d` 访问 |
|
||||
|
||||
### L4: Engine Component
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 封装底层引擎 | 隔离第三方 SDK 的具体实现 |
|
||||
| 状态管理 | 维护 `isSectionBoxActive` 等状态标记 |
|
||||
| 数据转换 | UI 层数据格式 ↔ 底层引擎数据格式 |
|
||||
| 缓存数据 | 保存 `sectionBoxFullBounds` 等中间数据 |
|
||||
| 幂等操作 | 防止重复激活/停用 |
|
||||
|
||||
### L5: 底层引擎
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 3D 渲染 | WebGL/Three.js 渲染 |
|
||||
| 功能实现 | 测量、剖切、漫游等实际功能 |
|
||||
| 场景管理 | 模型加载、相机控制等 |
|
||||
```ts
|
||||
registry.engine3d?.getEngineComponent()?.CameraGoHome();
|
||||
registry.engine3d?.getEngineComponent()?.activateZoomBox();
|
||||
registry.engine3d?.getEngineComponent()?.toggleMiniMap();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新增功能对接步骤
|
||||
## D. 原始事件对接(替代 getEngine)
|
||||
|
||||
以新增一个 "XX 功能" 为例:
|
||||
`Engine.getEngine()` 已删除,改为事件桥接:
|
||||
|
||||
### Step 1: 创建 Toolbar Button
|
||||
|
||||
**文件**: `src/components/button-group/toolbar/buttons/xx/index.ts`
|
||||
|
||||
```typescript
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import { getIcon } from '../../../../../utils/icon-manager';
|
||||
import { ManagerRegistry } from '../../../../../core/manager-registry';
|
||||
|
||||
export const createXxButton = (): ButtonConfig => {
|
||||
return {
|
||||
id: 'xx-feature',
|
||||
groupId: 'group-1',
|
||||
type: 'button',
|
||||
keepActive: true,
|
||||
label: 'toolbar.xxFeature',
|
||||
icon: getIcon('XX图标'),
|
||||
onClick: (button) => {
|
||||
const registry = ManagerRegistry.getInstance();
|
||||
if (button.isActive) {
|
||||
registry.xxFeature?.show();
|
||||
} else {
|
||||
registry.xxFeature?.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
```ts
|
||||
engineComponent?.onRawEvent('measure-changed', handler);
|
||||
engineComponent?.offRawEvent('measure-changed', handler);
|
||||
```
|
||||
|
||||
### Step 2: 注册按钮到 Toolbar
|
||||
适用场景:
|
||||
|
||||
**文件**: `src/components/button-group/toolbar/index.ts`
|
||||
- 测量回调(`measure-changed` / `measure-click`)
|
||||
- 剖切盒拖动回调(`section-move`)
|
||||
|
||||
```typescript
|
||||
// 添加导入
|
||||
const { createXxButton } = await import('./buttons/xx');
|
||||
---
|
||||
|
||||
// 添加按钮
|
||||
this.addButton(createXxButton());
|
||||
```
|
||||
## E. 新功能对接步骤(最新模板)
|
||||
|
||||
### Step 3: 创建 DialogManager
|
||||
### Step 1: 在 Engine 组件实现能力
|
||||
|
||||
**文件**: `src/managers/xx-dialog-manager.ts`
|
||||
文件:`src/components/engine/index.ts`
|
||||
|
||||
```typescript
|
||||
import { BaseDialogManager } from '../core/base-dialog-manager';
|
||||
import { XxPanel } from '../components/xx-panel';
|
||||
|
||||
export class XxDialogManager extends BaseDialogManager {
|
||||
private panel: XxPanel | null = null;
|
||||
|
||||
protected get dialogId(): string {
|
||||
return 'xx-dialog';
|
||||
}
|
||||
|
||||
protected get dialogTitle(): string {
|
||||
return 'xxFeature.dialogTitle';
|
||||
}
|
||||
|
||||
protected createContent(): HTMLElement {
|
||||
this.panel = new XxPanel({
|
||||
onSomeAction: () => {
|
||||
this.registry.engine3d?.doSomething();
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
return this.panel.element;
|
||||
}
|
||||
|
||||
protected onDialogCreated(): void {
|
||||
this.registry.engine3d?.activateXx();
|
||||
}
|
||||
|
||||
protected onBeforeDestroy(): void {
|
||||
this.registry.engine3d?.deactivateXx();
|
||||
this.panel?.destroy();
|
||||
this.panel = null;
|
||||
}
|
||||
|
||||
protected onDialogClose(): void {
|
||||
this.registry.toolbar?.setBtnActive('xx-feature', false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: 注册到 ManagerRegistry
|
||||
|
||||
**文件**: `src/core/manager-registry.ts`
|
||||
|
||||
```typescript
|
||||
// 添加类型导入
|
||||
import type { XxDialogManager } from '../managers/xx-dialog-manager';
|
||||
|
||||
// 添加属性
|
||||
public xxFeature: XxDialogManager | null = null;
|
||||
```
|
||||
|
||||
### Step 5: 在 BimEngine 中初始化
|
||||
|
||||
**文件**: `src/bim-engine.ts`
|
||||
|
||||
```typescript
|
||||
// 添加导入
|
||||
import { XxDialogManager } from './managers/xx-dialog-manager';
|
||||
|
||||
// 添加属性
|
||||
public xxFeature: XxDialogManager | null = null;
|
||||
|
||||
// 在 init() 中初始化
|
||||
this.xxFeature = new XxDialogManager();
|
||||
this.registry.xxFeature = this.xxFeature;
|
||||
|
||||
// 在 destroy() 中销毁
|
||||
this.xxFeature?.destroy();
|
||||
```
|
||||
|
||||
### Step 6: 在 EngineManager 中添加方法
|
||||
|
||||
**文件**: `src/managers/engine-manager.ts`
|
||||
|
||||
```typescript
|
||||
```ts
|
||||
public activateXx(): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activateXx();
|
||||
if (!this._isInitialized || !this.engine?.xxModule) return;
|
||||
this.engine.xxModule.active();
|
||||
}
|
||||
|
||||
public deactivateXx(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.deactivateXx();
|
||||
}
|
||||
|
||||
public doSomething(): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.doSomething();
|
||||
if (!this._isInitialized || !this.engine?.xxModule) return;
|
||||
this.engine.xxModule.disActive();
|
||||
}
|
||||
```
|
||||
|
||||
### Step 7: 在 Engine 组件中实现
|
||||
### Step 2: 在 Manager 中调用(不要新增 EngineManager 透传)
|
||||
|
||||
**文件**: `src/components/engine/index.ts`
|
||||
文件:`src/managers/xx-dialog-manager.ts`
|
||||
|
||||
```typescript
|
||||
private isXxActive: boolean = false;
|
||||
|
||||
public activateXx(): void {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
console.error('[Engine] Cannot activate XX: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isXxActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用底层 API
|
||||
this.engine.xxModule.active();
|
||||
this.isXxActive = true;
|
||||
```ts
|
||||
protected onDialogCreated(): void {
|
||||
this.engineComponent?.activateXx();
|
||||
}
|
||||
|
||||
public deactivateXx(): void {
|
||||
if (!this._isInitialized || !this.engine?.xxModule) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isXxActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.engine.xxModule.disActive();
|
||||
this.isXxActive = false;
|
||||
}
|
||||
|
||||
public doSomething(): void {
|
||||
if (!this._isInitialized || !this.engine?.xxModule) {
|
||||
return;
|
||||
}
|
||||
this.engine.xxModule.doSomething();
|
||||
protected onBeforeDestroy(): void {
|
||||
this.engineComponent?.deactivateXx();
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: 若是非 Manager 组件,走 getEngineComponent
|
||||
|
||||
```ts
|
||||
registry.engine3d?.getEngineComponent()?.activateXx();
|
||||
```
|
||||
|
||||
### Step 4: 仅当需要“对外 SDK 公共 API”时,再考虑给 EngineManager 增加方法
|
||||
|
||||
判定标准:
|
||||
|
||||
- 该能力是否需要 `bimEngine.engine?.xxx()` 供外部用户直接调用
|
||||
- 若仅为内部链路使用,不应在 `EngineManager` 再包一层
|
||||
|
||||
---
|
||||
|
||||
## 数据流向
|
||||
## F. 常见反模式
|
||||
|
||||
### 用户操作 → 底层引擎
|
||||
- ❌ 在 `EngineManager` 里新增大批 `this.engineInstance?.xxx()` 透传
|
||||
- ❌ 内部 Manager 继续写 `this.registry.engine3d?.xxx()`
|
||||
- ❌ 通过 `Engine.getEngine()` 暴露 raw engine
|
||||
|
||||
```
|
||||
用户点击按钮
|
||||
↓
|
||||
Button.onClick(button)
|
||||
↓
|
||||
registry.xxxManager.show()
|
||||
↓
|
||||
BaseDialogManager.show()
|
||||
↓
|
||||
createContent() → 创建 Panel
|
||||
↓
|
||||
onDialogCreated() → registry.engine3d.activateXxx()
|
||||
↓
|
||||
EngineManager.activateXxx()
|
||||
↓
|
||||
Engine.activateXxx()
|
||||
↓
|
||||
this.engine.xxxModule.active() ← 底层引擎 API
|
||||
```
|
||||
对应正确方式:
|
||||
|
||||
### UI 面板操作 → 底层引擎
|
||||
|
||||
```
|
||||
用户操作 Panel(拖动滑块、点击按钮等)
|
||||
↓
|
||||
Panel 回调触发 onXxxChange(data)
|
||||
↓
|
||||
DialogManager 中的回调:registry.engine3d.setXxx(data)
|
||||
↓
|
||||
EngineManager.setXxx(data)
|
||||
↓
|
||||
Engine.setXxx(data)
|
||||
↓
|
||||
数据转换(如百分比 → 坐标)
|
||||
↓
|
||||
this.engine.xxxModule.setXxx(convertedData) ← 底层引擎 API
|
||||
```
|
||||
|
||||
### 关闭对话框
|
||||
|
||||
```
|
||||
用户点击关闭按钮 / 再次点击工具栏按钮
|
||||
↓
|
||||
BimDialog.onClose() / Button.onClick()
|
||||
↓
|
||||
BaseDialogManager.hide() / registry.xxxManager.hide()
|
||||
↓
|
||||
destroyDialog()
|
||||
↓
|
||||
onBeforeDestroy() → registry.engine3d.deactivateXxx()
|
||||
↓
|
||||
EngineManager.deactivateXxx()
|
||||
↓
|
||||
Engine.deactivateXxx()
|
||||
↓
|
||||
this.engine.xxxModule.disActive() ← 底层引擎 API
|
||||
↓
|
||||
onDialogClose() → registry.toolbar.setBtnActive('xxx', false)
|
||||
```
|
||||
- ✅ 内部 Manager 使用 `this.engineComponent?.xxx()`
|
||||
- ✅ 非 Manager 使用 `registry.engine3d?.getEngineComponent()?.xxx()`
|
||||
- ✅ 原始事件使用 `onRawEvent/offRawEvent`
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
## G. 验收清单
|
||||
|
||||
### Q1: 如何确保激活/停用的幂等性?
|
||||
重构或新增功能后,至少检查:
|
||||
|
||||
在 Engine 组件中维护状态标记,在方法开头检查:
|
||||
|
||||
```typescript
|
||||
public activateSectionBox(): void {
|
||||
// ...
|
||||
if (this.isSectionBoxActive) {
|
||||
console.log('[Engine] Section box already active, skipping.');
|
||||
return; // 幂等:已激活则跳过
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Q2: 如何处理底层 API 不支持的功能?
|
||||
|
||||
在 DialogManager 的回调中输出日志说明:
|
||||
|
||||
```typescript
|
||||
onReverseToggle: (isReversed) => {
|
||||
// 底层暂不支持反向功能
|
||||
console.log('[SectionBoxDialogManager] 反向切换(底层暂不支持):', isReversed);
|
||||
}
|
||||
```
|
||||
|
||||
### Q3: 如何进行数据格式转换?
|
||||
|
||||
在 Engine 组件中进行,不要在 DialogManager 或 EngineManager 中:
|
||||
|
||||
```typescript
|
||||
// Engine 组件中
|
||||
public setSectionBoxRange(range: SectionBoxRange): void {
|
||||
// UI 层:百分比 (0-100)
|
||||
// 底层:实际坐标
|
||||
|
||||
const toCoord = (percent: number, min: number, max: number): number => {
|
||||
return min + (max - min) * (percent / 100);
|
||||
};
|
||||
|
||||
const xyz = {
|
||||
minX: toCoord(range.x.min, full.min.x, full.max.x),
|
||||
// ...
|
||||
};
|
||||
|
||||
this.engine.clipping.sectionBox.setboxXyz(xyz);
|
||||
}
|
||||
```
|
||||
|
||||
### Q4: 如何访问其他 Manager?
|
||||
|
||||
通过 `ManagerRegistry` 单例访问:
|
||||
|
||||
```typescript
|
||||
// 在 Button 中
|
||||
const registry = ManagerRegistry.getInstance();
|
||||
registry.sectionBox?.show();
|
||||
|
||||
// 在 DialogManager 中(继承自 BaseManager,自动获得 this.registry)
|
||||
this.registry.engine3d?.activateSectionBox();
|
||||
this.registry.toolbar?.setBtnActive('section-box', false);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 层级 | 关注点 |
|
||||
|------|--------|
|
||||
| **Button** | 按钮配置、点击事件、调用 DialogManager |
|
||||
| **DialogManager** | UI 生命周期、Panel 回调绑定、调用 EngineManager |
|
||||
| **EngineManager** | API 代理、初始化检查、错误处理 |
|
||||
| **Engine** | 状态管理、数据转换、调用底层 API |
|
||||
| **底层引擎** | 实际功能实现 |
|
||||
|
||||
遵循这个分层架构,可以保持代码的清晰性和可维护性。
|
||||
1. `EngineManager` 是否只保留必要公共 API
|
||||
2. Manager 层是否统一走 `engineComponent`
|
||||
3. 是否无 `getEngine()` 调用
|
||||
4. 事件订阅是否成对 `onRawEvent/offRawEvent`
|
||||
5. `npm run build` 是否通过
|
||||
|
||||
Reference in New Issue
Block a user