bce3a6e253
* 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): 修复字符串替换安全问题
354 lines
9.8 KiB
TypeScript
354 lines
9.8 KiB
TypeScript
import { Node } from './Node';
|
|
import { Connection } from './Connection';
|
|
import { Blackboard } from './Blackboard';
|
|
import { ValidationError, NodeNotFoundError } from '../errors';
|
|
|
|
/**
|
|
* 行为树聚合根
|
|
* 管理整个行为树的节点、连接和黑板
|
|
*/
|
|
export class BehaviorTree {
|
|
private readonly _nodes: Map<string, Node>;
|
|
private readonly _connections: Connection[];
|
|
private readonly _blackboard: Blackboard;
|
|
private readonly _rootNodeId: string | null;
|
|
|
|
constructor(
|
|
nodes: Node[] = [],
|
|
connections: Connection[] = [],
|
|
blackboard: Blackboard = Blackboard.empty(),
|
|
rootNodeId: string | null = null
|
|
) {
|
|
this._nodes = new Map(nodes.map((node) => [node.id, node]));
|
|
this._connections = [...connections];
|
|
this._blackboard = blackboard;
|
|
this._rootNodeId = rootNodeId;
|
|
|
|
this.validateTree();
|
|
}
|
|
|
|
get nodes(): ReadonlyArray<Node> {
|
|
return Array.from(this._nodes.values());
|
|
}
|
|
|
|
get connections(): ReadonlyArray<Connection> {
|
|
return this._connections;
|
|
}
|
|
|
|
get blackboard(): Blackboard {
|
|
return this._blackboard;
|
|
}
|
|
|
|
get rootNodeId(): string | null {
|
|
return this._rootNodeId;
|
|
}
|
|
|
|
/**
|
|
* 获取指定节点
|
|
*/
|
|
getNode(nodeId: string): Node {
|
|
const node = this._nodes.get(nodeId);
|
|
if (!node) {
|
|
throw new NodeNotFoundError(nodeId);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* 检查节点是否存在
|
|
*/
|
|
hasNode(nodeId: string): boolean {
|
|
return this._nodes.has(nodeId);
|
|
}
|
|
|
|
/**
|
|
* 添加节点
|
|
*/
|
|
addNode(node: Node): BehaviorTree {
|
|
if (this._nodes.has(node.id)) {
|
|
throw new ValidationError(`节点 ${node.id} 已存在`);
|
|
}
|
|
|
|
if (node.isRoot()) {
|
|
if (this._rootNodeId) {
|
|
throw new ValidationError('行为树只能有一个根节点');
|
|
}
|
|
return new BehaviorTree(
|
|
[...this.nodes, node],
|
|
this._connections,
|
|
this._blackboard,
|
|
node.id
|
|
);
|
|
}
|
|
|
|
return new BehaviorTree(
|
|
[...this.nodes, node],
|
|
this._connections,
|
|
this._blackboard,
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 移除节点
|
|
* 会同时移除相关的连接
|
|
*/
|
|
removeNode(nodeId: string): BehaviorTree {
|
|
if (!this._nodes.has(nodeId)) {
|
|
throw new NodeNotFoundError(nodeId);
|
|
}
|
|
|
|
const node = this.getNode(nodeId);
|
|
const newNodes = Array.from(this.nodes.filter((n) => n.id !== nodeId));
|
|
const newConnections = this._connections.filter(
|
|
(conn) => conn.from !== nodeId && conn.to !== nodeId
|
|
);
|
|
|
|
const newRootNodeId = node.isRoot() ? null : this._rootNodeId;
|
|
|
|
return new BehaviorTree(
|
|
newNodes,
|
|
newConnections,
|
|
this._blackboard,
|
|
newRootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 更新节点
|
|
*/
|
|
updateNode(nodeId: string, updater: (node: Node) => Node): BehaviorTree {
|
|
const node = this.getNode(nodeId);
|
|
const updatedNode = updater(node);
|
|
|
|
const newNodes = Array.from(this.nodes.map((n) => n.id === nodeId ? updatedNode : n));
|
|
|
|
return new BehaviorTree(
|
|
newNodes,
|
|
this._connections,
|
|
this._blackboard,
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 添加连接
|
|
* 会验证连接的合法性
|
|
*/
|
|
addConnection(connection: Connection): BehaviorTree {
|
|
const fromNode = this.getNode(connection.from);
|
|
const toNode = this.getNode(connection.to);
|
|
|
|
if (this.hasConnection(connection.from, connection.to)) {
|
|
throw new ValidationError(`连接已存在:${connection.from} -> ${connection.to}`);
|
|
}
|
|
|
|
if (this.wouldCreateCycle(connection.from, connection.to)) {
|
|
throw ValidationError.circularReference(connection.to);
|
|
}
|
|
|
|
if (connection.isNodeConnection()) {
|
|
if (!fromNode.canAddChild()) {
|
|
if (fromNode.isRoot()) {
|
|
throw ValidationError.rootNodeMaxChildren();
|
|
}
|
|
if (fromNode.nodeType.isDecorator()) {
|
|
throw ValidationError.decoratorNodeMaxChildren();
|
|
}
|
|
throw new ValidationError(`节点 ${connection.from} 无法添加更多子节点`);
|
|
}
|
|
|
|
if (toNode.nodeType.getMaxChildren() === 0 && toNode.nodeType.isLeaf()) {
|
|
}
|
|
|
|
const updatedFromNode = fromNode.addChild(connection.to);
|
|
const newNodes = Array.from(this.nodes.map((n) =>
|
|
n.id === connection.from ? updatedFromNode : n
|
|
));
|
|
|
|
return new BehaviorTree(
|
|
newNodes,
|
|
[...this._connections, connection],
|
|
this._blackboard,
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
return new BehaviorTree(
|
|
Array.from(this.nodes),
|
|
[...this._connections, connection],
|
|
this._blackboard,
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 移除连接
|
|
*/
|
|
removeConnection(from: string, to: string, fromProperty?: string, toProperty?: string): BehaviorTree {
|
|
const connection = this._connections.find((c) => c.matches(from, to, fromProperty, toProperty));
|
|
|
|
if (!connection) {
|
|
throw new ValidationError(`连接不存在:${from} -> ${to}`);
|
|
}
|
|
|
|
const newConnections = this._connections.filter((c) => !c.matches(from, to, fromProperty, toProperty));
|
|
|
|
if (connection.isNodeConnection()) {
|
|
const fromNode = this.getNode(from);
|
|
const updatedFromNode = fromNode.removeChild(to);
|
|
const newNodes = Array.from(this.nodes.map((n) =>
|
|
n.id === from ? updatedFromNode : n
|
|
));
|
|
|
|
return new BehaviorTree(
|
|
newNodes,
|
|
newConnections,
|
|
this._blackboard,
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
return new BehaviorTree(
|
|
Array.from(this.nodes),
|
|
newConnections,
|
|
this._blackboard,
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 检查是否存在连接
|
|
*/
|
|
hasConnection(from: string, to: string): boolean {
|
|
return this._connections.some((c) => c.from === from && c.to === to);
|
|
}
|
|
|
|
/**
|
|
* 检查是否会创建循环引用
|
|
*/
|
|
private wouldCreateCycle(from: string, to: string): boolean {
|
|
const visited = new Set<string>();
|
|
const queue: string[] = [to];
|
|
|
|
while (queue.length > 0) {
|
|
const current = queue.shift()!;
|
|
|
|
if (current === from) {
|
|
return true;
|
|
}
|
|
|
|
if (visited.has(current)) {
|
|
continue;
|
|
}
|
|
|
|
visited.add(current);
|
|
|
|
const childConnections = this._connections.filter((c) => c.from === current && c.isNodeConnection());
|
|
childConnections.forEach((conn) => queue.push(conn.to));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 更新黑板
|
|
*/
|
|
updateBlackboard(updater: (blackboard: Blackboard) => Blackboard): BehaviorTree {
|
|
return new BehaviorTree(
|
|
Array.from(this.nodes),
|
|
this._connections,
|
|
updater(this._blackboard),
|
|
this._rootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 获取节点的子节点
|
|
*/
|
|
getChildren(nodeId: string): Node[] {
|
|
const node = this.getNode(nodeId);
|
|
return node.children.map((childId) => this.getNode(childId));
|
|
}
|
|
|
|
/**
|
|
* 获取节点的父节点
|
|
*/
|
|
getParent(nodeId: string): Node | null {
|
|
const parentConnection = this._connections.find(
|
|
(c) => c.to === nodeId && c.isNodeConnection()
|
|
);
|
|
|
|
if (!parentConnection) {
|
|
return null;
|
|
}
|
|
|
|
return this.getNode(parentConnection.from);
|
|
}
|
|
|
|
/**
|
|
* 验证树的完整性
|
|
*/
|
|
private validateTree(): void {
|
|
const rootNodes = this.nodes.filter((n) => n.isRoot());
|
|
|
|
if (rootNodes.length > 1) {
|
|
throw new ValidationError('行为树只能有一个根节点');
|
|
}
|
|
|
|
if (rootNodes.length === 1 && rootNodes[0] && this._rootNodeId !== rootNodes[0].id) {
|
|
throw new ValidationError('根节点ID不匹配');
|
|
}
|
|
|
|
this._connections.forEach((conn) => {
|
|
if (!this._nodes.has(conn.from)) {
|
|
throw new NodeNotFoundError(conn.from);
|
|
}
|
|
if (!this._nodes.has(conn.to)) {
|
|
throw new NodeNotFoundError(conn.to);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 转换为普通对象
|
|
*/
|
|
toObject(): {
|
|
nodes: ReturnType<Node['toObject']>[];
|
|
connections: ReturnType<Connection['toObject']>[];
|
|
blackboard: Record<string, unknown>;
|
|
rootNodeId: string | null;
|
|
} {
|
|
return {
|
|
nodes: this.nodes.map((n) => n.toObject()),
|
|
connections: this._connections.map((c) => c.toObject()),
|
|
blackboard: this._blackboard.toObject(),
|
|
rootNodeId: this._rootNodeId
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 从普通对象创建行为树
|
|
*/
|
|
static fromObject(obj: {
|
|
nodes: Parameters<typeof Node.fromObject>[0][];
|
|
connections: Parameters<typeof Connection.fromObject>[0][];
|
|
blackboard: Record<string, unknown>;
|
|
rootNodeId: string | null;
|
|
}): BehaviorTree {
|
|
return new BehaviorTree(
|
|
obj.nodes.map((n) => Node.fromObject(n)),
|
|
obj.connections.map((c) => Connection.fromObject(c)),
|
|
Blackboard.fromObject(obj.blackboard),
|
|
obj.rootNodeId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 创建空行为树
|
|
*/
|
|
static empty(): BehaviorTree {
|
|
return new BehaviorTree();
|
|
}
|
|
}
|