mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 02:36:02 +10:00
149 lines
5.3 KiB
JavaScript
149 lines
5.3 KiB
JavaScript
/**
|
|
* @typedef {import('./NodeRenderer.js').NodeRenderer} NodeRenderer
|
|
* @typedef {(nodeId: string, value: string) => void} PrintHandler
|
|
* @typedef {(connId: string, kind: string) => Promise<void>} StepHandler
|
|
*/
|
|
|
|
import { GlobalVariables } from './GlobalVariables.js';
|
|
import { NodeRegistry, NodeExecutionContext } from './nodes/index.js';
|
|
|
|
/**
|
|
* Walks exec connections from a starting output pin and runs node logic
|
|
* for recognised node types (print, etc.).
|
|
*
|
|
* Execution is fully async — each exec hop awaits the optional onStep callback,
|
|
* allowing callers to animate wires between steps.
|
|
*
|
|
* Resolution order for string values:
|
|
* 1. The defaultValue on the output pin of the connected source node.
|
|
* 2. The defaultValue on the input pin itself (unconnected fallback).
|
|
*/
|
|
export class GraphExecutor {
|
|
/** @type {NodeRenderer} */
|
|
#nodeRenderer;
|
|
|
|
/** @type {PrintHandler | null} */
|
|
#onPrint;
|
|
|
|
/**
|
|
* Called with (connId, kind) before traversing each connection.
|
|
* Awaited for exec wires; fire-and-forget for data wires.
|
|
*
|
|
* @type {StepHandler | null}
|
|
*/
|
|
#onStep;
|
|
|
|
/**
|
|
* @param {NodeRenderer} nodeRenderer
|
|
* @param {PrintHandler | null} [onPrint] - Invoked when a print node executes.
|
|
* @param {StepHandler | null} [onStep] - Awaited when traversing exec connections.
|
|
*/
|
|
constructor(nodeRenderer, onPrint = null, onStep = null) {
|
|
this.#nodeRenderer = nodeRenderer;
|
|
this.#onPrint = onPrint;
|
|
this.#onStep = onStep;
|
|
}
|
|
|
|
// ─── Public ───────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Starts async execution from a node's exec output pin.
|
|
*
|
|
* @param {string} nodeId
|
|
* @param {string} [pinId]
|
|
* @returns {Promise<void>}
|
|
*/
|
|
execute(nodeId, pinId = "exec_out") {
|
|
return this.#runExecPin(nodeId, pinId);
|
|
}
|
|
|
|
// ─── Private ──────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Follows a single exec output connection, animates it, then executes the target node.
|
|
*
|
|
* @param {string} nodeId
|
|
* @param {string} pinId
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async #runExecPin(nodeId, pinId) {
|
|
const conn = this.#nodeRenderer.getConnections()
|
|
.find(c => c.from.nodeId === nodeId && c.from.pinId === pinId);
|
|
if (!conn) return;
|
|
if (this.#onStep) await this.#onStep(conn.id, conn.kind);
|
|
await this.#executeNode(conn.to.nodeId);
|
|
}
|
|
|
|
/**
|
|
* Executes the logic for a single node then advances through exec_out.
|
|
*
|
|
* @param {string} nodeId
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async #executeNode(nodeId) {
|
|
const node = this.#nodeRenderer.getNodes().find(n => n.id === nodeId);
|
|
if (!node) return;
|
|
|
|
const ctx = new NodeExecutionContext(
|
|
nodeId,
|
|
this.#nodeRenderer,
|
|
(nId, pId) => this.#resolveInput(nId, pId),
|
|
(nId, pId) => this.#runExecPin(nId, pId),
|
|
this.#onPrint,
|
|
this.#onStep
|
|
);
|
|
|
|
const handler = NodeRegistry.BlueprintPure_Get(node.type);
|
|
if (handler) {
|
|
try {
|
|
await handler.BlueprintCallable_Execute(ctx);
|
|
} catch (error) {
|
|
console.error(`[Node ${nodeId}] Execution error:`, error);
|
|
}
|
|
}
|
|
|
|
// Always continue exec flow
|
|
await this.#runExecPin(nodeId, "exec_out");
|
|
}
|
|
|
|
/**
|
|
* Resolves an input value by following connections and handling special node types.
|
|
*
|
|
* @param {string} nodeId
|
|
* @param {string} pinId
|
|
* @returns {any}
|
|
*/
|
|
#resolveInput(nodeId, pinId) {
|
|
const conn = this.#nodeRenderer.getConnections().find(
|
|
c => c.to.nodeId === nodeId && c.to.pinId === pinId
|
|
);
|
|
|
|
if (conn) {
|
|
// Auto-flash data wire when a value is resolved (fire-and-forget)
|
|
this.#onStep?.(conn.id, conn.kind);
|
|
const sourceNode = this.#nodeRenderer.getNodes().find(n => n.id === conn.from.nodeId);
|
|
const sourcePin = sourceNode?.getPin(conn.from.pinId);
|
|
|
|
if (sourceNode?.type === "get_matrix_chat" && conn.from.pinId === "value") {
|
|
if (GlobalVariables.has("MatrixChat")) return GlobalVariables.get("MatrixChat");
|
|
}
|
|
|
|
if (sourceNode?.type === "random_name" && conn.from.pinId === "name") {
|
|
return sourceNode._generatedName || "";
|
|
}
|
|
|
|
if (sourceNode?.type === "append" && conn.from.pinId === "result") {
|
|
const input1 = this.#resolveInput(sourceNode.id, "input1");
|
|
const input2 = this.#resolveInput(sourceNode.id, "input2");
|
|
const input3 = this.#resolveInput(sourceNode.id, "input3");
|
|
return input1 + input2 + input3;
|
|
}
|
|
|
|
if (sourcePin?.defaultValue != null) return sourcePin.defaultValue;
|
|
}
|
|
|
|
const node = this.#nodeRenderer.getNodes().find(n => n.id === nodeId);
|
|
return node?.getPin(pinId)?.defaultValue ?? "";
|
|
}
|
|
}
|