diff --git a/app/src/lib/graph-interface/graph/Graph.svelte b/app/src/lib/graph-interface/graph/Graph.svelte
index 272bc1c..ddf493a 100644
--- a/app/src/lib/graph-interface/graph/Graph.svelte
+++ b/app/src/lib/graph-interface/graph/Graph.svelte
@@ -7,7 +7,7 @@
import { getContext, onMount, setContext } from "svelte";
import Camera from "../Camera.svelte";
import GraphView from "./GraphView.svelte";
- import type { Node, Node as NodeType, Socket } from "@nodes/types";
+ import type { Node, NodeId, Node as NodeType, Socket } from "@nodes/types";
import { NodeDefinitionSchema } from "@nodes/types";
import FloatingEdge from "../edges/FloatingEdge.svelte";
import {
@@ -783,7 +783,7 @@
event.preventDefault();
isDragging = false;
if (!event.dataTransfer) return;
- const nodeId = event.dataTransfer.getData("data/node-id");
+ const nodeId: NodeId = event.dataTransfer.getData("data/node-id");
if (nodeId) {
let mx = event.clientX - rect.x;
@@ -805,7 +805,7 @@
}
const pos = projectScreenToWorld(mx, my);
- graph.loadNode(nodeId).then(() => {
+ graph.load([nodeId]).then(() => {
graph.createNode({
type: nodeId,
props,
diff --git a/app/src/lib/node-registry-client.ts b/app/src/lib/node-registry-client.ts
index 11f12df..529b905 100644
--- a/app/src/lib/node-registry-client.ts
+++ b/app/src/lib/node-registry-client.ts
@@ -11,17 +11,6 @@ export class RemoteNodeRegistry implements NodeRegistry {
constructor(private url: string) { }
async loadNode(id: `${string}/${string}/${string}`) {
- const wasmResponse = await this.fetchNode(id);
-
- const wrapper = createWasmWrapper(wasmResponse);
-
- const definition = wrapper.get_definition();
-
- return {
- ...definition,
- id,
- execute: wrapper.execute
- };
}
async fetchUsers() {
@@ -67,7 +56,20 @@ export class RemoteNodeRegistry implements NodeRegistry {
async load(nodeIds: `${string}/${string}/${string}`[]) {
const a = performance.now();
- const nodes = await Promise.all(nodeIds.map(id => this.loadNode(id)));
+ const nodes = await Promise.all(nodeIds.map(async id => {
+
+ const wasmResponse = await this.fetchNode(id);
+
+ const wrapper = createWasmWrapper(wasmResponse);
+
+ const definition = wrapper.get_definition();
+
+ return {
+ ...definition,
+ id,
+ execute: wrapper.execute
+ };
+ }));
for (const node of nodes) {
this.nodes.set(node.id, node);
diff --git a/app/src/lib/performance/PerformanceViewer.svelte b/app/src/lib/performance/PerformanceViewer.svelte
new file mode 100644
index 0000000..9810b90
--- /dev/null
+++ b/app/src/lib/performance/PerformanceViewer.svelte
@@ -0,0 +1,17 @@
+
+
+{#if $store.runs.length !== 0}
+ {#each getPerformanceData() as [key, value]}
+
{key}: {Math.floor(value * 100) / 100}ms
+ {/each}
+{:else}
+ No runs available
+{/if}
diff --git a/app/src/lib/performance/index.ts b/app/src/lib/performance/index.ts
new file mode 100644
index 0000000..694e160
--- /dev/null
+++ b/app/src/lib/performance/index.ts
@@ -0,0 +1,62 @@
+import { readable, type Readable } from "svelte/store";
+
+type PerformanceData = {
+ total: Record;
+ runs: Record[];
+}
+export interface PerformanceStore extends Readable {
+ startRun(): void;
+ stopRun(): void;
+ addPoint(name: string, value?: number): void;
+}
+
+export function createPerformanceStore(): PerformanceStore {
+
+ let data: PerformanceData = { total: {}, runs: [] };
+
+ let currentRun: Record | undefined;
+
+ let set: (v: PerformanceData) => void;
+
+ const { subscribe } = readable({ total: {}, runs: [] }, (_set) => {
+ set = _set;
+ });
+
+ function startRun() {
+ currentRun = {};
+ }
+
+ function stopRun() {
+ if (currentRun) {
+ // Calculate total
+ Object.keys(currentRun).forEach((name) => {
+ if (!currentRun?.[name]?.length) return;
+ let runTotal = currentRun[name].reduce((a, b) => a + b, 0) / currentRun[name].length;
+ if (!data.total[name]) {
+ data.total[name] = runTotal;
+ } else {
+ data.total[name] = (data.total[name] + runTotal) / 2;
+ }
+ });
+
+ data.runs.push(currentRun);
+ currentRun = undefined;
+ set(data);
+ }
+ }
+
+ function addPoint(name: string, value: number) {
+ if (!currentRun) return;
+ currentRun[name] = currentRun[name] || [];
+ currentRun[name].push(value);
+ }
+
+ return {
+ subscribe,
+ startRun,
+ stopRun,
+ addPoint,
+ }
+}
+
+export { default as PerformanceViewer } from "./PerformanceViewer.svelte";
diff --git a/app/src/lib/runtime-executor.ts b/app/src/lib/runtime-executor.ts
index 2dd8ea3..1fb847d 100644
--- a/app/src/lib/runtime-executor.ts
+++ b/app/src/lib/runtime-executor.ts
@@ -1,16 +1,51 @@
-import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor } from "@nodes/types";
-import { fastHash, concatEncodedArrays, encodeFloat, decodeNestedArray } from "@nodes/utils"
+import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor, NodeInput } from "@nodes/types";
+import { concatEncodedArrays, encodeFloat, fastHashArray } from "@nodes/utils"
import { createLogger } from "./helpers";
+import type { RuntimeCache } from "@nodes/types";
+import type { PerformanceStore } from "./performance";
const log = createLogger("runtime-executor");
+log.mute()
+
+function getValue(input: NodeInput, value?: unknown) {
+ if (value === undefined && "value" in input) {
+ value = input.value
+ }
+ if (input.type === "float") {
+ return encodeFloat(value as number);
+ }
+
+ if (Array.isArray(value)) {
+ if (input.type === "vec3") {
+ return [0, value.length + 1, ...value.map(v => encodeFloat(v)), 1, 1] as number[];
+ }
+ return [0, value.length + 1, ...value, 1, 1] as number[];
+ }
+
+ if (typeof value === "boolean") {
+ return value ? 1 : 0;
+ }
+
+ if (typeof value === "number") {
+ return value;
+ }
+
+ if (value instanceof Int32Array) {
+ return value;
+ }
+
+ throw new Error(`Unknown input type ${input.type}`);
+}
export class MemoryRuntimeExecutor implements RuntimeExecutor {
private definitionMap: Map = new Map();
- private cache: Record = {};
+ private randomSeed = Math.floor(Math.random() * 100000000);
- constructor(private registry: NodeRegistry) { }
+ perf?: PerformanceStore;
+
+ constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { }
private getNodeDefinitions(graph: Graph) {
@@ -67,26 +102,23 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
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);
- }
+ if (!node) continue;
+ 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);
}
+ nodes.push(node);
}
return [outputNode, nodes] as const;
@@ -94,8 +126,17 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
execute(graph: Graph, settings: Record) {
+ this.perf?.startRun();
+
+ let a0 = performance.now();
+
+ let a = performance.now();
+
// Then we add some metadata to the graph
const [outputNode, nodes] = this.addMetaData(graph);
+ let b = performance.now();
+
+ this.perf?.addPoint("metadata", b - a);
/*
* Here we sort the nodes into buckets, which we then execute one by one
@@ -114,100 +155,109 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// here we store the intermediate results of the nodes
const results: Record = {};
- const runSeed = settings["randomSeed"] === true ? Math.floor(Math.random() * 100000000) : 5120983;
-
for (const node of sortedNodes) {
const node_type = this.definitionMap.get(node.type)!;
- if (node?.tmp && node_type?.execute) {
+ if (!node_type || !node.tmp || !node_type.execute) {
+ log.warn(`Node ${node.id} has no definition`);
+ continue;
+ };
- const inputs = Object.entries(node_type.inputs || {}).map(([key, input]) => {
+ a = performance.now();
- if (input.type === "seed") {
- return runSeed;
+ // Collect the inputs for the node
+ const inputs = Object.entries(node_type.inputs || {}).map(([key, input]) => {
+
+ if (input.type === "seed") {
+ if (settings["randomSeed"] === true) {
+ return Math.floor(Math.random() * 100000000)
+ } else {
+ return this.randomSeed
}
-
- if (input.setting) {
- if (settings[input.setting] === undefined) {
- if ("value" in input && input.value !== undefined) {
- if (input.type === "float") {
- return encodeFloat(input.value);
- }
- return input.value;
- } else {
- log.warn(`Setting ${input.setting} is not defined`);
- }
- } else {
- if (input.type === "float") {
- return encodeFloat(settings[input.setting] as number);
- }
- return settings[input.setting];
- }
- }
-
- // 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");
- }
- return results[inputNode.id];
- }
-
- // If the value is stored in the node itself, we use that value
- if (node.props?.[key] !== undefined) {
- let value = node.props[key];
- if (input.type === "vec3") {
- return [0, 4, ...value.map(v => encodeFloat(v)), 1, 1]
- } else if (Array.isArray(value)) {
- return [0, value.length + 1, ...value, 1, 1];
- } else if (input.type === "float") {
- return encodeFloat(value);
- } else {
- return value;
- }
- }
-
- let defaultValue = input.value;
- if (defaultValue !== undefined) {
- if (Array.isArray(defaultValue)) {
- return [0, defaultValue.length + 1, ...defaultValue.map(v => encodeFloat(v)), 1, 1];
- } else if (input.type === "float") {
- return encodeFloat(defaultValue);
- } else {
- return defaultValue;
- }
- }
-
- throw new Error(`Input ${key} is not connected and has no default value`);
-
- });
-
- try {
-
- const encoded_inputs = concatEncodedArrays(inputs);
- log.group(`executing ${node_type.id || node.id}`);
- log.log(`Inputs:`, inputs);
- log.log(`Encoded Inputs:`, encoded_inputs);
- results[node.id] = node_type.execute(encoded_inputs);
- log.log("Result:", results[node.id]);
- log.log("Result (decoded):", decodeNestedArray(results[node.id]));
- log.groupEnd();
-
- } catch (e) {
- log.groupEnd();
- log.error(`Error executing node ${node_type.id || node.id}`, e);
}
+ // If the input is linked to a setting, we use that value
+ if (input.setting) {
+ return getValue(input, settings[input.setting]);
+ }
+
+ // 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");
+ }
+ return results[inputNode.id];
+ }
+
+ // If the value is stored in the node itself, we use that value
+ if (node.props?.[key] !== undefined) {
+ return getValue(input, node.props[key]);
+ }
+
+ return getValue(input);
+ });
+ b = performance.now();
+
+ this.perf?.addPoint("collected-inputs", b - a);
+
+ try {
+
+ a = performance.now();
+ const encoded_inputs = concatEncodedArrays(inputs);
+ b = performance.now();
+ this.perf?.addPoint("encoded-inputs", b - a);
+
+ let inputHash = `node-${node.id}-${fastHashArray(encoded_inputs)}`;
+ let cachedValue = this.cache?.get(inputHash);
+ if (cachedValue !== undefined) {
+ log.log(`Using cached value for ${node_type.id || node.id}`);
+ results[node.id] = cachedValue as Int32Array;
+ continue;
+ }
+
+ log.group(`executing ${node_type.id || node.id}`);
+ log.log(`Inputs:`, inputs);
+ a = performance.now();
+ results[node.id] = node_type.execute(encoded_inputs);
+ b = performance.now();
+ this.perf?.addPoint("node/" + node_type.id, b - a);
+ log.log("Result:", results[node.id]);
+ log.groupEnd();
+
+ } catch (e) {
+ log.groupEnd();
+ log.error(`Error executing node ${node_type.id || node.id}`, e);
}
+
+
}
// return the result of the parent of the output node
const res = results[outputNode.id];
+ this.perf?.addPoint("total", performance.now() - a0);
+
+ this.perf?.stopRun();
+
return res as unknown as Int32Array;
}
}
+
+export class MemoryRuntimeCache implements RuntimeCache {
+
+ private cache: Record = {};
+ get(key: string): T | undefined {
+ return this.cache[key] as T;
+ }
+ set(key: string, value: T): void {
+ this.cache[key] = value;
+ }
+ clear(): void {
+ this.cache = {};
+ }
+
+}
diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte
index 850eff3..c0b490f 100644
--- a/app/src/routes/+page.svelte
+++ b/app/src/routes/+page.svelte
@@ -1,7 +1,10 @@