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): 修复字符串替换安全问题
This commit is contained in:
348
packages/behavior-tree-editor/src/stores/ExecutionStatsStore.ts
Normal file
348
packages/behavior-tree-editor/src/stores/ExecutionStatsStore.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
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);
|
||||
}
|
||||
}));
|
||||
Reference in New Issue
Block a user