Files
esengine/packages/behavior-tree-editor/src/stores/ExecutionStatsStore.ts

349 lines
8.9 KiB
TypeScript
Raw Normal View History

refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 (#216) * refactor(editor): 提取行为树编辑器为独立包并重构编辑器架构 * feat(editor): 添加插件市场功能 * feat(editor): 重构插件市场以支持版本管理和ZIP打包 * feat(editor): 重构插件发布流程并修复React渲染警告 * fix(plugin): 修复插件发布和市场的路径不一致问题 * feat: 重构插件发布流程并添加插件删除功能 * fix(editor): 完善插件删除功能并修复多个关键问题 * fix(auth): 修复自动登录与手动登录的竞态条件问题 * feat(editor): 重构插件管理流程 * feat(editor): 支持 ZIP 文件直接发布插件 - 新增 PluginSourceParser 解析插件源 - 重构发布流程支持文件夹和 ZIP 两种方式 - 优化发布向导 UI * feat(editor): 插件市场支持多版本安装 - 插件解压到项目 plugins 目录 - 新增 Tauri 后端安装/卸载命令 - 支持选择任意版本安装 - 修复打包逻辑,保留完整 dist 目录结构 * feat(editor): 个人中心支持多版本管理 - 合并同一插件的不同版本 - 添加版本历史展开/折叠功能 - 禁止有待审核 PR 时更新插件 * fix(editor): 修复 InspectorRegistry 服务注册 - InspectorRegistry 实现 IService 接口 - 注册到 Core.services 供插件使用 * feat(behavior-tree-editor): 完善插件注册和文件操作 - 添加文件创建模板和操作处理器 - 实现右键菜单创建行为树功能 - 修复文件读取权限问题(使用 Tauri 命令) - 添加 BehaviorTreeEditorPanel 组件 - 修复 rollup 配置支持动态导入 * feat(plugin): 完善插件构建和发布流程 * fix(behavior-tree-editor): 完整恢复编辑器并修复 Toast 集成 * fix(behavior-tree-editor): 修复节点选中、连线跟随和文件加载问题并优化性能 * fix(behavior-tree-editor): 修复端口连接失败问题并优化连线样式 * refactor(behavior-tree-editor): 移除调试面板功能简化代码结构 * refactor(behavior-tree-editor): 清理冗余代码合并重复逻辑 * feat(behavior-tree-editor): 完善编辑器核心功能增强扩展性 * fix(lint): 修复ESLint错误确保CI通过 * refactor(behavior-tree-editor): 优化编辑器工具栏和编译器功能 * refactor(behavior-tree-editor): 清理技术债务,优化代码质量 * fix(editor-app): 修复字符串替换安全问题
2025-11-18 14:46:51 +08:00
import { create } from 'zustand';
/**
*
*/
export interface NodeExecutionStats {
/** 节点ID */
nodeId: string;
/** 执行总次数 */
totalExecutions: number;
/** 成功次数 */
successCount: number;
/** 失败次数 */
failureCount: number;
/** 运行中次数(记录开始运行的次数) */
runningCount: number;
/** 总耗时(毫秒) */
totalDuration: number;
/** 平均耗时(毫秒) */
averageDuration: number;
/** 最小耗时(毫秒) */
minDuration: number;
/** 最大耗时(毫秒) */
maxDuration: number;
/** 最后执行时间戳 */
lastExecutionTime: number;
/** 最后执行状态 */
lastStatus: 'success' | 'failure' | 'running' | 'idle';
}
/**
*
*/
export interface ExecutionHistoryEntry {
/** 节点ID */
nodeId: string;
/** 执行开始时间 */
startTime: number;
/** 执行结束时间 */
endTime?: number;
/** 执行状态 */
status: 'success' | 'failure' | 'running';
/** 执行耗时(毫秒) */
duration?: number;
/** 执行顺序号 */
executionOrder: number;
}
/**
*
*/
export interface ExecutionPath {
/** 路径ID */
id: string;
/** 开始时间 */
startTime: number;
/** 结束时间 */
endTime?: number;
/** 执行历史记录 */
history: ExecutionHistoryEntry[];
/** 是否正在执行 */
isActive: boolean;
}
interface ExecutionStatsState {
/** 节点统计信息 */
nodeStats: Map<string, NodeExecutionStats>;
/** 执行路径列表 */
executionPaths: ExecutionPath[];
/** 当前活动路径ID */
currentPathId: string | null;
/** 是否启用统计 */
isEnabled: boolean;
/** 是否启用历史记录 */
enableHistory: boolean;
/** 历史记录最大条数 */
maxHistorySize: number;
/**
*
*/
recordNodeStart: (nodeId: string, executionOrder: number) => void;
/**
*
*/
recordNodeEnd: (nodeId: string, status: 'success' | 'failure' | 'running') => void;
/**
*
*/
startNewPath: () => void;
/**
*
*/
endCurrentPath: () => void;
/**
*
*/
getNodeStats: (nodeId: string) => NodeExecutionStats | undefined;
/**
*
*/
getCurrentPath: () => ExecutionPath | undefined;
/**
*
*/
clearStats: () => void;
/**
*
*/
clearHistory: () => void;
/**
*
*/
setEnabled: (enabled: boolean) => void;
/**
*
*/
setEnableHistory: (enabled: boolean) => void;
/**
* JSON
*/
exportStats: () => string;
}
/**
*
*/
function createDefaultNodeStats(nodeId: string): NodeExecutionStats {
return {
nodeId,
totalExecutions: 0,
successCount: 0,
failureCount: 0,
runningCount: 0,
totalDuration: 0,
averageDuration: 0,
minDuration: Infinity,
maxDuration: 0,
lastExecutionTime: 0,
lastStatus: 'idle'
};
}
/**
*
*/
export const useExecutionStatsStore = create<ExecutionStatsState>((set, get) => ({
nodeStats: new Map(),
executionPaths: [],
currentPathId: null,
isEnabled: true,
enableHistory: true,
maxHistorySize: 100,
recordNodeStart: (nodeId: string, executionOrder: number) => {
if (!get().isEnabled) return;
const now = Date.now();
const state = get();
// 更新节点统计
const stats = state.nodeStats.get(nodeId) || createDefaultNodeStats(nodeId);
const updatedStats = { ...stats };
set((state) => {
const newStats = new Map(state.nodeStats);
newStats.set(nodeId, updatedStats);
return { nodeStats: newStats };
});
// 添加到执行历史
if (state.enableHistory && state.currentPathId) {
const paths = [...state.executionPaths];
const currentPath = paths.find((p) => p.id === state.currentPathId);
if (currentPath && currentPath.isActive) {
currentPath.history.push({
nodeId,
startTime: now,
status: 'running',
executionOrder
});
set({ executionPaths: paths });
}
}
},
recordNodeEnd: (nodeId: string, status: 'success' | 'failure' | 'running') => {
if (!get().isEnabled) return;
const now = Date.now();
const state = get();
// 更新节点统计
const stats = state.nodeStats.get(nodeId) || createDefaultNodeStats(nodeId);
// 计算耗时(从历史记录中查找对应的开始时间)
let duration = 0;
if (state.enableHistory && state.currentPathId) {
const currentPath = state.executionPaths.find((p) => p.id === state.currentPathId);
if (currentPath) {
// 找到最近的该节点的记录
for (let i = currentPath.history.length - 1; i >= 0; i--) {
const entry = currentPath.history[i];
if (entry.nodeId === nodeId && !entry.endTime) {
duration = now - entry.startTime;
entry.endTime = now;
entry.status = status;
entry.duration = duration;
break;
}
}
}
}
const updatedStats: NodeExecutionStats = {
...stats,
totalExecutions: stats.totalExecutions + 1,
successCount: status === 'success' ? stats.successCount + 1 : stats.successCount,
failureCount: status === 'failure' ? stats.failureCount + 1 : stats.failureCount,
runningCount: status === 'running' ? stats.runningCount + 1 : stats.runningCount,
totalDuration: stats.totalDuration + duration,
averageDuration: (stats.totalDuration + duration) / (stats.totalExecutions + 1),
minDuration: Math.min(stats.minDuration, duration || Infinity),
maxDuration: Math.max(stats.maxDuration, duration),
lastExecutionTime: now,
lastStatus: status
};
set((state) => {
const newStats = new Map(state.nodeStats);
newStats.set(nodeId, updatedStats);
return { nodeStats: newStats };
});
},
startNewPath: () => {
if (!get().isEnabled) return;
const pathId = `path_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const newPath: ExecutionPath = {
id: pathId,
startTime: Date.now(),
history: [],
isActive: true
};
set((state) => {
// 结束之前的路径
const paths = state.executionPaths.map((p) =>
p.isActive ? { ...p, isActive: false, endTime: Date.now() } : p
);
// 添加新路径
paths.push(newPath);
// 限制历史记录数量
const trimmedPaths = paths.slice(-state.maxHistorySize);
return {
executionPaths: trimmedPaths,
currentPathId: pathId
};
});
},
endCurrentPath: () => {
const state = get();
if (!state.currentPathId) return;
set((state) => ({
executionPaths: state.executionPaths.map((p) =>
p.id === state.currentPathId
? { ...p, isActive: false, endTime: Date.now() }
: p
)
}));
},
getNodeStats: (nodeId: string) => {
return get().nodeStats.get(nodeId);
},
getCurrentPath: () => {
const state = get();
if (!state.currentPathId) return undefined;
return state.executionPaths.find((p) => p.id === state.currentPathId);
},
clearStats: () => {
set({ nodeStats: new Map() });
},
clearHistory: () => {
set({ executionPaths: [], currentPathId: null });
},
setEnabled: (enabled: boolean) => {
set({ isEnabled: enabled });
},
setEnableHistory: (enabled: boolean) => {
set({ enableHistory: enabled });
},
exportStats: () => {
const state = get();
const data = {
nodeStats: Array.from(state.nodeStats.entries()).map(([_id, stats]) => stats),
executionPaths: state.executionPaths,
exportTime: new Date().toISOString()
};
return JSON.stringify(data, null, 2);
}
}));