Feature/runtime cdn and plugin loader (#240)

* feat(ui): 完善 UI 布局系统和编辑器可视化工具

* refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统

* fix: 修复 CodeQL 警告并提升测试覆盖率

* refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题

* fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤

* docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明

* fix(ci): 修复 type-check 失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖

* fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖

* fix(ci): platform-web 添加缺失的 behavior-tree 依赖

* fix(lint): 移除正则表达式中不必要的转义字符
This commit is contained in:
YHH
2025-11-27 20:42:46 +08:00
committed by GitHub
parent 71869b1a58
commit 107439d70c
367 changed files with 10661 additions and 12473 deletions

View File

@@ -1,25 +0,0 @@
import { Node } from '../models/Node';
import { Position } from '../value-objects/Position';
import { NodeTemplate } from '@esengine/behavior-tree';
export const ROOT_NODE_ID = 'root-node';
export const createRootNodeTemplate = (): NodeTemplate => ({
type: 'root',
displayName: '根节点',
category: '根节点',
icon: 'TreePine',
description: '行为树根节点',
color: '#FFD700',
maxChildren: 1,
defaultConfig: {
nodeType: 'root'
},
properties: []
});
export const createRootNode = (): Node => {
const template = createRootNodeTemplate();
const position = new Position(400, 100);
return new Node(ROOT_NODE_ID, template, { nodeType: 'root' }, position, []);
};

View File

@@ -1,10 +0,0 @@
/**
* 领域错误基类
*/
export abstract class DomainError extends Error {
constructor(message: string) {
super(message);
this.name = this.constructor.name;
Object.setPrototypeOf(this, new.target.prototype);
}
}

View File

@@ -1,10 +0,0 @@
import { DomainError } from './DomainError';
/**
* 节点未找到错误
*/
export class NodeNotFoundError extends DomainError {
constructor(public readonly nodeId: string) {
super(`节点未找到: ${nodeId}`);
}
}

View File

@@ -1,52 +0,0 @@
import { DomainError } from './DomainError';
/**
* 验证错误
* 当业务规则验证失败时抛出
*/
export class ValidationError extends DomainError {
constructor(
message: string,
public readonly field?: string,
public readonly value?: unknown
) {
super(message);
}
static rootNodeMaxChildren(): ValidationError {
return new ValidationError(
'根节点只能连接一个子节点',
'children'
);
}
static decoratorNodeMaxChildren(): ValidationError {
return new ValidationError(
'装饰节点只能连接一个子节点',
'children'
);
}
static leafNodeNoChildren(): ValidationError {
return new ValidationError(
'叶子节点不能有子节点',
'children'
);
}
static circularReference(nodeId: string): ValidationError {
return new ValidationError(
`检测到循环引用,节点 ${nodeId} 不能连接到自己或其子节点`,
'connection',
nodeId
);
}
static invalidConnection(from: string, to: string, reason: string): ValidationError {
return new ValidationError(
`无效的连接:${reason}`,
'connection',
{ from, to }
);
}
}

View File

@@ -1,3 +0,0 @@
export { DomainError } from './DomainError';
export { ValidationError } from './ValidationError';
export { NodeNotFoundError } from './NodeNotFoundError';

View File

@@ -1,5 +0,0 @@
export * from './models';
export * from './value-objects';
export * from './interfaces';
export { DomainError, ValidationError as DomainValidationError, NodeNotFoundError } from './errors';
export * from './services';

View File

@@ -1,32 +0,0 @@
import { NodeTemplate } from '@esengine/behavior-tree';
import { Node } from '../models/Node';
import { Position } from '../value-objects';
/**
* 节点工厂接口
* 负责创建不同类型的节点
*/
export interface INodeFactory {
/**
* 创建节点
*/
createNode(
template: NodeTemplate,
position: Position,
data?: Record<string, unknown>
): Node;
/**
* 根据模板类型创建节点
*/
createNodeByType(
nodeType: string,
position: Position,
data?: Record<string, unknown>
): Node;
/**
* 克隆节点
*/
cloneNode(node: Node, newPosition?: Position): Node;
}

View File

@@ -1,27 +0,0 @@
import { BehaviorTree } from '../models/BehaviorTree';
/**
* 仓储接口
* 负责行为树的持久化
*/
export interface IBehaviorTreeRepository {
/**
* 保存行为树
*/
save(tree: BehaviorTree, path: string): Promise<void>;
/**
* 加载行为树
*/
load(path: string): Promise<BehaviorTree>;
/**
* 检查文件是否存在
*/
exists(path: string): Promise<boolean>;
/**
* 删除行为树文件
*/
delete(path: string): Promise<void>;
}

View File

@@ -1,30 +0,0 @@
import { BehaviorTree } from '../models/BehaviorTree';
/**
* 序列化格式
*/
export type SerializationFormat = 'json' | 'binary';
/**
* 序列化接口
* 负责行为树的序列化和反序列化
*/
export interface ISerializer {
/**
* 序列化行为树
*/
serialize(tree: BehaviorTree, format: SerializationFormat): string | Uint8Array;
/**
* 反序列化行为树
*/
deserialize(data: string | Uint8Array, format: SerializationFormat): BehaviorTree;
/**
* 导出为运行时资产格式
*/
exportToRuntimeAsset(
tree: BehaviorTree,
format: SerializationFormat
): string | Uint8Array;
}

View File

@@ -1,46 +0,0 @@
import { BehaviorTree } from '../models/BehaviorTree';
import { Node } from '../models/Node';
import { Connection } from '../models/Connection';
/**
* 验证结果
*/
export interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
}
/**
* 验证错误详情
*/
export interface ValidationError {
message: string;
nodeId?: string;
field?: string;
}
/**
* 验证器接口
* 负责行为树的验证逻辑
*/
export interface IValidator {
/**
* 验证整个行为树
*/
validateTree(tree: BehaviorTree): ValidationResult;
/**
* 验证节点
*/
validateNode(node: Node): ValidationResult;
/**
* 验证连接
*/
validateConnection(connection: Connection, tree: BehaviorTree): ValidationResult;
/**
* 验证是否会产生循环引用
*/
validateNoCycles(tree: BehaviorTree): ValidationResult;
}

