feat: add simple performance tracker
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m23s
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m23s
This commit is contained in:
parent
2de2560a57
commit
f51f61df17
@ -7,7 +7,7 @@
|
|||||||
import { getContext, onMount, setContext } from "svelte";
|
import { getContext, onMount, setContext } from "svelte";
|
||||||
import Camera from "../Camera.svelte";
|
import Camera from "../Camera.svelte";
|
||||||
import GraphView from "./GraphView.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 { NodeDefinitionSchema } from "@nodes/types";
|
||||||
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
import FloatingEdge from "../edges/FloatingEdge.svelte";
|
||||||
import {
|
import {
|
||||||
@ -783,7 +783,7 @@
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
if (!event.dataTransfer) return;
|
if (!event.dataTransfer) return;
|
||||||
const nodeId = event.dataTransfer.getData("data/node-id");
|
const nodeId: NodeId = event.dataTransfer.getData("data/node-id");
|
||||||
|
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
let mx = event.clientX - rect.x;
|
let mx = event.clientX - rect.x;
|
||||||
@ -805,7 +805,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pos = projectScreenToWorld(mx, my);
|
const pos = projectScreenToWorld(mx, my);
|
||||||
graph.loadNode(nodeId).then(() => {
|
graph.load([nodeId]).then(() => {
|
||||||
graph.createNode({
|
graph.createNode({
|
||||||
type: nodeId,
|
type: nodeId,
|
||||||
props,
|
props,
|
||||||
|
@ -11,17 +11,6 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
constructor(private url: string) { }
|
constructor(private url: string) { }
|
||||||
|
|
||||||
async loadNode(id: `${string}/${string}/${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() {
|
async fetchUsers() {
|
||||||
@ -67,7 +56,20 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
async load(nodeIds: `${string}/${string}/${string}`[]) {
|
||||||
const a = performance.now();
|
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) {
|
for (const node of nodes) {
|
||||||
this.nodes.set(node.id, node);
|
this.nodes.set(node.id, node);
|
||||||
|
17
app/src/lib/performance/PerformanceViewer.svelte
Normal file
17
app/src/lib/performance/PerformanceViewer.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PerformanceStore } from ".";
|
||||||
|
|
||||||
|
export let store: PerformanceStore;
|
||||||
|
|
||||||
|
function getPerformanceData() {
|
||||||
|
return Object.entries($store.total).sort((a, b) => b[1] - a[1]);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $store.runs.length !== 0}
|
||||||
|
{#each getPerformanceData() as [key, value]}
|
||||||
|
<p>{key}: {Math.floor(value * 100) / 100}ms</p>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<p>No runs available</p>
|
||||||
|
{/if}
|
62
app/src/lib/performance/index.ts
Normal file
62
app/src/lib/performance/index.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { readable, type Readable } from "svelte/store";
|
||||||
|
|
||||||
|
type PerformanceData = {
|
||||||
|
total: Record<string, number>;
|
||||||
|
runs: Record<string, number[]>[];
|
||||||
|
}
|
||||||
|
export interface PerformanceStore extends Readable<PerformanceData> {
|
||||||
|
startRun(): void;
|
||||||
|
stopRun(): void;
|
||||||
|
addPoint(name: string, value?: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPerformanceStore(): PerformanceStore {
|
||||||
|
|
||||||
|
let data: PerformanceData = { total: {}, runs: [] };
|
||||||
|
|
||||||
|
let currentRun: Record<string, number[]> | undefined;
|
||||||
|
|
||||||
|
let set: (v: PerformanceData) => void;
|
||||||
|
|
||||||
|
const { subscribe } = readable<PerformanceData>({ 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";
|
@ -1,16 +1,51 @@
|
|||||||
import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor } from "@nodes/types";
|
import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor, NodeInput } from "@nodes/types";
|
||||||
import { fastHash, concatEncodedArrays, encodeFloat, decodeNestedArray } from "@nodes/utils"
|
import { concatEncodedArrays, encodeFloat, fastHashArray } from "@nodes/utils"
|
||||||
import { createLogger } from "./helpers";
|
import { createLogger } from "./helpers";
|
||||||
|
import type { RuntimeCache } from "@nodes/types";
|
||||||
|
import type { PerformanceStore } from "./performance";
|
||||||
|
|
||||||
const log = createLogger("runtime-executor");
|
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 {
|
export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||||
|
|
||||||
private definitionMap: Map<string, NodeDefinition> = new Map();
|
private definitionMap: Map<string, NodeDefinition> = new Map();
|
||||||
|
|
||||||
private cache: Record<string, { eol: number, value: any }> = {};
|
private randomSeed = Math.floor(Math.random() * 100000000);
|
||||||
|
|
||||||
constructor(private registry: NodeRegistry) { }
|
perf?: PerformanceStore;
|
||||||
|
|
||||||
|
constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { }
|
||||||
|
|
||||||
private getNodeDefinitions(graph: Graph) {
|
private getNodeDefinitions(graph: Graph) {
|
||||||
|
|
||||||
@ -67,26 +102,23 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
const stack = [outputNode];
|
const stack = [outputNode];
|
||||||
while (stack.length) {
|
while (stack.length) {
|
||||||
const node = stack.pop();
|
const node = stack.pop();
|
||||||
if (node) {
|
if (!node) continue;
|
||||||
node.tmp = node.tmp || {};
|
node.tmp = node.tmp || {};
|
||||||
|
if (node?.tmp?.depth === undefined) {
|
||||||
if (node?.tmp?.depth === undefined) {
|
node.tmp.depth = 0;
|
||||||
node.tmp.depth = 0;
|
}
|
||||||
}
|
if (node?.tmp?.parents !== undefined) {
|
||||||
if (node?.tmp?.parents !== undefined) {
|
for (const parent of node.tmp.parents) {
|
||||||
for (const parent of node.tmp.parents) {
|
parent.tmp = parent.tmp || {};
|
||||||
parent.tmp = parent.tmp || {};
|
if (parent.tmp?.depth === undefined) {
|
||||||
if (parent.tmp?.depth === undefined) {
|
parent.tmp.depth = node.tmp.depth + 1;
|
||||||
parent.tmp.depth = node.tmp.depth + 1;
|
stack.push(parent);
|
||||||
stack.push(parent);
|
} else {
|
||||||
} else {
|
parent.tmp.depth = Math.max(parent.tmp.depth, node.tmp.depth + 1);
|
||||||
parent.tmp.depth = Math.max(parent.tmp.depth, node.tmp.depth + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.push(node);
|
|
||||||
}
|
}
|
||||||
|
nodes.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [outputNode, nodes] as const;
|
return [outputNode, nodes] as const;
|
||||||
@ -94,8 +126,17 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
|
|
||||||
execute(graph: Graph, settings: Record<string, unknown>) {
|
execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
|
|
||||||
|
this.perf?.startRun();
|
||||||
|
|
||||||
|
let a0 = performance.now();
|
||||||
|
|
||||||
|
let a = performance.now();
|
||||||
|
|
||||||
// Then we add some metadata to the graph
|
// Then we add some metadata to the graph
|
||||||
const [outputNode, nodes] = this.addMetaData(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
|
* 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
|
// here we store the intermediate results of the nodes
|
||||||
const results: Record<string, Int32Array> = {};
|
const results: Record<string, Int32Array> = {};
|
||||||
|
|
||||||
const runSeed = settings["randomSeed"] === true ? Math.floor(Math.random() * 100000000) : 5120983;
|
|
||||||
|
|
||||||
for (const node of sortedNodes) {
|
for (const node of sortedNodes) {
|
||||||
|
|
||||||
const node_type = this.definitionMap.get(node.type)!;
|
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") {
|
// Collect the inputs for the node
|
||||||
return runSeed;
|
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
|
// return the result of the parent of the output node
|
||||||
const res = results[outputNode.id];
|
const res = results[outputNode.id];
|
||||||
|
|
||||||
|
this.perf?.addPoint("total", performance.now() - a0);
|
||||||
|
|
||||||
|
this.perf?.stopRun();
|
||||||
|
|
||||||
return res as unknown as Int32Array;
|
return res as unknown as Int32Array;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MemoryRuntimeCache implements RuntimeCache {
|
||||||
|
|
||||||
|
private cache: Record<string, unknown> = {};
|
||||||
|
get<T>(key: string): T | undefined {
|
||||||
|
return this.cache[key] as T;
|
||||||
|
}
|
||||||
|
set<T>(key: string, value: T): void {
|
||||||
|
this.cache[key] = value;
|
||||||
|
}
|
||||||
|
clear(): void {
|
||||||
|
this.cache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Grid from "$lib/grid";
|
import Grid from "$lib/grid";
|
||||||
import GraphInterface from "$lib/graph-interface";
|
import GraphInterface from "$lib/graph-interface";
|
||||||
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
import {
|
||||||
|
MemoryRuntimeExecutor,
|
||||||
|
MemoryRuntimeCache,
|
||||||
|
} from "$lib/runtime-executor";
|
||||||
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
import { RemoteNodeRegistry } from "$lib/node-registry-client";
|
||||||
import * as templates from "$lib/graph-templates";
|
import * as templates from "$lib/graph-templates";
|
||||||
import type { Graph, Node } from "@nodes/types";
|
import type { Graph, Node } from "@nodes/types";
|
||||||
@ -22,9 +25,15 @@
|
|||||||
import type { PerspectiveCamera, Vector3 } from "three";
|
import type { PerspectiveCamera, Vector3 } from "three";
|
||||||
import type { OrbitControls } from "three/examples/jsm/Addons.js";
|
import type { OrbitControls } from "three/examples/jsm/Addons.js";
|
||||||
import ActiveNode from "$lib/settings/panels/ActiveNode.svelte";
|
import ActiveNode from "$lib/settings/panels/ActiveNode.svelte";
|
||||||
|
import { createPerformanceStore } from "$lib/performance";
|
||||||
|
import PerformanceViewer from "$lib/performance/PerformanceViewer.svelte";
|
||||||
|
|
||||||
|
const nodePerformance = createPerformanceStore();
|
||||||
|
|
||||||
|
const runtimeCache = new MemoryRuntimeCache();
|
||||||
const nodeRegistry = new RemoteNodeRegistry("");
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||||
|
runtimeExecutor.perf = nodePerformance;
|
||||||
|
|
||||||
globalThis.decode = decodeNestedArray;
|
globalThis.decode = decodeNestedArray;
|
||||||
globalThis.encode = encodeNestedArray;
|
globalThis.encode = encodeNestedArray;
|
||||||
@ -96,6 +105,12 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
performance: {
|
||||||
|
id: "performance",
|
||||||
|
icon: "i-tabler-brand-speedtest",
|
||||||
|
props: { store: nodePerformance, title: "Runtime Performance" },
|
||||||
|
component: PerformanceViewer,
|
||||||
|
},
|
||||||
activeNode: {
|
activeNode: {
|
||||||
id: "Active Node",
|
id: "Active Node",
|
||||||
icon: "i-tabler-adjustments",
|
icon: "i-tabler-adjustments",
|
||||||
|
@ -15,7 +15,6 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
|||||||
let args = split_args(input);
|
let args = split_args(input);
|
||||||
|
|
||||||
assert_eq!(args.len(), 2, "Expected 2 arguments, got {}", args.len());
|
assert_eq!(args.len(), 2, "Expected 2 arguments, got {}", args.len());
|
||||||
|
|
||||||
let inputs = split_args(args[0]);
|
let inputs = split_args(args[0]);
|
||||||
|
|
||||||
let resolution = evaluate_int(args[1]) as usize;
|
let resolution = evaluate_int(args[1]) as usize;
|
||||||
|
57
packages/types/src/components.ts
Normal file
57
packages/types/src/components.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Graph, NodeDefinition, NodeId } from "./types";
|
||||||
|
|
||||||
|
export interface NodeRegistry {
|
||||||
|
/**
|
||||||
|
* The status of the node registry
|
||||||
|
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
||||||
|
*/
|
||||||
|
status: "loading" | "ready" | "error";
|
||||||
|
/**
|
||||||
|
* Load the nodes with the given ids
|
||||||
|
* @param nodeIds - The ids of the nodes to load
|
||||||
|
* @returns A promise that resolves when the nodes are loaded
|
||||||
|
* @throws An error if the nodes could not be loaded
|
||||||
|
* @remarks This method should be called before calling getNode or getAllNodes
|
||||||
|
*/
|
||||||
|
load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
|
||||||
|
/**
|
||||||
|
* Get a node by id
|
||||||
|
* @param id - The id of the node to get
|
||||||
|
* @returns The node with the given id, or undefined if no such node exists
|
||||||
|
*/
|
||||||
|
getNode: (id: NodeId) => NodeDefinition | undefined;
|
||||||
|
/**
|
||||||
|
* Get all nodes
|
||||||
|
* @returns An array of all nodes
|
||||||
|
*/
|
||||||
|
getAllNodes: () => NodeDefinition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeExecutor {
|
||||||
|
/**
|
||||||
|
* Execute the given graph
|
||||||
|
* @param graph - The graph to execute
|
||||||
|
* @returns The result of the execution
|
||||||
|
*/
|
||||||
|
execute: (graph: Graph, settings: Record<string, unknown>) => unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeCache {
|
||||||
|
/**
|
||||||
|
* Get the value for the given key
|
||||||
|
* @param key - The key to get the value for
|
||||||
|
* @returns The value for the given key, or undefined if no such value exists
|
||||||
|
*/
|
||||||
|
get: (key: string) => unknown | undefined;
|
||||||
|
/**
|
||||||
|
* Set the value for the given key
|
||||||
|
* @param key - The key to set the value for
|
||||||
|
* @param value - The value to set
|
||||||
|
*/
|
||||||
|
set: (key: string, value: unknown) => void;
|
||||||
|
/**
|
||||||
|
* Clear the cache
|
||||||
|
*/
|
||||||
|
clear: () => void;
|
||||||
|
|
||||||
|
}
|
@ -1,100 +1,5 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { NodeInputSchema } from "./inputs";
|
|
||||||
export type { NodeInput } from "./inputs";
|
export type { NodeInput } from "./inputs";
|
||||||
|
export type { NodeRegistry, RuntimeExecutor, RuntimeCache } from "./components";
|
||||||
|
export type { Node, NodeDefinition, Socket, NodeId, Edge, Graph } from "./types";
|
||||||
|
export { NodeDefinitionSchema } from "./types";
|
||||||
|
|
||||||
export type Node = {
|
|
||||||
id: number;
|
|
||||||
type: string;
|
|
||||||
props?: Record<string, any>,
|
|
||||||
tmp?: {
|
|
||||||
depth?: number;
|
|
||||||
mesh?: any;
|
|
||||||
random?: number;
|
|
||||||
parents?: Node[],
|
|
||||||
children?: Node[],
|
|
||||||
inputNodes?: Record<string, Node>
|
|
||||||
type?: NodeDefinition;
|
|
||||||
downX?: number;
|
|
||||||
downY?: number;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
ref?: HTMLElement;
|
|
||||||
visible?: boolean;
|
|
||||||
isMoving?: boolean;
|
|
||||||
},
|
|
||||||
meta?: {
|
|
||||||
title?: string;
|
|
||||||
lastModified?: string;
|
|
||||||
},
|
|
||||||
position: [x: number, y: number]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NodeDefinitionSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
inputs: z.record(NodeInputSchema).optional(),
|
|
||||||
outputs: z.array(z.string()).optional(),
|
|
||||||
meta: z.object({
|
|
||||||
description: z.string().optional(),
|
|
||||||
title: z.string().optional(),
|
|
||||||
}).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
|
|
||||||
execute(input: Int32Array): Int32Array;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Socket = {
|
|
||||||
node: Node;
|
|
||||||
index: number | string;
|
|
||||||
position: [number, number];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface NodeRegistry {
|
|
||||||
/**
|
|
||||||
* The status of the node registry
|
|
||||||
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
|
||||||
*/
|
|
||||||
status: "loading" | "ready" | "error";
|
|
||||||
/**
|
|
||||||
* Load the nodes with the given ids
|
|
||||||
* @param nodeIds - The ids of the nodes to load
|
|
||||||
* @returns A promise that resolves when the nodes are loaded
|
|
||||||
* @throws An error if the nodes could not be loaded
|
|
||||||
* @remarks This method should be called before calling getNode or getAllNodes
|
|
||||||
*/
|
|
||||||
load: (nodeIds: string[]) => Promise<void>;
|
|
||||||
/**
|
|
||||||
* Get a node by id
|
|
||||||
* @param id - The id of the node to get
|
|
||||||
* @returns The node with the given id, or undefined if no such node exists
|
|
||||||
*/
|
|
||||||
getNode: (id: string) => NodeDefinition | undefined;
|
|
||||||
/**
|
|
||||||
* Get all nodes
|
|
||||||
* @returns An array of all nodes
|
|
||||||
*/
|
|
||||||
getAllNodes: () => NodeDefinition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RuntimeExecutor {
|
|
||||||
/**
|
|
||||||
* Execute the given graph
|
|
||||||
* @param graph - The graph to execute
|
|
||||||
* @returns The result of the execution
|
|
||||||
*/
|
|
||||||
execute: (graph: Graph, settings: Record<string, unknown>) => unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export type Edge = [Node, number, Node, string];
|
|
||||||
|
|
||||||
export type Graph = {
|
|
||||||
id: number;
|
|
||||||
meta?: {
|
|
||||||
title?: string;
|
|
||||||
lastModified?: string;
|
|
||||||
},
|
|
||||||
settings?: Record<string, any>,
|
|
||||||
nodes: Node[];
|
|
||||||
edges: [number, number, number, string][];
|
|
||||||
}
|
|
||||||
|
64
packages/types/src/types.ts
Normal file
64
packages/types/src/types.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { NodeInputSchema } from "./inputs";
|
||||||
|
|
||||||
|
export type NodeId = `${string}/${string}/${string}`;
|
||||||
|
|
||||||
|
export type Node = {
|
||||||
|
id: number;
|
||||||
|
type: NodeId;
|
||||||
|
props?: Record<string, number | number[]>,
|
||||||
|
tmp?: {
|
||||||
|
depth?: number;
|
||||||
|
mesh?: any;
|
||||||
|
random?: number;
|
||||||
|
parents?: Node[],
|
||||||
|
children?: Node[],
|
||||||
|
inputNodes?: Record<string, Node>
|
||||||
|
type?: NodeDefinition;
|
||||||
|
downX?: number;
|
||||||
|
downY?: number;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
ref?: HTMLElement;
|
||||||
|
visible?: boolean;
|
||||||
|
isMoving?: boolean;
|
||||||
|
},
|
||||||
|
meta?: {
|
||||||
|
title?: string;
|
||||||
|
lastModified?: string;
|
||||||
|
},
|
||||||
|
position: [x: number, y: number]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeDefinitionSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
inputs: z.record(NodeInputSchema).optional(),
|
||||||
|
outputs: z.array(z.string()).optional(),
|
||||||
|
meta: z.object({
|
||||||
|
description: z.string().optional(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
}).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
|
||||||
|
execute(input: Int32Array): Int32Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Socket = {
|
||||||
|
node: Node;
|
||||||
|
index: number | string;
|
||||||
|
position: [number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Edge = [Node, number, Node, string];
|
||||||
|
|
||||||
|
export type Graph = {
|
||||||
|
id: number;
|
||||||
|
meta?: {
|
||||||
|
title?: string;
|
||||||
|
lastModified?: string;
|
||||||
|
},
|
||||||
|
settings?: Record<string, any>,
|
||||||
|
nodes: Node[];
|
||||||
|
edges: [number, number, number, string][];
|
||||||
|
}
|
@ -1,27 +1,40 @@
|
|||||||
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
|
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
|
||||||
|
|
||||||
export function concatEncodedArrays(input: (number | number[])[]): number[] {
|
export function concatEncodedArrays(input: (number | number[] | Int32Array)[]): Int32Array {
|
||||||
|
|
||||||
if (input.length === 1 && Array.isArray(input[0])) {
|
let totalLength = 4;
|
||||||
return input[0]
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const item = input[i];
|
||||||
|
if (Array.isArray(item) || item instanceof Int32Array) {
|
||||||
|
totalLength += item.length;
|
||||||
|
} else {
|
||||||
|
totalLength++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = [0, 1]; // opening bracket
|
const result = new Int32Array(totalLength);
|
||||||
|
|
||||||
|
result[0] = 0;
|
||||||
|
result[1] = 1;
|
||||||
|
|
||||||
|
let index = 2; // Start after the opening bracket
|
||||||
let last_closing_bracket = 1;
|
let last_closing_bracket = 1;
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
const item = input[i];
|
const item = input[i];
|
||||||
if (Array.isArray(item) || item instanceof Int32Array) {
|
if (Array.isArray(item) || item instanceof Int32Array) {
|
||||||
result.push(...item);
|
result.set(item, index);
|
||||||
last_closing_bracket = result.length - 1;
|
index += item.length;
|
||||||
|
last_closing_bracket = index - 1;
|
||||||
} else {
|
} else {
|
||||||
result[last_closing_bracket]++;
|
result[last_closing_bracket]++;
|
||||||
result.push(item);
|
result[index] = item;
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(1, 1); // closing bracket
|
result[totalLength - 2] = 1;
|
||||||
|
result[totalLength - 1] = 1;
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use utils::{
|
use utils::{
|
||||||
geometry::{create_multiple_paths, create_path, wrap_multiple_paths},
|
geometry::{create_multiple_paths, create_path, wrap_multiple_paths},
|
||||||
get_args,
|
split_args,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -16,7 +16,7 @@ fn test_split_args(){
|
|||||||
];
|
];
|
||||||
|
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
println!("RESULT: {:?}", get_args(&input));
|
println!("RESULT: {:?}", split_args(&input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user