2025-11-03 21:22:16 +08:00
|
|
|
import { BehaviorTreeExecutor, ExecutionStatus, ExecutionLog } from '../../utils/BehaviorTreeExecutor';
|
2025-11-04 18:29:28 +08:00
|
|
|
import { BehaviorTreeNode, Connection, NodeExecutionStatus } from '../../stores/behaviorTreeStore';
|
2025-11-03 21:22:16 +08:00
|
|
|
import { BlackboardValue } from '../../domain/models/Blackboard';
|
|
|
|
|
import { DOMCache } from '../../presentation/utils/DOMCache';
|
|
|
|
|
import { EditorEventBus, EditorEvent } from '../../infrastructure/events/EditorEventBus';
|
|
|
|
|
import { ExecutionHooksManager } from '../interfaces/IExecutionHooks';
|
|
|
|
|
|
|
|
|
|
export type ExecutionMode = 'idle' | 'running' | 'paused' | 'step';
|
|
|
|
|
type BlackboardVariables = Record<string, BlackboardValue>;
|
|
|
|
|
|
|
|
|
|
interface ExecutionControllerConfig {
|
|
|
|
|
rootNodeId: string;
|
|
|
|
|
projectPath: string | null;
|
|
|
|
|
onLogsUpdate: (logs: ExecutionLog[]) => void;
|
|
|
|
|
onBlackboardUpdate: (variables: BlackboardVariables) => void;
|
|
|
|
|
onTickCountUpdate: (count: number) => void;
|
2025-11-04 18:29:28 +08:00
|
|
|
onExecutionStatusUpdate: (statuses: Map<string, NodeExecutionStatus>, orders: Map<string, number>) => void;
|
2025-11-03 21:22:16 +08:00
|
|
|
eventBus?: EditorEventBus;
|
|
|
|
|
hooksManager?: ExecutionHooksManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ExecutionController {
|
|
|
|
|
private executor: BehaviorTreeExecutor | null = null;
|
|
|
|
|
private mode: ExecutionMode = 'idle';
|
|
|
|
|
private animationFrameId: number | null = null;
|
|
|
|
|
private lastTickTime: number = 0;
|
|
|
|
|
private speed: number = 1.0;
|
|
|
|
|
private tickCount: number = 0;
|
|
|
|
|
|
|
|
|
|
private domCache: DOMCache = new DOMCache();
|
|
|
|
|
private eventBus?: EditorEventBus;
|
|
|
|
|
private hooksManager?: ExecutionHooksManager;
|
|
|
|
|
|
|
|
|
|
private config: ExecutionControllerConfig;
|
|
|
|
|
private currentNodes: BehaviorTreeNode[] = [];
|
|
|
|
|
private currentConnections: Connection[] = [];
|
|
|
|
|
private currentBlackboard: BlackboardVariables = {};
|
|
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
private stepByStepMode: boolean = true;
|
|
|
|
|
private pendingStatusUpdates: ExecutionStatus[] = [];
|
|
|
|
|
private currentlyDisplayedIndex: number = 0;
|
|
|
|
|
private lastStepTime: number = 0;
|
|
|
|
|
private stepInterval: number = 200;
|
|
|
|
|
|
2025-11-03 21:22:16 +08:00
|
|
|
constructor(config: ExecutionControllerConfig) {
|
|
|
|
|
this.config = config;
|
|
|
|
|
this.executor = new BehaviorTreeExecutor();
|
|
|
|
|
this.eventBus = config.eventBus;
|
|
|
|
|
this.hooksManager = config.hooksManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMode(): ExecutionMode {
|
|
|
|
|
return this.mode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getTickCount(): number {
|
|
|
|
|
return this.tickCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getSpeed(): number {
|
|
|
|
|
return this.speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setSpeed(speed: number): void {
|
|
|
|
|
this.speed = speed;
|
2025-11-04 18:29:28 +08:00
|
|
|
this.lastTickTime = 0;
|
2025-11-03 21:22:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async play(
|
|
|
|
|
nodes: BehaviorTreeNode[],
|
|
|
|
|
blackboardVariables: BlackboardVariables,
|
|
|
|
|
connections: Connection[]
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (this.mode === 'running') return;
|
|
|
|
|
|
|
|
|
|
this.currentNodes = nodes;
|
|
|
|
|
this.currentConnections = connections;
|
|
|
|
|
this.currentBlackboard = blackboardVariables;
|
|
|
|
|
|
|
|
|
|
const context = {
|
|
|
|
|
nodes,
|
|
|
|
|
connections,
|
|
|
|
|
blackboardVariables,
|
|
|
|
|
rootNodeId: this.config.rootNodeId,
|
|
|
|
|
tickCount: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await this.hooksManager?.triggerBeforePlay(context);
|
|
|
|
|
|
|
|
|
|
this.mode = 'running';
|
|
|
|
|
this.tickCount = 0;
|
|
|
|
|
this.lastTickTime = 0;
|
|
|
|
|
|
|
|
|
|
if (!this.executor) {
|
|
|
|
|
this.executor = new BehaviorTreeExecutor();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.executor.buildTree(
|
|
|
|
|
nodes,
|
|
|
|
|
this.config.rootNodeId,
|
|
|
|
|
blackboardVariables,
|
|
|
|
|
connections,
|
|
|
|
|
this.handleExecutionStatusUpdate.bind(this)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.executor.start();
|
|
|
|
|
this.animationFrameId = requestAnimationFrame(this.tickLoop.bind(this));
|
|
|
|
|
|
|
|
|
|
this.eventBus?.emit(EditorEvent.EXECUTION_STARTED, context);
|
|
|
|
|
await this.hooksManager?.triggerAfterPlay(context);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error in play:', error);
|
|
|
|
|
await this.hooksManager?.triggerOnError(error as Error, 'play');
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async pause(): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
if (this.mode === 'running') {
|
|
|
|
|
await this.hooksManager?.triggerBeforePause();
|
|
|
|
|
|
|
|
|
|
this.mode = 'paused';
|
|
|
|
|
|
|
|
|
|
if (this.executor) {
|
|
|
|
|
this.executor.pause();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.animationFrameId !== null) {
|
|
|
|
|
cancelAnimationFrame(this.animationFrameId);
|
|
|
|
|
this.animationFrameId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.eventBus?.emit(EditorEvent.EXECUTION_PAUSED);
|
|
|
|
|
await this.hooksManager?.triggerAfterPause();
|
|
|
|
|
} else if (this.mode === 'paused') {
|
|
|
|
|
await this.hooksManager?.triggerBeforeResume();
|
|
|
|
|
|
|
|
|
|
this.mode = 'running';
|
|
|
|
|
this.lastTickTime = 0;
|
|
|
|
|
|
|
|
|
|
if (this.executor) {
|
|
|
|
|
this.executor.resume();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.animationFrameId = requestAnimationFrame(this.tickLoop.bind(this));
|
|
|
|
|
|
|
|
|
|
this.eventBus?.emit(EditorEvent.EXECUTION_RESUMED);
|
|
|
|
|
await this.hooksManager?.triggerAfterResume();
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error in pause/resume:', error);
|
|
|
|
|
await this.hooksManager?.triggerOnError(error as Error, 'pause');
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async stop(): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
await this.hooksManager?.triggerBeforeStop();
|
|
|
|
|
|
|
|
|
|
this.mode = 'idle';
|
|
|
|
|
this.tickCount = 0;
|
|
|
|
|
this.lastTickTime = 0;
|
2025-11-04 18:29:28 +08:00
|
|
|
this.lastStepTime = 0;
|
|
|
|
|
this.pendingStatusUpdates = [];
|
|
|
|
|
this.currentlyDisplayedIndex = 0;
|
2025-11-03 21:22:16 +08:00
|
|
|
|
|
|
|
|
this.domCache.clearAllStatusTimers();
|
|
|
|
|
this.domCache.clearStatusCache();
|
|
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
this.config.onExecutionStatusUpdate(new Map(), new Map());
|
2025-11-03 21:22:16 +08:00
|
|
|
|
|
|
|
|
if (this.animationFrameId !== null) {
|
|
|
|
|
cancelAnimationFrame(this.animationFrameId);
|
|
|
|
|
this.animationFrameId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.executor) {
|
|
|
|
|
this.executor.stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.eventBus?.emit(EditorEvent.EXECUTION_STOPPED);
|
|
|
|
|
await this.hooksManager?.triggerAfterStop();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error in stop:', error);
|
|
|
|
|
await this.hooksManager?.triggerOnError(error as Error, 'stop');
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async reset(): Promise<void> {
|
|
|
|
|
await this.stop();
|
|
|
|
|
|
|
|
|
|
if (this.executor) {
|
|
|
|
|
this.executor.cleanup();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
step(): void {
|
|
|
|
|
// 单步执行功能预留
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateBlackboardVariable(key: string, value: BlackboardValue): void {
|
|
|
|
|
if (this.executor && this.mode !== 'idle') {
|
|
|
|
|
this.executor.updateBlackboardVariable(key, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getBlackboardVariables(): BlackboardVariables {
|
|
|
|
|
if (this.executor) {
|
|
|
|
|
return this.executor.getBlackboardVariables();
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
updateNodes(nodes: BehaviorTreeNode[]): void {
|
|
|
|
|
if (this.mode === 'idle' || !this.executor) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.currentNodes = nodes;
|
|
|
|
|
|
|
|
|
|
this.executor.buildTree(
|
|
|
|
|
nodes,
|
|
|
|
|
this.config.rootNodeId,
|
|
|
|
|
this.currentBlackboard,
|
|
|
|
|
this.currentConnections,
|
|
|
|
|
this.handleExecutionStatusUpdate.bind(this)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.executor.start();
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 21:22:16 +08:00
|
|
|
clearDOMCache(): void {
|
|
|
|
|
this.domCache.clearAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroy(): void {
|
|
|
|
|
this.stop();
|
|
|
|
|
|
|
|
|
|
if (this.executor) {
|
|
|
|
|
this.executor.destroy();
|
|
|
|
|
this.executor = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private tickLoop(currentTime: number): void {
|
|
|
|
|
if (this.mode !== 'running') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this.executor) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
if (this.stepByStepMode) {
|
|
|
|
|
this.handleStepByStepExecution(currentTime);
|
|
|
|
|
} else {
|
|
|
|
|
this.handleNormalExecution(currentTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.animationFrameId = requestAnimationFrame(this.tickLoop.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private handleNormalExecution(currentTime: number): void {
|
2025-11-03 21:22:16 +08:00
|
|
|
const baseTickInterval = 16.67;
|
2025-11-04 18:29:28 +08:00
|
|
|
const scaledTickInterval = baseTickInterval / this.speed;
|
|
|
|
|
|
|
|
|
|
if (this.lastTickTime === 0) {
|
|
|
|
|
this.lastTickTime = currentTime;
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
const elapsed = currentTime - this.lastTickTime;
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
if (elapsed >= scaledTickInterval) {
|
|
|
|
|
const deltaTime = baseTickInterval / 1000;
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
this.executor!.tick(deltaTime);
|
|
|
|
|
|
|
|
|
|
this.tickCount = this.executor!.getTickCount();
|
2025-11-03 21:22:16 +08:00
|
|
|
this.config.onTickCountUpdate(this.tickCount);
|
|
|
|
|
|
|
|
|
|
this.lastTickTime = currentTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
private handleStepByStepExecution(currentTime: number): void {
|
|
|
|
|
if (this.lastStepTime === 0) {
|
|
|
|
|
this.lastStepTime = currentTime;
|
2025-11-03 21:22:16 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
const stepElapsed = currentTime - this.lastStepTime;
|
|
|
|
|
const actualStepInterval = this.stepInterval / this.speed;
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
if (stepElapsed >= actualStepInterval) {
|
|
|
|
|
if (this.currentlyDisplayedIndex < this.pendingStatusUpdates.length) {
|
|
|
|
|
this.displayNextNode();
|
|
|
|
|
this.lastStepTime = currentTime;
|
|
|
|
|
} else {
|
|
|
|
|
if (this.lastTickTime === 0) {
|
|
|
|
|
this.lastTickTime = currentTime;
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
const tickElapsed = currentTime - this.lastTickTime;
|
|
|
|
|
const baseTickInterval = 16.67;
|
|
|
|
|
const scaledTickInterval = baseTickInterval / this.speed;
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
if (tickElapsed >= scaledTickInterval) {
|
|
|
|
|
const deltaTime = baseTickInterval / 1000;
|
|
|
|
|
this.executor!.tick(deltaTime);
|
|
|
|
|
this.tickCount = this.executor!.getTickCount();
|
|
|
|
|
this.config.onTickCountUpdate(this.tickCount);
|
|
|
|
|
this.lastTickTime = currentTime;
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
}
|
2025-11-04 18:29:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
private displayNextNode(): void {
|
|
|
|
|
if (this.currentlyDisplayedIndex >= this.pendingStatusUpdates.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
const statusesToDisplay = this.pendingStatusUpdates.slice(0, this.currentlyDisplayedIndex + 1);
|
|
|
|
|
const currentNode = this.pendingStatusUpdates[this.currentlyDisplayedIndex];
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
if (!currentNode) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
const statusMap = new Map<string, NodeExecutionStatus>();
|
|
|
|
|
const orderMap = new Map<string, number>();
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
statusesToDisplay.forEach((s) => {
|
|
|
|
|
statusMap.set(s.nodeId, s.status);
|
|
|
|
|
if (s.executionOrder !== undefined) {
|
|
|
|
|
orderMap.set(s.nodeId, s.executionOrder);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
const nodeName = this.currentNodes.find(n => n.id === currentNode.nodeId)?.template.displayName || 'Unknown';
|
|
|
|
|
console.log(`[StepByStep] Displaying ${this.currentlyDisplayedIndex + 1}/${this.pendingStatusUpdates.length} | ${nodeName} | Order: ${currentNode.executionOrder} | ID: ${currentNode.nodeId}`);
|
|
|
|
|
this.config.onExecutionStatusUpdate(statusMap, orderMap);
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
this.currentlyDisplayedIndex++;
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
private handleExecutionStatusUpdate(
|
|
|
|
|
statuses: ExecutionStatus[],
|
|
|
|
|
logs: ExecutionLog[],
|
|
|
|
|
runtimeBlackboardVars?: BlackboardVariables
|
|
|
|
|
): void {
|
|
|
|
|
this.config.onLogsUpdate([...logs]);
|
|
|
|
|
|
|
|
|
|
if (runtimeBlackboardVars) {
|
|
|
|
|
this.config.onBlackboardUpdate(runtimeBlackboardVars);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.stepByStepMode) {
|
|
|
|
|
const statusesWithOrder = statuses.filter(s => s.executionOrder !== undefined);
|
|
|
|
|
|
|
|
|
|
if (statusesWithOrder.length > 0) {
|
|
|
|
|
const minOrder = Math.min(...statusesWithOrder.map(s => s.executionOrder!));
|
|
|
|
|
|
|
|
|
|
if (minOrder === 1 || this.pendingStatusUpdates.length === 0) {
|
|
|
|
|
this.pendingStatusUpdates = statusesWithOrder.sort((a, b) =>
|
|
|
|
|
(a.executionOrder || 0) - (b.executionOrder || 0)
|
|
|
|
|
);
|
|
|
|
|
this.currentlyDisplayedIndex = 0;
|
|
|
|
|
this.lastStepTime = 0;
|
|
|
|
|
} else {
|
|
|
|
|
const maxExistingOrder = this.pendingStatusUpdates.length > 0
|
|
|
|
|
? Math.max(...this.pendingStatusUpdates.map(s => s.executionOrder || 0))
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
const newStatuses = statusesWithOrder.filter(s =>
|
|
|
|
|
(s.executionOrder || 0) > maxExistingOrder
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (newStatuses.length > 0) {
|
|
|
|
|
console.log(`[StepByStep] Appending ${newStatuses.length} new nodes, orders:`, newStatuses.map(s => s.executionOrder));
|
|
|
|
|
this.pendingStatusUpdates = [
|
|
|
|
|
...this.pendingStatusUpdates,
|
|
|
|
|
...newStatuses
|
|
|
|
|
].sort((a, b) => (a.executionOrder || 0) - (b.executionOrder || 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
}
|
2025-11-04 18:29:28 +08:00
|
|
|
} else {
|
|
|
|
|
const statusMap = new Map<string, NodeExecutionStatus>();
|
|
|
|
|
const orderMap = new Map<string, number>();
|
|
|
|
|
|
|
|
|
|
statuses.forEach((s) => {
|
|
|
|
|
statusMap.set(s.nodeId, s.status);
|
|
|
|
|
if (s.executionOrder !== undefined) {
|
|
|
|
|
orderMap.set(s.nodeId, s.executionOrder);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-11-03 21:22:16 +08:00
|
|
|
|
2025-11-04 18:29:28 +08:00
|
|
|
this.config.onExecutionStatusUpdate(statusMap, orderMap);
|
|
|
|
|
}
|
2025-11-03 21:22:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private updateConnectionStyles(
|
|
|
|
|
statusMap: Record<string, NodeExecutionStatus>,
|
|
|
|
|
connections?: Connection[]
|
|
|
|
|
): void {
|
|
|
|
|
if (!connections) return;
|
|
|
|
|
|
|
|
|
|
connections.forEach((conn) => {
|
|
|
|
|
const connKey = `${conn.from}-${conn.to}`;
|
|
|
|
|
|
|
|
|
|
const pathElement = this.domCache.getConnection(connKey);
|
|
|
|
|
if (!pathElement) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fromStatus = statusMap[conn.from];
|
|
|
|
|
const toStatus = statusMap[conn.to];
|
|
|
|
|
const isActive = fromStatus === 'running' || toStatus === 'running';
|
|
|
|
|
|
|
|
|
|
if (conn.connectionType === 'property') {
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke', '#9c27b0');
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke-width', '2');
|
|
|
|
|
} else if (isActive) {
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke', '#ffa726');
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke-width', '3');
|
|
|
|
|
} else {
|
|
|
|
|
const isExecuted = this.domCache.hasNodeClass(conn.from, 'executed') &&
|
|
|
|
|
this.domCache.hasNodeClass(conn.to, 'executed');
|
|
|
|
|
|
|
|
|
|
if (isExecuted) {
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke', '#4caf50');
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke-width', '2.5');
|
|
|
|
|
} else {
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke', '#0e639c');
|
|
|
|
|
this.domCache.setConnectionAttribute(connKey, 'stroke-width', '2');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setConnections(connections: Connection[]): void {
|
|
|
|
|
if (this.mode !== 'idle') {
|
|
|
|
|
const currentStatuses: Record<string, NodeExecutionStatus> = {};
|
|
|
|
|
connections.forEach((conn) => {
|
|
|
|
|
const fromStatus = this.domCache.getLastStatus(conn.from);
|
|
|
|
|
const toStatus = this.domCache.getLastStatus(conn.to);
|
|
|
|
|
if (fromStatus) currentStatuses[conn.from] = fromStatus;
|
|
|
|
|
if (toStatus) currentStatuses[conn.to] = toStatus;
|
|
|
|
|
});
|
|
|
|
|
this.updateConnectionStyles(currentStatuses, connections);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|