View File

@@ -1,4 +0,0 @@
export { type INodeFactory } from './INodeFactory';
export { type ISerializer, type SerializationFormat } from './ISerializer';
export { type IBehaviorTreeRepository } from './IRepository';
export { type IValidator, type ValidationResult, type ValidationError } from './IValidator';

View File

@@ -1,353 +0,0 @@
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();
}
}

View File

@@ -1,122 +0,0 @@
/**
* 黑板值类型
*/
export type BlackboardValue = string | number | boolean | null | undefined | Record<string, unknown> | unknown[];
/**
* 黑板领域实体
* 管理行为树的全局变量
*/
export class Blackboard {
private _variables: Map<string, BlackboardValue>;
constructor(variables: Record<string, BlackboardValue> = {}) {
this._variables = new Map(Object.entries(variables));
}
/**
* 获取变量值
*/
get(key: string): BlackboardValue {
return this._variables.get(key);
}
/**
* 设置变量值
*/
set(key: string, value: BlackboardValue): Blackboard {
const newVariables = new Map(this._variables);
newVariables.set(key, value);
return new Blackboard(Object.fromEntries(newVariables));
}
/**
* 设置变量值(别名方法)
*/
setValue(key: string, value: BlackboardValue): void {
this._variables.set(key, value);
}
/**
* 删除变量
*/
delete(key: string): Blackboard {
const newVariables = new Map(this._variables);
newVariables.delete(key);
return new Blackboard(Object.fromEntries(newVariables));
}
/**
* 检查变量是否存在
*/
has(key: string): boolean {
return this._variables.has(key);
}
/**
* 获取所有变量名
*/
keys(): string[] {
return Array.from(this._variables.keys());
}
/**
* 获取所有变量
*/
getAll(): Record<string, BlackboardValue> {
return Object.fromEntries(this._variables);
}
/**
* 批量设置变量
*/
setAll(variables: Record<string, BlackboardValue>): Blackboard {
const newVariables = new Map(this._variables);
Object.entries(variables).forEach(([key, value]) => {
newVariables.set(key, value);
});
return new Blackboard(Object.fromEntries(newVariables));
}
/**
* 清空所有变量
*/
clear(): Blackboard {
return new Blackboard();
}
/**
* 获取变量数量
*/
size(): number {
return this._variables.size;
}
/**
* 克隆黑板
*/
clone(): Blackboard {
return new Blackboard(this.getAll());
}
/**
* 转换为普通对象
*/
toObject(): Record<string, BlackboardValue> {
return this.getAll();
}
/**
* 从普通对象创建黑板
*/
static fromObject(obj: Record<string, unknown>): Blackboard {
return new Blackboard(obj as Record<string, BlackboardValue>);
}
/**
* 创建空黑板
*/
static empty(): Blackboard {
return new Blackboard();
}
}

