Files
esengine/packages/behavior-tree-editor/src/hooks/useExecutionController.ts
YHH b42a7b4e43 Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级

* feat(core): 层级系统重构与UI变换矩阵修复

* refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题

* fix(physics): 修复跨包组件类引用问题

* feat: 统一运行时架构与浏览器运行支持

* feat(asset): 实现浏览器运行时资产加载系统

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题和CI类型检查错误

* fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误

* test: 补齐核心模块测试用例,修复CI构建配置

* fix: 修复测试用例中的类型错误和断言问题

* fix: 修复 turbo build:npm 任务的依赖顺序问题

* fix: 修复 CI 构建错误并优化构建性能
2025-12-01 22:28:51 +08:00

176 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect, useMemo, useRef, createLogger } from '@esengine/editor-runtime';
import { ExecutionController, ExecutionMode } from '../application/services/ExecutionController';
import { BlackboardManager } from '../application/services/BlackboardManager';
import { BehaviorTreeNode, Connection, useBehaviorTreeDataStore } from '../stores';
import { ExecutionLog } from '../utils/BehaviorTreeExecutor';
import { BlackboardValue } from '../domain/models/Blackboard';
const logger = createLogger('useExecutionController');
type BlackboardVariables = Record<string, BlackboardValue>;
interface UseExecutionControllerParams {
rootNodeId: string;
projectPath: string | null;
blackboardVariables: BlackboardVariables;
nodes: BehaviorTreeNode[];
connections: Connection[];
initialBlackboardVariables: BlackboardVariables;
onBlackboardUpdate: (variables: BlackboardVariables) => void;
onInitialBlackboardSave: (variables: BlackboardVariables) => void;
onExecutingChange: (isExecuting: boolean) => void;
onSaveNodesDataSnapshot: () => void;
onRestoreNodesData: () => void;
sortChildrenByPosition: () => void;
}
export function useExecutionController(params: UseExecutionControllerParams) {
const {
rootNodeId,
projectPath,
blackboardVariables,
nodes,
connections,
onBlackboardUpdate,
onInitialBlackboardSave,
onExecutingChange,
onSaveNodesDataSnapshot,
onRestoreNodesData,
sortChildrenByPosition
} = params;
const [executionMode, setExecutionMode] = useState<ExecutionMode>('idle');
const [executionLogs, setExecutionLogs] = useState<ExecutionLog[]>([]);
const [executionSpeed, setExecutionSpeed] = useState<number>(1.0);
const [tickCount, setTickCount] = useState(0);
const controller = useMemo(() => {
return new ExecutionController({
rootNodeId,
projectPath,
onLogsUpdate: setExecutionLogs,
onBlackboardUpdate,
onTickCountUpdate: setTickCount,
onExecutionStatusUpdate: (statuses, orders) => {
const store = useBehaviorTreeDataStore.getState();
store.updateNodeExecutionStatuses(statuses, orders);
}
// 不在这里传递 onBreakpointHit避免频繁重建
});
}, [rootNodeId, projectPath, onBlackboardUpdate]);
const blackboardManager = useMemo(() => new BlackboardManager(), []);
useEffect(() => {
// 保存当前 controller 的引用,确保清理时使用正确的实例
const currentController = controller;
return () => {
currentController.destroy();
};
}, [controller]);
useEffect(() => {
controller.setConnections(connections);
}, [connections, controller]);
useEffect(() => {
if (executionMode === 'idle') return;
const executorVars = controller.getBlackboardVariables();
Object.entries(blackboardVariables).forEach(([key, value]) => {
if (executorVars[key] !== value) {
controller.updateBlackboardVariable(key, value);
}
});
}, [blackboardVariables, executionMode, controller]);
useEffect(() => {
if (executionMode === 'idle') return;
controller.updateNodes(nodes);
}, [nodes, executionMode, controller]);
const handlePlay = async () => {
try {
sortChildrenByPosition();
logger.info('[Execute] Sorted children by position before execution');
blackboardManager.setInitialVariables(blackboardVariables);
blackboardManager.setCurrentVariables(blackboardVariables);
onInitialBlackboardSave(blackboardManager.getInitialVariables());
onSaveNodesDataSnapshot();
onExecutingChange(true);
setExecutionMode('running');
await controller.play(nodes, blackboardVariables, connections);
} catch (error) {
logger.error('Failed to start execution:', error);
setExecutionMode('idle');
onExecutingChange(false);
}
};
const handlePause = async () => {
try {
await controller.pause();
const newMode = controller.getMode();
setExecutionMode(newMode);
} catch (error) {
logger.error('Failed to pause/resume execution:', error);
}
};
const handleStop = async () => {
try {
await controller.stop();
setExecutionMode('idle');
setTickCount(0);
const restoredVars = blackboardManager.restoreInitialVariables();
onBlackboardUpdate(restoredVars);
onRestoreNodesData();
useBehaviorTreeDataStore.getState().clearNodeExecutionStatuses();
onExecutingChange(false);
} catch (error) {
logger.error('Failed to stop execution:', error);
}
};
const handleStep = () => {
controller.step();
// 单步执行后保持idle状态不需要专门的step状态
};
const handleReset = async () => {
try {
await controller.reset();
setExecutionMode('idle');
setTickCount(0);
} catch (error) {
logger.error('Failed to reset execution:', error);
}
};
const handleSpeedChange = (speed: number) => {
setExecutionSpeed(speed);
controller.setSpeed(speed);
};
return {
executionMode,
executionLogs,
executionSpeed,
tickCount,
handlePlay,
handlePause,
handleStop,
handleStep,
handleReset,
handleSpeedChange,
setExecutionLogs,
controller,
blackboardManager
};
}