Files
esengine/packages/behavior-tree-editor/src/domain/services/TreeValidator.ts
YHH bce3a6e253 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

199 lines
5.8 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 { BehaviorTree } from '../models/BehaviorTree';
import { Node } from '../models/Node';
import { Connection } from '../models/Connection';
import { IValidator, ValidationResult, ValidationError as IValidationError } from '../interfaces/IValidator';
/**
* 行为树验证服务
* 实现所有业务验证规则
*/
export class TreeValidator implements IValidator {
/**
* 验证整个行为树
*/
validateTree(tree: BehaviorTree): ValidationResult {
const errors: IValidationError[] = [];
if (!tree.rootNodeId) {
errors.push({
message: '行为树必须有一个根节点'
});
}
const rootNodes = tree.nodes.filter((n) => n.isRoot());
if (rootNodes.length > 1) {
errors.push({
message: '行为树只能有一个根节点',
nodeId: rootNodes.map((n) => n.id).join(', ')
});
}
tree.nodes.forEach((node) => {
const nodeValidation = this.validateNode(node);
errors.push(...nodeValidation.errors);
});
tree.connections.forEach((connection) => {
const connValidation = this.validateConnection(connection, tree);
errors.push(...connValidation.errors);
});
const cycleValidation = this.validateNoCycles(tree);
errors.push(...cycleValidation.errors);
return {
isValid: errors.length === 0,
errors
};
}
/**
* 验证节点
*/
validateNode(node: Node): ValidationResult {
const errors: IValidationError[] = [];
// 使用模板定义的约束undefined 表示无限制
const maxChildren = (node.template.maxChildren ?? Infinity) as number;
const actualChildren = node.children.length;
if (actualChildren > maxChildren) {
if (node.isRoot()) {
errors.push({
message: '根节点只能连接一个子节点',
nodeId: node.id,
field: 'children'
});
} else if (node.nodeType.isDecorator()) {
errors.push({
message: '装饰节点只能连接一个子节点',
nodeId: node.id,
field: 'children'
});
} else if (node.nodeType.isLeaf()) {
errors.push({
message: '叶子节点不能有子节点',
nodeId: node.id,
field: 'children'
});
} else {
errors.push({
message: `节点子节点数量 (${actualChildren}) 超过最大限制 (${maxChildren})`,
nodeId: node.id,
field: 'children'
});
}
}
return {
isValid: errors.length === 0,
errors
};
}
/**
* 验证连接
*/
validateConnection(connection: Connection, tree: BehaviorTree): ValidationResult {
const errors: IValidationError[] = [];
if (!tree.hasNode(connection.from)) {
errors.push({
message: `源节点不存在: ${connection.from}`,
nodeId: connection.from
});
}
if (!tree.hasNode(connection.to)) {
errors.push({
message: `目标节点不存在: ${connection.to}`,
nodeId: connection.to
});
}
if (connection.from === connection.to) {
errors.push({
message: '节点不能连接到自己',
nodeId: connection.from
});
}
if (tree.hasNode(connection.from) && tree.hasNode(connection.to)) {
const fromNode = tree.getNode(connection.from);
const toNode = tree.getNode(connection.to);
if (connection.isNodeConnection()) {
if (!fromNode.canAddChild()) {
errors.push({
message: `节点 ${connection.from} 无法添加更多子节点`,
nodeId: connection.from
});
}
if (toNode.nodeType.isLeaf() && toNode.hasChildren()) {
errors.push({
message: `叶子节点 ${connection.to} 不能有子节点`,
nodeId: connection.to
});
}
}
}
return {
isValid: errors.length === 0,
errors
};
}
/**
* 验证是否存在循环引用
*/
validateNoCycles(tree: BehaviorTree): ValidationResult {
const errors: IValidationError[] = [];
const visited = new Set<string>();
const recursionStack = new Set<string>();
const dfs = (nodeId: string): boolean => {
if (recursionStack.has(nodeId)) {
errors.push({
message: `检测到循环引用: 节点 ${nodeId}`,
nodeId
});
return true;
}
if (visited.has(nodeId)) {
return false;
}
visited.add(nodeId);
recursionStack.add(nodeId);
const node = tree.getNode(nodeId);
for (const childId of node.children) {
if (dfs(childId)) {
return true;
}
}
recursionStack.delete(nodeId);
return false;
};
if (tree.rootNodeId) {
dfs(tree.rootNodeId);
}
tree.nodes.forEach((node) => {
if (!visited.has(node.id) && !node.isRoot()) {
dfs(node.id);
}
});
return {
isValid: errors.length === 0,
errors
};
}
}