View File

@@ -1,140 +0,0 @@
import { ValidationError } from '../errors';
/**
* 连接类型
*/
export type ConnectionType = 'node' | 'property';
/**
* 连接领域实体
* 表示两个节点之间的连接关系
*/
export class Connection {
private readonly _from: string;
private readonly _to: string;
private readonly _fromProperty?: string;
private readonly _toProperty?: string;
private readonly _connectionType: ConnectionType;
constructor(
from: string,
to: string,
connectionType: ConnectionType = 'node',
fromProperty?: string,
toProperty?: string
) {
if (from === to) {
throw ValidationError.circularReference(from);
}
if (connectionType === 'property' && (!fromProperty || !toProperty)) {
throw new ValidationError('属性连接必须指定源属性和目标属性');
}
this._from = from;
this._to = to;
this._connectionType = connectionType;
this._fromProperty = fromProperty;
this._toProperty = toProperty;
}
get from(): string {
return this._from;
}
get to(): string {
return this._to;
}
get fromProperty(): string | undefined {
return this._fromProperty;
}
get toProperty(): string | undefined {
return this._toProperty;
}
get connectionType(): ConnectionType {
return this._connectionType;
}
/**
* 检查是否为节点连接
*/
isNodeConnection(): boolean {
return this._connectionType === 'node';
}
/**
* 检查是否为属性连接
*/
isPropertyConnection(): boolean {
return this._connectionType === 'property';
}
/**
* 检查连接是否匹配指定的条件
*/
matches(from: string, to: string, fromProperty?: string, toProperty?: string): boolean {
if (this._from !== from || this._to !== to) {
return false;
}
if (this._connectionType === 'property') {
return this._fromProperty === fromProperty && this._toProperty === toProperty;
}
return true;
}
/**
* 相等性比较
*/
equals(other: Connection): boolean {
return (
this._from === other._from &&
this._to === other._to &&
this._connectionType === other._connectionType &&
this._fromProperty === other._fromProperty &&
this._toProperty === other._toProperty
);
}
/**
* 转换为普通对象
*/
toObject(): {
from: string;
to: string;
fromProperty?: string;
toProperty?: string;
connectionType: ConnectionType;
} {
return {
from: this._from,
to: this._to,
connectionType: this._connectionType,
...(this._fromProperty && { fromProperty: this._fromProperty }),
...(this._toProperty && { toProperty: this._toProperty })
};
}
/**
* 从普通对象创建连接
*/
static fromObject(obj: {
from: string;
to: string;
fromProperty?: string;
toProperty?: string;
connectionType: ConnectionType;
}): Connection {
return new Connection(
obj.from,
obj.to,
obj.connectionType,
obj.fromProperty,
obj.toProperty
);
}
}

View File

