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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export { Node } from './Node';
|
||||
export { Connection, type ConnectionType } from './Connection';
|
||||
export { Blackboard, type BlackboardValue } from './Blackboard';
|
||||
export { BehaviorTree } from './BehaviorTree';
|
||||
Reference in New Issue
Block a user