Refactor/clean architecture phase1 (#215)
* refactor(editor): 建立Clean Architecture领域模型层 * refactor(editor): 实现应用层架构 - 命令模式、用例和状态管理 * refactor(editor): 实现展示层核心Hooks * refactor(editor): 实现基础设施层和展示层组件 * refactor(editor): 迁移画布和连接渲染到 Clean Architecture 组件 * feat(editor): 集成应用层架构和命令模式,实现撤销/重做功能 * refactor(editor): UI组件拆分 * refactor(editor): 提取快速创建菜单逻辑 * refactor(editor): 重构BehaviorTreeEditor,提取组件和Hook * refactor(editor): 提取端口连接和键盘事件Hook * refactor(editor): 提取拖放处理Hook * refactor(editor): 提取画布交互Hook和工具函数 * refactor(editor): 完成核心重构 * fix(editor): 修复节点无法创建和连接 * refactor(behavior-tree,editor): 重构节点子节点约束系统,实现元数据驱动的架构
This commit is contained in:
137
packages/editor-app/src/infrastructure/events/EditorEventBus.ts
Normal file
137
packages/editor-app/src/infrastructure/events/EditorEventBus.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
type EventHandler<T = any> = (data: T) => void;
|
||||
|
||||
interface Subscription {
|
||||
unsubscribe: () => void;
|
||||
}
|
||||
|
||||
export enum EditorEvent {
|
||||
NODE_CREATED = 'node:created',
|
||||
NODE_DELETED = 'node:deleted',
|
||||
NODE_UPDATED = 'node:updated',
|
||||
NODE_MOVED = 'node:moved',
|
||||
NODE_SELECTED = 'node:selected',
|
||||
|
||||
CONNECTION_ADDED = 'connection:added',
|
||||
CONNECTION_REMOVED = 'connection:removed',
|
||||
|
||||
EXECUTION_STARTED = 'execution:started',
|
||||
EXECUTION_PAUSED = 'execution:paused',
|
||||
EXECUTION_RESUMED = 'execution:resumed',
|
||||
EXECUTION_STOPPED = 'execution:stopped',
|
||||
EXECUTION_TICK = 'execution:tick',
|
||||
EXECUTION_NODE_STATUS_CHANGED = 'execution:node_status_changed',
|
||||
|
||||
TREE_SAVED = 'tree:saved',
|
||||
TREE_LOADED = 'tree:loaded',
|
||||
TREE_VALIDATED = 'tree:validated',
|
||||
|
||||
BLACKBOARD_VARIABLE_UPDATED = 'blackboard:variable_updated',
|
||||
BLACKBOARD_RESTORED = 'blackboard:restored',
|
||||
|
||||
CANVAS_ZOOM_CHANGED = 'canvas:zoom_changed',
|
||||
CANVAS_PAN_CHANGED = 'canvas:pan_changed',
|
||||
CANVAS_RESET = 'canvas:reset',
|
||||
|
||||
COMMAND_EXECUTED = 'command:executed',
|
||||
COMMAND_UNDONE = 'command:undone',
|
||||
COMMAND_REDONE = 'command:redone'
|
||||
}
|
||||
|
||||
export class EditorEventBus {
|
||||
private listeners: Map<string, Set<EventHandler>> = new Map();
|
||||
private eventHistory: Array<{ event: string; data: any; timestamp: number }> = [];
|
||||
private maxHistorySize: number = 100;
|
||||
|
||||
on<T = any>(event: EditorEvent | string, handler: EventHandler<T>): Subscription {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
}
|
||||
|
||||
this.listeners.get(event)!.add(handler);
|
||||
|
||||
return {
|
||||
unsubscribe: () => this.off(event, handler)
|
||||
};
|
||||
}
|
||||
|
||||
once<T = any>(event: EditorEvent | string, handler: EventHandler<T>): Subscription {
|
||||
const wrappedHandler = (data: T) => {
|
||||
handler(data);
|
||||
this.off(event, wrappedHandler);
|
||||
};
|
||||
|
||||
return this.on(event, wrappedHandler);
|
||||
}
|
||||
|
||||
off<T = any>(event: EditorEvent | string, handler: EventHandler<T>): void {
|
||||
const handlers = this.listeners.get(event);
|
||||
if (handlers) {
|
||||
handlers.delete(handler);
|
||||
if (handlers.size === 0) {
|
||||
this.listeners.delete(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit<T = any>(event: EditorEvent | string, data?: T): void {
|
||||
if (this.eventHistory.length >= this.maxHistorySize) {
|
||||
this.eventHistory.shift();
|
||||
}
|
||||
this.eventHistory.push({
|
||||
event,
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
const handlers = this.listeners.get(event);
|
||||
if (handlers) {
|
||||
handlers.forEach((handler) => {
|
||||
try {
|
||||
handler(data);
|
||||
} catch (error) {
|
||||
console.error(`Error in event handler for ${event}:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clear(event?: EditorEvent | string): void {
|
||||
if (event) {
|
||||
this.listeners.delete(event);
|
||||
} else {
|
||||
this.listeners.clear();
|
||||
}
|
||||
}
|
||||
|
||||
getListenerCount(event: EditorEvent | string): number {
|
||||
return this.listeners.get(event)?.size || 0;
|
||||
}
|
||||
|
||||
getAllEvents(): string[] {
|
||||
return Array.from(this.listeners.keys());
|
||||
}
|
||||
|
||||
getEventHistory(count?: number): Array<{ event: string; data: any; timestamp: number }> {
|
||||
if (count) {
|
||||
return this.eventHistory.slice(-count);
|
||||
}
|
||||
return [...this.eventHistory];
|
||||
}
|
||||
|
||||
clearHistory(): void {
|
||||
this.eventHistory = [];
|
||||
}
|
||||
}
|
||||
|
||||
let globalEventBus: EditorEventBus | null = null;
|
||||
|
||||
export function getGlobalEventBus(): EditorEventBus {
|
||||
if (!globalEventBus) {
|
||||
globalEventBus = new EditorEventBus();
|
||||
}
|
||||
return globalEventBus;
|
||||
}
|
||||
|
||||
export function resetGlobalEventBus(): void {
|
||||
globalEventBus = null;
|
||||
}
|
||||
Reference in New Issue
Block a user