@@ -1,190 +0,0 @@
import { NodeTemplate } from '@esengine/behavior-tree';
import { Position, NodeType } from '../value-objects';
import { ValidationError } from '../errors';
/**
* 行为树节点领域实体
* 封装节点的业务逻辑和验证规则
*/
export class Node {
private readonly _id: string;
private readonly _template: NodeTemplate;
private _data: Record<string, unknown>;
private _position: Position;
private _children: string[];
private readonly _nodeType: NodeType;
constructor(
id: string,
template: NodeTemplate,
data: Record<string, unknown>,
position: Position,
children: string[] = []
) {
this._id = id;
this._template = template;
this._data = { ...data };
this._position = position;
this._children = [...children];
this._nodeType = NodeType.fromString(template.type);
}
get id(): string {
return this._id;
}
get template(): NodeTemplate {
return this._template;
}
get data(): Record<string, unknown> {
return { ...this._data };
}
get position(): Position {
return this._position;
}
get children(): ReadonlyArray<string> {
return this._children;
}
get nodeType(): NodeType {
return this._nodeType;
}
/**
* 更新节点位置
*/
moveToPosition(newPosition: Position): Node {
return new Node(
this._id,
this._template,
this._data,
newPosition,
this._children
);
}
/**
* 更新节点数据
*/
updateData(data: Record<string, unknown>): Node {
return new Node(
this._id,
this._template,
{ ...this._data, ...data },
this._position,
this._children
);
}
/**
* 添加子节点
* @throws ValidationError 如果违反业务规则
*/
addChild(childId: string): Node {
// 使用模板定义的约束undefined 表示无限制
const maxChildren = (this._template.maxChildren ?? Infinity) as number;
if (maxChildren === 0) {
throw ValidationError.leafNodeNoChildren();
}
if (this._children.length >= maxChildren) {
if (this._nodeType.isRoot()) {
throw ValidationError.rootNodeMaxChildren();
}
if (this._nodeType.isDecorator()) {
throw ValidationError.decoratorNodeMaxChildren();
}
throw new ValidationError(`节点 ${this._id} 已达到最大子节点数 ${maxChildren}`);
}
if (this._children.includes(childId)) {
throw new ValidationError(`子节点 ${childId} 已存在`);
}
return new Node(
this._id,
this._template,
this._data,
this._position,
[...this._children, childId]
);
}
/**
* 移除子节点
*/
removeChild(childId: string): Node {
return new Node(
this._id,
this._template,
this._data,
this._position,
this._children.filter((id) => id !== childId)
);
}
/**
* 检查是否可以添加子节点
*/
canAddChild(): boolean {
// 使用模板定义的最大子节点数undefined 表示无限制
const maxChildren = (this._template.maxChildren ?? Infinity) as number;
return this._children.length < maxChildren;
}
/**
* 检查是否有子节点
*/
hasChildren(): boolean {
return this._children.length > 0;
}
/**
* 检查是否为根节点
*/
isRoot(): boolean {
return this._nodeType.isRoot();
}
/**
* 转换为普通对象(用于序列化)
*/
toObject(): {
id: string;
template: NodeTemplate;
data: Record<string, unknown>;
position: { x: number; y: number };
children: string[];
} {
return {
id: this._id,
template: this._template,
data: this._data,
position: this._position.toObject(),
children: [...this._children]
};
}
/**
* 从普通对象创建节点
*/
static fromObject(obj: {
id: string;
template: NodeTemplate;
data: Record<string, unknown>;
position: { x: number; y: number };
children: string[];
}): Node {
return new Node(
obj.id,
obj.template,
obj.data,
Position.fromObject(obj.position),
obj.children
);
}
}

View File

@@ -1,4 +0,0 @@
export { Node } from './Node';
export { Connection, type ConnectionType } from './Connection';
export { Blackboard, type BlackboardValue } from './Blackboard';
export { BehaviorTree } from './BehaviorTree';

View File

@@ -1,198 +0,0 @@
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
};
}
}

View File

@@ -1 +0,0 @@
export { TreeValidator } from './TreeValidator';

View File

