2024-04-04 19:17:27 +02:00
|
|
|
import type { Graph, NodeRegistry, NodeType, RuntimeExecutor } from "@nodes/types";
|
2024-04-15 18:46:34 +02:00
|
|
|
import { encodeFloat } from "./helpers/encode";
|
2024-04-16 15:32:23 +02:00
|
|
|
import { concat_encoded } from "./helpers/flat_tree";
|
|
|
|
import { fastHash } from "./helpers/fastHash";
|
2024-04-15 22:13:43 +02:00
|
|
|
|
|
|
|
|
2024-04-05 16:45:57 +02:00
|
|
|
|
2024-03-19 16:41:15 +01:00
|
|
|
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|
|
|
|
2024-04-15 14:11:54 +02:00
|
|
|
private typeMap: Map<string, NodeType> = new Map();
|
|
|
|
|
2024-04-15 22:13:43 +02:00
|
|
|
private cache: Record<string, { eol: number, value: any }> = {};
|
|
|
|
|
2024-03-20 17:39:52 +01:00
|
|
|
constructor(private registry: NodeRegistry) { }
|
2024-03-19 16:41:15 +01:00
|
|
|
|
|
|
|
private getNodeTypes(graph: Graph) {
|
2024-04-10 21:57:03 +02:00
|
|
|
|
|
|
|
if (this.registry.status !== "ready") {
|
|
|
|
throw new Error("Node registry is not ready");
|
|
|
|
}
|
|
|
|
|
2024-03-19 16:41:15 +01:00
|
|
|
const typeMap = new Map<string, NodeType>();
|
|
|
|
for (const node of graph.nodes) {
|
|
|
|
if (!typeMap.has(node.type)) {
|
|
|
|
const type = this.registry.getNode(node.type);
|
|
|
|
if (type) {
|
|
|
|
typeMap.set(node.type, type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return typeMap;
|
|
|
|
}
|
|
|
|
|
2024-04-04 19:17:27 +02:00
|
|
|
private addMetaData(graph: Graph) {
|
2024-03-19 16:41:15 +01:00
|
|
|
|
|
|
|
// First, lets check if all nodes have a type
|
2024-04-15 14:11:54 +02:00
|
|
|
this.typeMap = this.getNodeTypes(graph);
|
2024-03-19 16:41:15 +01:00
|
|
|
|
2024-04-05 18:03:23 +02:00
|
|
|
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
2024-03-19 16:41:15 +01:00
|
|
|
if (!outputNode) {
|
|
|
|
throw new Error("No output node found");
|
|
|
|
}
|
|
|
|
outputNode.tmp = outputNode.tmp || {};
|
|
|
|
outputNode.tmp.depth = 0;
|
|
|
|
|
|
|
|
const nodeMap = new Map(graph.nodes.map(node => [node.id, node]));
|
|
|
|
|
|
|
|
// loop through all edges and assign the parent and child nodes to each node
|
|
|
|
for (const edge of graph.edges) {
|
|
|
|
const [parentId, _parentOutput, childId, childInput] = edge;
|
|
|
|
const parent = nodeMap.get(parentId);
|
|
|
|
const child = nodeMap.get(childId);
|
|
|
|
if (parent && child) {
|
|
|
|
parent.tmp = parent.tmp || {};
|
|
|
|
parent.tmp.children = parent.tmp.children || [];
|
|
|
|
parent.tmp.children.push(child);
|
|
|
|
child.tmp = child.tmp || {};
|
|
|
|
child.tmp.parents = child.tmp.parents || [];
|
|
|
|
child.tmp.parents.push(parent);
|
|
|
|
child.tmp.inputNodes = child.tmp.inputNodes || {};
|
|
|
|
child.tmp.inputNodes[childInput] = parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const nodes = []
|
|
|
|
|
|
|
|
// loop through all the nodes and assign each nodes its depth
|
|
|
|
const stack = [outputNode];
|
|
|
|
while (stack.length) {
|
|
|
|
const node = stack.pop();
|
|
|
|
if (node) {
|
|
|
|
node.tmp = node.tmp || {};
|
|
|
|
|
|
|
|
if (node?.tmp?.depth === undefined) {
|
|
|
|
node.tmp.depth = 0;
|
|
|
|
}
|
|
|
|
if (node?.tmp?.parents !== undefined) {
|
|
|
|
for (const parent of node.tmp.parents) {
|
|
|
|
parent.tmp = parent.tmp || {};
|
|
|
|
if (parent.tmp?.depth === undefined) {
|
|
|
|
parent.tmp.depth = node.tmp.depth + 1;
|
|
|
|
stack.push(parent);
|
|
|
|
} else {
|
|
|
|
parent.tmp.depth = Math.max(parent.tmp.depth, node.tmp.depth + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nodes.push(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [outputNode, nodes] as const;
|
|
|
|
}
|
|
|
|
|
|
|
|
execute(graph: Graph) {
|
|
|
|
|
|
|
|
// Then we add some metadata to the graph
|
|
|
|
const [outputNode, nodes] = this.addMetaData(graph);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Here we sort the nodes into buckets, which we then execute one by one
|
|
|
|
* +-b2-+-b1-+---b0---+
|
|
|
|
* | | | |
|
|
|
|
* | n3 | n2 | Output |
|
|
|
|
* | n6 | n4 | Level |
|
|
|
|
* | | n5 | |
|
|
|
|
* | | | |
|
|
|
|
* +----+----+--------+
|
|
|
|
*/
|
|
|
|
|
|
|
|
// we execute the nodes from the bottom up
|
|
|
|
const sortedNodes = nodes.sort((a, b) => (b.tmp?.depth || 0) - (a.tmp?.depth || 0));
|
|
|
|
|
|
|
|
// here we store the intermediate results of the nodes
|
|
|
|
const results: Record<string, string | boolean | number> = {};
|
|
|
|
|
2024-04-15 22:13:43 +02:00
|
|
|
console.log(this.cache);
|
|
|
|
|
2024-03-19 16:41:15 +01:00
|
|
|
for (const node of sortedNodes) {
|
2024-04-15 14:11:54 +02:00
|
|
|
|
|
|
|
const node_type = this.typeMap.get(node.type)!;
|
|
|
|
|
|
|
|
if (node?.tmp && node_type?.execute) {
|
2024-03-19 16:41:15 +01:00
|
|
|
const inputs: Record<string, string | number | boolean> = {};
|
2024-04-15 14:11:54 +02:00
|
|
|
for (const [key, input] of Object.entries(node_type.inputs || {})) {
|
2024-03-19 16:41:15 +01:00
|
|
|
|
2024-04-05 19:38:10 +02:00
|
|
|
if (input.type === "seed") {
|
|
|
|
inputs[key] = Math.floor(Math.random() * 100000000);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-03-19 16:41:15 +01:00
|
|
|
// check if the input is connected to another node
|
|
|
|
const inputNode = node.tmp.inputNodes?.[key];
|
|
|
|
if (inputNode) {
|
|
|
|
if (results[inputNode.id] === undefined) {
|
|
|
|
throw new Error("Input node has no result");
|
|
|
|
}
|
|
|
|
inputs[key] = results[inputNode.id];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the input is not connected to another node, we use the value from the node itself
|
|
|
|
inputs[key] = node.props?.[key] ?? input?.value;
|
2024-04-05 19:38:10 +02:00
|
|
|
|
2024-03-19 16:41:15 +01:00
|
|
|
}
|
|
|
|
|
2024-04-15 22:13:43 +02:00
|
|
|
|
|
|
|
console.log(" ");
|
|
|
|
console.log("--> EXECUTING NODE " + node_type.id, node.id);
|
|
|
|
|
|
|
|
|
2024-03-19 16:41:15 +01:00
|
|
|
// execute the node and store the result
|
2024-04-10 23:50:41 +02:00
|
|
|
try {
|
2024-04-15 22:13:43 +02:00
|
|
|
const a0 = performance.now();
|
|
|
|
|
2024-04-15 14:11:54 +02:00
|
|
|
const node_inputs = Object.entries(inputs);
|
2024-04-16 13:30:14 +02:00
|
|
|
const cacheKey = "123" || `${node.id}/${fastHash(node_inputs.map(([_, value]: [string, any]) => {
|
|
|
|
return value
|
|
|
|
}))}`;
|
2024-04-15 22:13:43 +02:00
|
|
|
|
|
|
|
const a1 = performance.now();
|
|
|
|
console.log(`${a1 - a0}ms hashed inputs: ${node.id} -> ${cacheKey}`);
|
|
|
|
|
2024-04-16 13:30:14 +02:00
|
|
|
if (false && this.cache[cacheKey] && this.cache[cacheKey].eol > Date.now()) {
|
2024-04-15 22:13:43 +02:00
|
|
|
results[node.id] = this.cache[cacheKey].value;
|
|
|
|
console.log(`Using cached value`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-04-15 18:46:34 +02:00
|
|
|
const transformed_inputs = node_inputs.map(([key, value]: [string, any]) => {
|
|
|
|
const input_type = node_type.inputs?.[key]!;
|
|
|
|
if (value instanceof Int32Array) {
|
2024-04-15 22:13:43 +02:00
|
|
|
let _v = new Array(value.length);
|
|
|
|
for (let i = 0; i < value.length; i++) {
|
|
|
|
_v[i] = value[i];
|
|
|
|
}
|
|
|
|
return _v;
|
2024-04-15 14:11:54 +02:00
|
|
|
}
|
2024-04-15 18:46:34 +02:00
|
|
|
|
|
|
|
if (input_type.type === "float") {
|
2024-04-16 15:32:23 +02:00
|
|
|
return encodeFloat(value as number);
|
2024-04-15 18:46:34 +02:00
|
|
|
}
|
2024-04-15 22:13:43 +02:00
|
|
|
|
2024-04-15 18:46:34 +02:00
|
|
|
return value;
|
2024-04-15 14:11:54 +02:00
|
|
|
});
|
2024-04-15 22:13:43 +02:00
|
|
|
|
|
|
|
const a2 = performance.now();
|
|
|
|
|
|
|
|
console.log(`${a2 - a1}ms TRANSFORMED_INPUTS`);
|
|
|
|
|
2024-04-15 18:46:34 +02:00
|
|
|
const _inputs = concat_encoded(transformed_inputs);
|
2024-04-15 22:13:43 +02:00
|
|
|
const a3 = performance.now();
|
2024-04-15 18:46:34 +02:00
|
|
|
results[node.id] = node_type.execute(_inputs) as number;
|
2024-04-15 22:13:43 +02:00
|
|
|
const duration = performance.now() - a3;
|
|
|
|
if (duration > 5) {
|
|
|
|
this.cache[cacheKey] = { eol: Date.now() + 10_000, value: results[node.id] };
|
|
|
|
console.log(`Caching for 10 seconds`);
|
|
|
|
}
|
|
|
|
console.log(`${duration}ms Executed`);
|
|
|
|
const a4 = performance.now();
|
|
|
|
console.log(`${a4 - a0}ms e2e duration`);
|
2024-04-10 23:50:41 +02:00
|
|
|
} catch (e) {
|
2024-04-15 14:11:54 +02:00
|
|
|
console.error(`Error executing node ${node_type.id || node.id}`, e);
|
2024-04-10 23:50:41 +02:00
|
|
|
}
|
2024-03-19 16:41:15 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return the result of the parent of the output node
|
2024-04-16 15:32:23 +02:00
|
|
|
const res = results[outputNode.id];
|
2024-04-05 16:45:57 +02:00
|
|
|
|
2024-04-16 15:32:23 +02:00
|
|
|
return res as unknown as Int32Array;
|
2024-03-19 16:41:15 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|