概述
本文档分析了 Pacific Vis 2025 可视化项目中使用的状态管理和事件系统模式,并将其与传统的 Flux/Redux 架构模式进行比较。
Pacific Vis 2025 中的状态管理架构
1. 状态定义模式
项目采用状态优先方法,定义了明确的状态类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export const STATE_TYPE = { NORMAL: 'normal', EMPHASIS: 'emphasis', BLUR: 'blur', SELECTED: 'selected', UNSELECTED: 'unselected', OCCLUDED: 'occluded', } as const;
interface StateDefinition<M extends MeshBasicMaterial = MeshBasicMaterial> { [k: string]: unknown; material?: Partial<{ [K in keyof M]: M[K] }>; }
|
关键特征:
- 基于枚举的状态系统,使用明确的状态常量
- 基于材质的状态应用,状态直接修改 Three.js 材质属性
- 层次化状态继承,从基类到具体元素
- 上下文感知的状态管理,支持动态状态计算
2. 元素级状态管理
每个可视化元素通过三态系统维护自己的状态:
1 2 3 4 5 6
| class BaseElement { private __isHighlight: -1 | 0 | 1 = 0; private __isSelected: -1 | 0 | 1 = 0; private __isOccupied: boolean = false; private __isDirty = false; }
|
状态值:
1 = 激活/启用/强调
0 = 正常/中性状态
-1 = 禁用/隐藏/淡化
3. 状态转换逻辑
状态转换由控制器驱动,具有确定性的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| protected _applyElementStates(elem: IElement, stage: IStage): void { const isSelected = elem.isSelected() || 0; const isHighlight = elem.isHighlight() || 0; const isOccluded = elem.isOccluded() || false;
let stateName: (typeof STATE_TYPE)[keyof typeof STATE_TYPE];
if (isSelected === 1) stateName = STATE_TYPE.SELECTED; else if (isSelected === -1) stateName = STATE_TYPE.UNSELECTED; else if (isHighlight === 1) stateName = STATE_TYPE.EMPHASIS; else if (isHighlight === -1) stateName = STATE_TYPE.BLUR; else if (isOccluded) stateName = STATE_TYPE.OCCLUDED; else stateName = STATE_TYPE.NORMAL; }
|
事件系统实现
1. 集中式事件总线
项目使用基于 EventEmitter3 的集中式事件总线:
1 2 3 4 5 6 7 8 9 10
| @injectable() export default class EventBus implements IEventBus { private emitter: EventEmitter; private eventHistory: Array<{ event: string; data: any; timestamp: number }>; private maxHistorySize: number = 1000;
emit(event: string, data?: any): void; on(event: string, callback: (data: any) => void): void; off(event: string, callback: (data: any) => void): void; }
|
2. 事件类别
系统定义了几个事件类别:
- 服务生命周期:
services:initializing、services:initialized、services:destroyed
- 控制器事件:
controller:initializing、controller:initialized、modelLoad:error
- 状态变化事件:
viewMode:changed
- 模型加载事件:
modelLoaded:coil、modelLoaded:male
3. 事件驱动的数据流
系统遵循发布-订阅模式,内置调试功能:
1 2 3 4 5 6
| this.eventBus.emit('controller:initializing', { controller: this });
this.eventBus.emit('controller:initialized', { controller: this });
this.eventBus.emit('controller:error', { controller: this, error });
|
数据流模式
1. 混合数据流架构
项目实现了混合数据流,结合单向和双向模式:
单向流(控制器驱动)
1 2 3 4 5 6
| controller.tick(time, stage) { this.__applyChangedStates(stage); this.getElements().forEach(elem => { elem.tick(time, stage); }); }
|
双向通信(事件驱动)
1 2
| eventBus.emit('viewMode:changed', { viewMode });
|
2. 可变状态与脏标志
系统使用可变状态,配备高效的更新跟踪:
1 2 3 4 5 6 7 8 9
| isDirty(dirty?: boolean): boolean { if (typeof dirty === 'undefined') { return this.__isDirty; }
this.children().forEach(elem => elem.isDirty(dirty)); this.__isDirty = dirty; return this.__isDirty; }
|
3. 元素管理的不可变集合
控制器使用不可变集合进行元素管理:
1 2 3
| private __elementsMap: Map<string, IElement> = Map(); this.__elementsMap = this.__elementsMap.set(elem.name, elem);
|
与 Flux/Redux 架构的比较
相似之处
1. 集中式状态管理
- Flux/Redux:单一数据源的集中式存储
- Pacific Vis:集中式事件总线(
EventBus)和管理状态的控制器层次结构
2. 单向数据流
- Flux/Redux:Dispatch → Reducer → Store → Components
- Pacific Vis:Controller → Element → Three.js renderer(类似的单向流)
3. 状态不可变性原则
- Flux/Redux:通过 reducer 的不可变状态更新
- Pacific Vis:控制器中使用
Immutable.Map 的不可变元素集合
关键差异
1. 状态定义方法
| Flux/Redux |
Pacific Vis 2025 |
| 通过 action 和 reducer 驱动状态 |
状态优先设计,基于材质的状态 |
| 通用状态结构 |
领域特定的状态类型(NORMAL、EMPHASIS、BLUR) |
| 纯函数式 reducer |
直接材质属性修改 |
| 可序列化状态 |
基于 Three.js 对象的状态 |
2. 事件系统架构
| Flux/Redux |
Pacific Vis 2025 |
| 同步 action 和同步 reducer |
异步事件驱动,带事件历史 |
| 可预测的状态变化 |
基于事件的通信,带调试历史 |
| action creator 用于事件 |
直接使用事件总线和负载对象 |
| 无事件历史 |
内置事件历史跟踪 |
3. 状态应用模式
Flux/Redux:
1 2 3
| dispatch({ type: 'TOGGLE_SELECTION', payload: elementId });
|
Pacific Vis:
1 2 3 4
| elem.isSelected(1); elem.isDirty(true);
|
4. 性能优化策略
| 方面 |
Flux/Redux |
Pacific Vis 2025 |
| 更新策略 |
虚拟 DOM diffing |
直接操作 Three.js 对象 |
| 状态缓存 |
Memoization、selector |
Three.js 中的材质级状态缓存 |
| 渲染 |
React reconciliation |
基于动画帧的更新 |
| 内存管理 |
垃圾回收 |
使用 destroy() 方法手动清理 |
Pacific Vis 的独特模式
1. Three.js 状态映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| protected _applyStateObject(stateObject: S[keyof S]) { const { material: materialProps = {} } = stateObject as StateDefinition; this.object3D.traverse(obj => { if ((obj as Mesh).isMesh) { let materials = (obj as Mesh).material; materials = Array.isArray(materials) ? materials : [materials]; materials.filter(Boolean).forEach(material => { Object.keys(materialProps).forEach(key => { (material as any)[key] = materialProps[key]; }); }); } }); }
|
2. 层次化状态继承
1 2 3 4 5 6 7
| isHighlight(highlight?: -1 | 0 | 1): -1 | 0 | 1 { this.children().forEach(elem => { elem.isHighlight(highlight); }); }
|
3. 性能监控集成
1 2 3 4
| this.performanceMonitor.startMeasure('BaseTowerController.initialize');
this.performanceMonitor.endMeasure('BaseTowerController.initialize');
|
架构权衡
Pacific Vis 方法的优势
- 直接对象操作:无需虚拟 DOM 开销的即时更新
- Three.js 集成:原生 3D 对象状态管理
- 性能:脏标志和材质缓存实现高效渲染
- 调试:事件历史跟踪用于复杂交互调试
- 层次化状态:3D 场景中自然的父子关系
限制
- 可预测性较差:事件驱动特性使状态变化更难追踪
- 领域特定:与 Three.js 和 3D 可视化需求紧密耦合
- 手动状态管理:需要显式脏标志管理
- 时间旅行调试受限:无内置状态重放功能
何时选择这种模式
这种架构非常适合:
- 需要直接操作对象的 3D 可视化应用
- 性能关键场景,频繁状态更新
- 复杂的层次化场景,具有父子关系
- 需要即时反馈的实时可视化
结论
Pacific Vis 2025 项目实现了一个领域特定的状态管理架构,它:
- 采用 Flux/Redux 原则,但为 3D 可视化进行了适配
- 使用事件驱动模式,具备增强的调试功能
- 通过脏标志和直接对象操作保持性能
- 为复杂 3D 场景提供层次化状态管理
- 集成依赖注入实现模块化架构
与传统 Flux/Redux 的主要区别是直接操作方法 vs. 虚拟 DOM/状态协调。对于需要立即对象操作的 3D 可视化场景,这种方法提供更好的性能和与 Three.js 更自然的集成,同时仍然保持了集中式状态管理的许多架构优势。
建议
对于 3D 可视化项目
- 采用针对 Three.js 对象的直接操作模式
- 使用复杂场景的层次化状态管理
- 实现性能优化的脏标志模式
- 集成用于调试的事件历史跟踪
对于传统 UI 应用
- 坚持使用 Flux/Redux 以获得更好的可预测性和开发者体验
- 使用虚拟 DOM diffing 处理大多数 UI 场景
- 利用现有的生态系统工具(DevTools、中间件等)
- 仅对性能关键组件考虑直接操作
这个分析表明,虽然模式在哲学上与 Flux/Redux 有相似之处,但 Pacific Vis 架构代表了针对 3D 可视化场景特别优化的深思熟虑的适配。