@@ -1,107 +0,0 @@
/**
* 节点类型值对象
* 封装节点类型的业务逻辑
*/
export class NodeType {
private readonly _value: string;
private constructor(value: string) {
this._value = value;
}
get value(): string {
return this._value;
}
/**
* 是否为根节点
*/
isRoot(): boolean {
return this._value === 'root';
}
/**
* 是否为组合节点(可以有多个子节点)
*/
isComposite(): boolean {
return this._value === 'composite' ||
['sequence', 'selector', 'parallel'].includes(this._value);
}
/**
* 是否为装饰节点(只能有一个子节点)
*/
isDecorator(): boolean {
return this._value === 'decorator' ||
['repeater', 'inverter', 'succeeder', 'failer', 'until-fail', 'until-success'].includes(this._value);
}
/**
* 是否为叶子节点(不能有子节点)
*/
isLeaf(): boolean {
return this._value === 'action' || this._value === 'condition' ||
this._value.includes('action-') || this._value.includes('condition-');
}
/**
* 获取允许的最大子节点数
* @returns 0 表示叶子节点1 表示装饰节点Infinity 表示组合节点
*/
getMaxChildren(): number {
if (this.isLeaf()) {
return 0;
}
if (this.isRoot() || this.isDecorator()) {
return 1;
}
if (this.isComposite()) {
return Infinity;
}
return 0;
}
/**
* 值对象相等性比较
*/
equals(other: NodeType): boolean {
return this._value === other._value;
}
toString(): string {
return this._value;
}
/**
* 预定义的节点类型
*/
static readonly ROOT = new NodeType('root');
static readonly SEQUENCE = new NodeType('sequence');
static readonly SELECTOR = new NodeType('selector');
static readonly PARALLEL = new NodeType('parallel');
static readonly REPEATER = new NodeType('repeater');
static readonly INVERTER = new NodeType('inverter');
static readonly SUCCEEDER = new NodeType('succeeder');
static readonly FAILER = new NodeType('failer');
static readonly UNTIL_FAIL = new NodeType('until-fail');
static readonly UNTIL_SUCCESS = new NodeType('until-success');
/**
* 从字符串创建节点类型
*/
static fromString(value: string): NodeType {
switch (value) {
case 'root': return NodeType.ROOT;
case 'sequence': return NodeType.SEQUENCE;
case 'selector': return NodeType.SELECTOR;
case 'parallel': return NodeType.PARALLEL;
case 'repeater': return NodeType.REPEATER;
case 'inverter': return NodeType.INVERTER;
case 'succeeder': return NodeType.SUCCEEDER;
case 'failer': return NodeType.FAILER;
case 'until-fail': return NodeType.UNTIL_FAIL;
case 'until-success': return NodeType.UNTIL_SUCCESS;
default: return new NodeType(value);
}
}
}

View File

@@ -1,72 +0,0 @@
/**
* 位置值对象
* 表示二维空间中的坐标点
*/
export class Position {
private readonly _x: number;
private readonly _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
get x(): number {
return this._x;
}
get y(): number {
return this._y;
}
/**
* 创建新的位置,加上偏移量
*/
add(offset: Position): Position {
return new Position(this._x + offset._x, this._y + offset._y);
}
/**
* 创建新的位置,减去偏移量
*/
subtract(other: Position): Position {
return new Position(this._x - other._x, this._y - other._y);
}
/**
* 计算到另一个位置的距离
*/
distanceTo(other: Position): number {
const dx = this._x - other._x;
const dy = this._y - other._y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 值对象相等性比较
*/
equals(other: Position): boolean {
return this._x === other._x && this._y === other._y;
}
/**
* 转换为普通对象
*/
toObject(): { x: number; y: number } {
return { x: this._x, y: this._y };
}
/**
* 从普通对象创建
*/
static fromObject(obj: { x: number; y: number }): Position {
return new Position(obj.x, obj.y);
}
/**
* 创建零位置
*/
static zero(): Position {
return new Position(0, 0);
}
}

View File

@@ -1,59 +0,0 @@
/**
* 尺寸值对象
* 表示宽度和高度
*/
export class Size {
private readonly _width: number;
private readonly _height: number;
constructor(width: number, height: number) {
if (width < 0 || height < 0) {
throw new Error('Size dimensions must be non-negative');
}
this._width = width;
this._height = height;
}
get width(): number {
return this._width;
}
get height(): number {
return this._height;
}
/**
* 获取面积
*/
get area(): number {
return this._width * this._height;
}
/**
* 缩放尺寸
*/
scale(factor: number): Size {
return new Size(this._width * factor, this._height * factor);
}
/**
* 值对象相等性比较
*/
equals(other: Size): boolean {
return this._width === other._width && this._height === other._height;
}
/**
* 转换为普通对象
*/
toObject(): { width: number; height: number } {
return { width: this._width, height: this._height };
}
/**
* 从普通对象创建
*/
static fromObject(obj: { width: number; height: number }): Size {
return new Size(obj.width, obj.height);
}
}

View File

@@ -1,3 +0,0 @@
export { Position } from './Position';
export { Size } from './Size';
export { NodeType } from './NodeType';