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 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,
|
||||
|
@ -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);
|
||||
|
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 { 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<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) {
|
||||
|
||||
@ -67,9 +102,8 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
const stack = [outputNode];
|
||||
while (stack.length) {
|
||||
const node = stack.pop();
|
||||
if (node) {
|
||||
if (!node) continue;
|
||||
node.tmp = node.tmp || {};
|
||||
|
||||
if (node?.tmp?.depth === undefined) {
|
||||
node.tmp.depth = 0;
|
||||
}
|
||||
@ -84,18 +118,25 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return [outputNode, nodes] as const;
|
||||
}
|
||||
|
||||
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
|
||||
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,36 +155,31 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
// here we store the intermediate results of the nodes
|
||||
const results: Record<string, Int32Array> = {};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
a = performance.now();
|
||||
|
||||
// Collect the inputs for the node
|
||||
const inputs = Object.entries(node_type.inputs || {}).map(([key, input]) => {
|
||||
|
||||
if (input.type === "seed") {
|
||||
return runSeed;
|
||||
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) {
|
||||
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];
|
||||
}
|
||||
return getValue(input, settings[input.setting]);
|
||||
}
|
||||
|
||||
// check if the input is connected to another node
|
||||
@ -157,42 +193,37 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
|
||||
// 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;
|
||||
}
|
||||
return getValue(input, node.props[key]);
|
||||
}
|
||||
|
||||
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`);
|
||||
|
||||
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);
|
||||
log.log(`Encoded Inputs:`, encoded_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.log("Result (decoded):", decodeNestedArray(results[node.id]));
|
||||
log.groupEnd();
|
||||
|
||||
} catch (e) {
|
||||
@ -200,14 +231,33 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
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<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">
|
||||
import Grid from "$lib/grid";
|
||||
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 * as templates from "$lib/graph-templates";
|
||||
import type { Graph, Node } from "@nodes/types";
|
||||
@ -22,9 +25,15 @@
|
||||
import type { PerspectiveCamera, Vector3 } from "three";
|
||||
import type { OrbitControls } from "three/examples/jsm/Addons.js";
|
||||
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 runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry, runtimeCache);
|
||||
runtimeExecutor.perf = nodePerformance;
|
||||
|
||||
globalThis.decode = decodeNestedArray;
|
||||
globalThis.encode = encodeNestedArray;
|
||||
@ -96,6 +105,12 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
performance: {
|
||||
id: "performance",
|
||||
icon: "i-tabler-brand-speedtest",
|
||||
props: { store: nodePerformance, title: "Runtime Performance" },
|
||||
component: PerformanceViewer,
|
||||
},
|
||||
activeNode: {
|
||||
id: "Active Node",
|
||||
icon: "i-tabler-adjustments",
|
||||
|
@ -15,7 +15,6 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
|
||||
let args = split_args(input);
|
||||
|
||||
assert_eq!(args.len(), 2, "Expected 2 arguments, got {}", args.len());
|
||||
|
||||
let inputs = split_args(args[0]);
|
||||
|
||||
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 { 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>)[];
|
||||
|
||||
export function concatEncodedArrays(input: (number | number[])[]): number[] {
|
||||
export function concatEncodedArrays(input: (number | number[] | Int32Array)[]): Int32Array {
|
||||
|
||||
if (input.length === 1 && Array.isArray(input[0])) {
|
||||
return input[0]
|
||||
let totalLength = 4;
|
||||
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;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const item = input[i];
|
||||
if (Array.isArray(item) || item instanceof Int32Array) {
|
||||
result.push(...item);
|
||||
last_closing_bracket = result.length - 1;
|
||||
result.set(item, index);
|
||||
index += item.length;
|
||||
last_closing_bracket = index - 1;
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use utils::{
|
||||
geometry::{create_multiple_paths, create_path, wrap_multiple_paths},
|
||||
get_args,
|
||||
split_args,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -16,7 +16,7 @@ fn test_split_args(){
|
||||
];
|
||||
|
||||
for input in inputs {
|
||||
println!("RESULT: {:?}", get_args(&input));
|
||||
println!("RESULT: {:?}", split_args(&input));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user