feat: move registry and runtime into separate packages
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m32s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m32s
This commit is contained in:
@ -10,6 +10,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodes/registry": "link:../packages/registry",
|
||||
"@nodes/runtime": "link:../packages/runtime",
|
||||
"@nodes/ui": "link:../packages/ui",
|
||||
"@nodes/utils": "link:../packages/utils",
|
||||
"@sveltejs/kit": "^2.5.7",
|
||||
|
@ -73,35 +73,6 @@ export const debounce = (fn: Function, ms = 300) => {
|
||||
|
||||
export const clone: <T>(v: T) => T = "structedClone" in globalThis ? globalThis.structuredClone : (obj) => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
export const createLogger = (() => {
|
||||
let maxLength = 5;
|
||||
return (scope: string) => {
|
||||
maxLength = Math.max(maxLength, scope.length);
|
||||
let muted = false;
|
||||
|
||||
let isGrouped = false;
|
||||
|
||||
function s(color: string, ...args: any) {
|
||||
return isGrouped ? [...args] : [`[%c${scope.padEnd(maxLength, " ")}]:`, `color: ${color}`, ...args];
|
||||
}
|
||||
|
||||
return {
|
||||
log: (...args: any[]) => !muted && console.log(...s("#888", ...args)),
|
||||
info: (...args: any[]) => !muted && console.info(...s("#888", ...args)),
|
||||
warn: (...args: any[]) => !muted && console.warn(...s("#888", ...args)),
|
||||
error: (...args: any[]) => console.error(...s("#f88", ...args)),
|
||||
group: (...args: any[]) => { if (!muted) { console.groupCollapsed(...s("#888", ...args)); isGrouped = true; } },
|
||||
groupEnd: () => { if (!muted) { console.groupEnd(); isGrouped = false } },
|
||||
mute() {
|
||||
muted = true;
|
||||
},
|
||||
unmute() {
|
||||
muted = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
export function withSubComponents<A, B extends Record<string, any>>(
|
||||
|
@ -1,38 +0,0 @@
|
||||
import type { RuntimeCache } from '@nodes/types';
|
||||
import { openDB, type IDBPDatabase } from 'idb';
|
||||
|
||||
export class IndexDBCache implements RuntimeCache<ArrayBuffer> {
|
||||
|
||||
size: number = 100;
|
||||
|
||||
db: Promise<IDBPDatabase<ArrayBuffer>>;
|
||||
private _cache = new Map<string, ArrayBuffer>();
|
||||
|
||||
constructor(id: string) {
|
||||
this.db = openDB<ArrayBuffer>('cache/' + id, 1, {
|
||||
upgrade(db) {
|
||||
db.createObjectStore('keyval');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
let res = this._cache.get(key);
|
||||
if (!res) {
|
||||
res = await (await this.db).get('keyval', key);
|
||||
}
|
||||
if (res) {
|
||||
this._cache.set(key, res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
async set(key: string, value: ArrayBuffer) {
|
||||
this._cache.set(key, value);
|
||||
const db = await this.db;
|
||||
await db.put('keyval', value, key);
|
||||
}
|
||||
clear() {
|
||||
this.db.then(db => db.clear('keyval'));
|
||||
}
|
||||
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
import { type NodeRegistry, type NodeDefinition, NodeDefinitionSchema, type RuntimeCache } from "@nodes/types";
|
||||
import { createWasmWrapper } from "@nodes/utils";
|
||||
import { createLogger } from "./helpers";
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
log.mute();
|
||||
|
||||
export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
status: "loading" | "ready" | "error" = "loading";
|
||||
private nodes: Map<string, NodeDefinition> = new Map();
|
||||
|
||||
cache?: RuntimeCache<ArrayBuffer>;
|
||||
|
||||
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
|
||||
|
||||
constructor(private url: string) { }
|
||||
|
||||
async fetchUsers() {
|
||||
const response = await this.fetch(`${this.url}/nodes/users.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load users`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchUser(userId: `${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${userId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load user ${userId}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchCollection(userCollectionId: `${string}/${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${userCollectionId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load collection ${userCollectionId}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
|
||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load node definition ${nodeId}`);
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
|
||||
|
||||
const response = await this.fetch(`${this.url}/nodes/${nodeId}.wasm`);
|
||||
if (!response.ok) {
|
||||
if (this.cache) {
|
||||
let value = await this.cache.get(nodeId);
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed to load node wasm ${nodeId}`);
|
||||
}
|
||||
|
||||
return response.arrayBuffer();
|
||||
}
|
||||
|
||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||
const a = performance.now();
|
||||
|
||||
const nodes = await Promise.all([...new Set(nodeIds).values()].map(async id => {
|
||||
|
||||
if (this.nodes.has(id)) {
|
||||
return this.nodes.get(id)!;
|
||||
}
|
||||
|
||||
const wasmBuffer = await this.fetchNodeWasm(id);
|
||||
|
||||
return this.register(wasmBuffer);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
const duration = performance.now() - a;
|
||||
|
||||
log.group("loaded nodes in", duration, "ms");
|
||||
log.info(nodeIds);
|
||||
log.info(nodes);
|
||||
log.groupEnd();
|
||||
this.status = "ready";
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
async register(wasmBuffer: ArrayBuffer) {
|
||||
|
||||
const wrapper = createWasmWrapper(wasmBuffer);
|
||||
|
||||
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
|
||||
|
||||
if (definition.error) {
|
||||
console.error(definition.error);
|
||||
throw definition.error;
|
||||
}
|
||||
|
||||
if (this.cache) {
|
||||
await this.cache.set(definition.data.id, wasmBuffer);
|
||||
}
|
||||
|
||||
let node = {
|
||||
...definition.data,
|
||||
execute: wrapper.execute
|
||||
}
|
||||
|
||||
this.nodes.set(definition.data.id, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
getAllNodes() {
|
||||
return [...this.nodes.values()];
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
import { humanizeNumber } from "$lib/helpers";
|
||||
import { Checkbox } from "@nodes/ui";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import { type PerformanceData } from "./store";
|
||||
import { type PerformanceData } from "@nodes/utils";
|
||||
import BarSplit from "./BarSplit.svelte";
|
||||
|
||||
export let data: PerformanceData;
|
||||
|
@ -2,15 +2,13 @@
|
||||
import { humanizeDuration, humanizeNumber } from "$lib/helpers";
|
||||
import localStore from "$lib/helpers/localStore";
|
||||
import SmallGraph from "./SmallGraph.svelte";
|
||||
import type { PerformanceData, PerformanceStore } from "./store";
|
||||
import type { PerformanceData, PerformanceStore } from "@nodes/utils";
|
||||
|
||||
export let store: PerformanceStore;
|
||||
|
||||
const open = localStore("node.performance.small.open", {
|
||||
runtime: false,
|
||||
fps: false,
|
||||
vertices: false,
|
||||
faces: false,
|
||||
});
|
||||
|
||||
$: vertices = $store?.at(-1)?.["total-vertices"][0] || 0;
|
||||
@ -54,29 +52,15 @@
|
||||
</tr>
|
||||
{/if}
|
||||
|
||||
<tr on:click={() => ($open.vertices = !$open.vertices)}>
|
||||
<td>{$open.vertices ? "-" : "+"} vertices </td>
|
||||
<tr>
|
||||
<td>vertices </td>
|
||||
<td>{humanizeNumber(vertices || 0)}</td>
|
||||
</tr>
|
||||
{#if $open.vertices}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<SmallGraph points={getPoints($store, "total-vertices")} />
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
|
||||
<tr on:click={() => ($open.faces = !$open.faces)}>
|
||||
<td>{$open.faces ? "-" : "+"} faces </td>
|
||||
<tr>
|
||||
<td>faces </td>
|
||||
<td>{humanizeNumber(faces || 0)}</td>
|
||||
</tr>
|
||||
{#if $open.faces}
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<SmallGraph points={getPoints($store, "total-faces")} />
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
@ -1,2 +1 @@
|
||||
export * from "./store";
|
||||
export { default as PerformanceViewer } from "./PerformanceViewer.svelte";
|
||||
|
@ -1,97 +0,0 @@
|
||||
import { readable, type Readable } from "svelte/store";
|
||||
|
||||
export type PerformanceData = Record<string, number[]>[];
|
||||
|
||||
export interface PerformanceStore extends Readable<PerformanceData> {
|
||||
startRun(): void;
|
||||
stopRun(): void;
|
||||
addPoint(name: string, value?: number): void;
|
||||
endPoint(name?: string): void;
|
||||
mergeData(data: PerformanceData[number]): void;
|
||||
get: () => PerformanceData;
|
||||
}
|
||||
|
||||
export function createPerformanceStore(id?: string): PerformanceStore {
|
||||
|
||||
let data: PerformanceData = [];
|
||||
|
||||
let currentRun: Record<string, number[]> | undefined;
|
||||
let temp: Record<string, number> | undefined;
|
||||
let lastPoint: string | undefined;
|
||||
|
||||
let set: (v: PerformanceData) => void;
|
||||
|
||||
const { subscribe } = readable<PerformanceData>([], (_set) => {
|
||||
set = _set;
|
||||
});
|
||||
|
||||
function startRun() {
|
||||
if (currentRun) return;
|
||||
currentRun = {};
|
||||
lastPoint = undefined;
|
||||
temp = {
|
||||
start: performance.now()
|
||||
}
|
||||
}
|
||||
|
||||
function stopRun() {
|
||||
if (currentRun && temp) {
|
||||
currentRun["total"] = [performance.now() - temp.start];
|
||||
data.push(currentRun);
|
||||
data = data.slice(-100);
|
||||
currentRun = undefined;
|
||||
temp = undefined;
|
||||
if (set) set(data);
|
||||
}
|
||||
}
|
||||
|
||||
function addPoint(name: string, value?: number) {
|
||||
if (!currentRun) return;
|
||||
if (value === undefined) {
|
||||
if (temp) {
|
||||
lastPoint = name;
|
||||
temp[name] = performance.now();
|
||||
}
|
||||
} else {
|
||||
currentRun[name] = currentRun[name] || [];
|
||||
currentRun[name].push(value);
|
||||
}
|
||||
}
|
||||
|
||||
function get() {
|
||||
return data;
|
||||
}
|
||||
|
||||
function mergeData(newData: PerformanceData[number]) {
|
||||
|
||||
let r = currentRun;
|
||||
if (!r) return;
|
||||
|
||||
Object.keys(newData).forEach((name) => {
|
||||
if (name in r) {
|
||||
r[name].push(...newData[name]);
|
||||
} else {
|
||||
r[name] = newData[name];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function endPoint(name = lastPoint) {
|
||||
if (name === lastPoint) lastPoint = undefined;
|
||||
if (name && currentRun && temp && name in temp) {
|
||||
currentRun[name] = currentRun[name] || [];
|
||||
currentRun[name].push(performance.now() - temp[name]);
|
||||
delete temp[name];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
startRun,
|
||||
stopRun,
|
||||
addPoint,
|
||||
endPoint,
|
||||
mergeData,
|
||||
get
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import type { Graph, RuntimeExecutor } from "@nodes/types";
|
||||
|
||||
export class RemoteRuntimeExecutor implements RuntimeExecutor {
|
||||
|
||||
constructor(private url: string) { }
|
||||
|
||||
async execute(graph: Graph, settings: Record<string, any>): Promise<Int32Array> {
|
||||
|
||||
const res = await fetch(this.url, { method: "POST", body: JSON.stringify({ graph, settings }) });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to execute graph`);
|
||||
}
|
||||
|
||||
return new Int32Array(await res.arrayBuffer());
|
||||
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
|
||||
import { updateGeometries } from "./updateGeometries";
|
||||
import { decodeFloat, splitNestedArray } from "@nodes/utils";
|
||||
import type { PerformanceStore } from "$lib/performance";
|
||||
import type { PerformanceStore } from "@nodes/utils";
|
||||
import { AppSettings } from "$lib/settings/app-settings";
|
||||
import SmallPerformanceViewer from "$lib/performance/SmallPerformanceViewer.svelte";
|
||||
|
||||
|
@ -1,283 +0,0 @@
|
||||
import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor, NodeInput } from "@nodes/types";
|
||||
import { concatEncodedArrays, encodeFloat, fastHashArrayBuffer } 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<string, NodeDefinition> = new Map();
|
||||
|
||||
private randomSeed = Math.floor(Math.random() * 100000000);
|
||||
|
||||
perf?: PerformanceStore;
|
||||
|
||||
constructor(private registry: NodeRegistry, private cache?: RuntimeCache<Int32Array>) { }
|
||||
|
||||
private async getNodeDefinitions(graph: Graph) {
|
||||
|
||||
if (this.registry.status !== "ready") {
|
||||
throw new Error("Node registry is not ready");
|
||||
}
|
||||
|
||||
await this.registry.load(graph.nodes.map(node => node.type));
|
||||
|
||||
const typeMap = new Map<string, NodeDefinition>();
|
||||
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;
|
||||
}
|
||||
|
||||
private async addMetaData(graph: Graph) {
|
||||
|
||||
// First, lets check if all nodes have a definition
|
||||
this.definitionMap = await this.getNodeDefinitions(graph);
|
||||
|
||||
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
|
||||
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) 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);
|
||||
}
|
||||
|
||||
return [outputNode, nodes] as const;
|
||||
}
|
||||
|
||||
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||
|
||||
this.perf?.addPoint("runtime");
|
||||
|
||||
let a = performance.now();
|
||||
|
||||
// Then we add some metadata to the graph
|
||||
const [outputNode, nodes] = await this.addMetaData(graph);
|
||||
let b = performance.now();
|
||||
|
||||
this.perf?.addPoint("collect-metadata", b - a);
|
||||
|
||||
/*
|
||||
* 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, Int32Array> = {};
|
||||
|
||||
for (const node of sortedNodes) {
|
||||
|
||||
const node_type = this.definitionMap.get(node.type)!;
|
||||
|
||||
if (!node_type || !node.tmp || !node_type.execute) {
|
||||
log.warn(`Node ${node.id} has no definition`);
|
||||
continue;
|
||||
};
|
||||
|
||||
a = performance.now();
|
||||
|
||||
// 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 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);
|
||||
|
||||
a = performance.now();
|
||||
let inputHash = `node-${node.id}-${fastHashArrayBuffer(encoded_inputs)}`;
|
||||
b = performance.now();
|
||||
this.perf?.addPoint("hash-inputs", b - a);
|
||||
|
||||
let cachedValue = this.cache?.get(inputHash);
|
||||
if (cachedValue !== undefined) {
|
||||
log.log(`Using cached value for ${node_type.id || node.id}`);
|
||||
this.perf?.addPoint("cache-hit", 1);
|
||||
results[node.id] = cachedValue as Int32Array;
|
||||
continue;
|
||||
}
|
||||
this.perf?.addPoint("cache-hit", 0);
|
||||
|
||||
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();
|
||||
|
||||
if (this.cache) {
|
||||
this.cache.set(inputHash, results[node.id]);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (this.cache) {
|
||||
this.cache.size = sortedNodes.length * 2;
|
||||
}
|
||||
|
||||
this.perf?.endPoint("runtime");
|
||||
|
||||
return res as unknown as Int32Array;
|
||||
|
||||
}
|
||||
|
||||
getPerformanceData() {
|
||||
return this.perf?.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class MemoryRuntimeCache implements RuntimeCache {
|
||||
|
||||
private cache: [string, unknown][] = [];
|
||||
size = 50;
|
||||
|
||||
get<T>(key: string): T | undefined {
|
||||
return this.cache.find(([k]) => k === key)?.[1] as T;
|
||||
}
|
||||
set<T>(key: string, value: T): void {
|
||||
this.cache.push([key, value]);
|
||||
this.cache = this.cache.slice(-this.size);
|
||||
}
|
||||
clear(): void {
|
||||
this.cache = [];
|
||||
}
|
||||
|
||||
}
|
@ -136,16 +136,6 @@
|
||||
border: solid thin var(--outline);
|
||||
border-bottom: none;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 1em;
|
||||
font-size: 1em;
|
||||
padding: 0.5em;
|
||||
border: solid thin var(--outline);
|
||||
background: var(--layer-2);
|
||||
box-sizing: border-box;
|
||||
height: 2.5em;
|
||||
}
|
||||
i {
|
||||
opacity: 0.5;
|
||||
font-size: 0.8em;
|
||||
|
@ -1,26 +0,0 @@
|
||||
import { MemoryRuntimeExecutor, MemoryRuntimeCache } from "./runtime-executor";
|
||||
import { RemoteNodeRegistry } from "./node-registry-client";
|
||||
import type { Graph } from "@nodes/types";
|
||||
import { createPerformanceStore } from "./performance/store";
|
||||
import { IndexDBCache } from "./node-registry-cache";
|
||||
|
||||
const cache = new MemoryRuntimeCache();
|
||||
const indexDbCache = new IndexDBCache("node-registry");
|
||||
const nodeRegistry = new RemoteNodeRegistry("");
|
||||
nodeRegistry.cache = indexDbCache;
|
||||
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
|
||||
|
||||
const performanceStore = createPerformanceStore("worker");
|
||||
executor.perf = performanceStore;
|
||||
|
||||
export async function executeGraph(graph: Graph, settings: Record<string, unknown>): Promise<Int32Array> {
|
||||
await nodeRegistry.load(graph.nodes.map((n) => n.type));
|
||||
performanceStore.startRun();
|
||||
let res = await executor.execute(graph, settings);
|
||||
performanceStore.stopRun();
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getPerformanceData() {
|
||||
return performanceStore.get();
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
/// <reference types="vite-plugin-comlink/client" />
|
||||
|
||||
import type { Graph, RuntimeExecutor } from "@nodes/types";
|
||||
|
||||
export class WorkerRuntimeExecutor implements RuntimeExecutor {
|
||||
private worker = new ComlinkWorker<typeof import('./worker-runtime-executor-backend.ts')>(new URL("./worker-runtime-executor-backend.ts", import.meta.url));
|
||||
constructor() {
|
||||
}
|
||||
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||
return this.worker.executeGraph(graph, settings);
|
||||
}
|
||||
async getPerformanceData() {
|
||||
return this.worker.getPerformanceData();
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Grid from "$lib/grid";
|
||||
import GraphInterface from "$lib/graph-interface";
|
||||
import { WorkerRuntimeExecutor } from "$lib/worker-runtime-executor";
|
||||
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||
import * as templates from "$lib/graph-templates";
|
||||
import type { Graph, Node } from "@nodes/types";
|
||||
import Viewer from "$lib/result-viewer/Viewer.svelte";
|
||||
@ -18,17 +16,17 @@
|
||||
import Panel from "$lib/settings/Panel.svelte";
|
||||
import GraphSettings from "$lib/settings/panels/GraphSettings.svelte";
|
||||
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
|
||||
import { createPerformanceStore } from "$lib/performance";
|
||||
import type { Group, Scene } from "three";
|
||||
import type { Group } from "three";
|
||||
import ExportSettings from "$lib/settings/panels/ExportSettings.svelte";
|
||||
import {
|
||||
MemoryRuntimeCache,
|
||||
WorkerRuntimeExecutor,
|
||||
MemoryRuntimeExecutor,
|
||||
} from "$lib/runtime-executor";
|
||||
import { IndexDBCache } from "$lib/node-registry-cache";
|
||||
import { decodeNestedArray, fastHashString } from "@nodes/utils";
|
||||
} from "@nodes/runtime";
|
||||
import { IndexDBCache, RemoteNodeRegistry } from "@nodes/registry";
|
||||
import { decodeNestedArray, createPerformanceStore } from "@nodes/utils";
|
||||
import BenchmarkPanel from "$lib/settings/panels/BenchmarkPanel.svelte";
|
||||
import { debounceAsyncFunction, withArgsChangeOnly } from "$lib/helpers";
|
||||
import { debounceAsyncFunction } from "$lib/helpers";
|
||||
|
||||
let performanceStore = createPerformanceStore("page");
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
import type { RequestHandler } from "./$types";
|
||||
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
||||
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||
import { createPerformanceStore } from "$lib/performance";
|
||||
|
||||
const registry = new RemoteNodeRegistry("");
|
||||
const runtime = new MemoryRuntimeExecutor(registry);
|
||||
const performanceStore = createPerformanceStore();
|
||||
runtime.perf = performanceStore;
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
export const POST: RequestHandler = async ({ request, fetch }) => {
|
||||
|
||||
const { graph, settings } = await request.json();
|
||||
|
||||
if (!graph || !settings) {
|
||||
return new Response("Invalid request", { status: 400 });
|
||||
}
|
||||
|
||||
registry.fetch = fetch;
|
||||
|
||||
await registry.load(graph.nodes.map(node => node.type))
|
||||
|
||||
const res = await runtime.execute(graph, settings);
|
||||
|
||||
let headers: Record<string, string> = { "Content-Type": "application/octet-stream" };
|
||||
if (runtime.perf) {
|
||||
headers["performance"] = JSON.stringify(runtime.perf.get());
|
||||
}
|
||||
|
||||
return new Response(res, { headers });
|
||||
|
||||
}
|
Reference in New Issue
Block a user