feat/arena-runtime #27
@@ -25,14 +25,14 @@ const clone = 'structuredClone' in self
|
|||||||
? self.structuredClone
|
? self.structuredClone
|
||||||
: (args: any) => JSON.parse(JSON.stringify(args));
|
: (args: any) => JSON.parse(JSON.stringify(args));
|
||||||
|
|
||||||
function areSocketsCompatible(
|
export function areSocketsCompatible(
|
||||||
output: string | undefined,
|
output: string | undefined,
|
||||||
inputs: string | (string | undefined)[] | undefined
|
inputs: string | (string | undefined)[] | undefined
|
||||||
) {
|
) {
|
||||||
if (Array.isArray(inputs) && output) {
|
if (Array.isArray(inputs) && output) {
|
||||||
return inputs.includes(output);
|
return inputs.includes('*') || inputs.includes(output);
|
||||||
}
|
}
|
||||||
return inputs === output;
|
return inputs === output || inputs === '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
function areEdgesEqual(firstEdge: Edge, secondEdge: Edge) {
|
function areEdgesEqual(firstEdge: Edge, secondEdge: Edge) {
|
||||||
@@ -268,14 +268,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
private _init(graph: Graph) {
|
private _init(graph: Graph) {
|
||||||
const nodes = new Map(
|
const nodes = new Map(
|
||||||
graph.nodes.map((node) => {
|
graph.nodes.map((node) => {
|
||||||
const nodeType = this.registry.getNode(node.type);
|
return [node.id, node as NodeInstance];
|
||||||
const n = node as NodeInstance;
|
|
||||||
if (nodeType) {
|
|
||||||
n.state = {
|
|
||||||
type: nodeType
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return [node.id, n];
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -300,6 +293,30 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.execute();
|
this.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async loadAllCollections() {
|
||||||
|
// Fetch all nodes from all collections of the loaded nodes
|
||||||
|
const nodeIds = Array.from(new Set([...this.graph.nodes.map((n) => n.type)]));
|
||||||
|
const allCollections = new Set<`${string}/${string}`>();
|
||||||
|
for (const id of nodeIds) {
|
||||||
|
const [user, collection] = id.split('/');
|
||||||
|
allCollections.add(`${user}/${collection}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allCollectionIds = await Promise
|
||||||
|
.all([...allCollections]
|
||||||
|
.map(async (collection) =>
|
||||||
|
remoteRegistry
|
||||||
|
.fetchCollection(collection)
|
||||||
|
.then((collection: { nodes: { id: NodeId }[] }) => {
|
||||||
|
return collection.nodes.map(n => n.id.replace(/\.wasm$/, '') as NodeId);
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
const missingNodeIds = [...new Set(allCollectionIds.flat())];
|
||||||
|
|
||||||
|
this.registry.load(missingNodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
async load(graph: Graph) {
|
async load(graph: Graph) {
|
||||||
const a = performance.now();
|
const a = performance.now();
|
||||||
|
|
||||||
@@ -308,25 +325,16 @@ export class GraphManager extends EventEmitter<{
|
|||||||
this.status = 'loading';
|
this.status = 'loading';
|
||||||
this.id = graph.id;
|
this.id = graph.id;
|
||||||
|
|
||||||
logger.info('loading graph', { nodes: graph.nodes, edges: graph.edges, id: graph.id });
|
|
||||||
|
|
||||||
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
const nodeIds = Array.from(new Set([...graph.nodes.map((n) => n.type)]));
|
||||||
await this.registry.load(nodeIds);
|
|
||||||
|
|
||||||
// Fetch all nodes from all collections of the loaded nodes
|
logger.info('loading graph', {
|
||||||
const allCollections = new Set<`${string}/${string}`>();
|
nodes: graph.nodes,
|
||||||
for (const id of nodeIds) {
|
edges: graph.edges,
|
||||||
const [user, collection] = id.split('/');
|
id: graph.id,
|
||||||
allCollections.add(`${user}/${collection}`);
|
ids: nodeIds
|
||||||
}
|
});
|
||||||
for (const collection of allCollections) {
|
|
||||||
remoteRegistry
|
await this.registry.load(nodeIds);
|
||||||
.fetchCollection(collection)
|
|
||||||
.then((collection: { nodes: { id: NodeId }[] }) => {
|
|
||||||
const ids = collection.nodes.map((n) => n.id);
|
|
||||||
return this.registry.load(ids);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('loaded node types', this.registry.getAllNodes());
|
logger.info('loaded node types', this.registry.getAllNodes());
|
||||||
|
|
||||||
@@ -384,7 +392,9 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
||||||
|
|
||||||
setTimeout(() => this.execute(), 100);
|
setTimeout(() => this.execute(), 100);
|
||||||
|
this.loadAllCollections(); // lazily load all nodes from all collections
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllNodes() {
|
getAllNodes() {
|
||||||
@@ -491,10 +501,10 @@ export class GraphManager extends EventEmitter<{
|
|||||||
const inputs = Object.entries(to.state?.type?.inputs ?? {});
|
const inputs = Object.entries(to.state?.type?.inputs ?? {});
|
||||||
const outputs = from.state?.type?.outputs ?? [];
|
const outputs = from.state?.type?.outputs ?? [];
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
const [inputName, input] = inputs[0];
|
const [inputName, input] = inputs[i];
|
||||||
for (let o = 0; o < outputs.length; o++) {
|
for (let o = 0; o < outputs.length; o++) {
|
||||||
const output = outputs[0];
|
const output = outputs[o];
|
||||||
if (input.type === output) {
|
if (input.type === output || input.type === '*') {
|
||||||
return this.createEdge(from, o, to, inputName);
|
return this.createEdge(from, o, to, inputName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,6 +734,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
|
|
||||||
getPossibleSockets({ node, index }: Socket): [NodeInstance, string | number][] {
|
getPossibleSockets({ node, index }: Socket): [NodeInstance, string | number][] {
|
||||||
const nodeType = node?.state?.type;
|
const nodeType = node?.state?.type;
|
||||||
|
console.log({ node: $state.snapshot(node), index, nodeType });
|
||||||
if (!nodeType) return [];
|
if (!nodeType) return [];
|
||||||
|
|
||||||
const sockets: [NodeInstance, string | number][] = [];
|
const sockets: [NodeInstance, string | number][] = [];
|
||||||
@@ -783,6 +794,7 @@ export class GraphManager extends EventEmitter<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${sockets.length} possible sockets`, sockets);
|
||||||
return sockets;
|
return sockets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ export class GraphState {
|
|||||||
|
|
||||||
wrapper = $state<HTMLDivElement>(null!);
|
wrapper = $state<HTMLDivElement>(null!);
|
||||||
rect: DOMRect = $derived(
|
rect: DOMRect = $derived(
|
||||||
(this.wrapper && this.width && this.height) ? this.wrapper.getBoundingClientRect() : new DOMRect(0, 0, 0, 0)
|
(this.wrapper && this.width && this.height)
|
||||||
|
? this.wrapper.getBoundingClientRect()
|
||||||
|
: new DOMRect(0, 0, 0, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
camera = $state<OrthographicCamera>(null!);
|
camera = $state<OrthographicCamera>(null!);
|
||||||
@@ -168,11 +170,14 @@ export class GraphState {
|
|||||||
(node?.state?.y ?? node.position[1]) + 2.5 + 10 * index
|
(node?.state?.y ?? node.position[1]) + 2.5 + 10 * index
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
const _index = Object.keys(node.state?.type?.inputs || {}).indexOf(index);
|
const inputs = node.state.type?.inputs || this.graph.registry.getNode(node.type)?.inputs
|
||||||
return [
|
|| {};
|
||||||
|
const _index = Object.keys(inputs).indexOf(index);
|
||||||
|
const pos = [
|
||||||
node?.state?.x ?? node.position[0],
|
node?.state?.x ?? node.position[0],
|
||||||
(node?.state?.y ?? node.position[1]) + 10 + 10 * _index
|
(node?.state?.y ?? node.position[1]) + 10 + 10 * _index
|
||||||
];
|
] as [number, number];
|
||||||
|
return pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +253,7 @@ export class GraphState {
|
|||||||
|
|
||||||
let { node, index, position } = socket;
|
let { node, index, position } = socket;
|
||||||
|
|
||||||
// remove existing edge
|
// if the socket is an input socket -> remove existing edges
|
||||||
if (typeof index === 'string') {
|
if (typeof index === 'string') {
|
||||||
const edges = this.graph.getEdgesToNode(node);
|
const edges = this.graph.getEdgesToNode(node);
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
|
const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
|
||||||
(inp) => inp[1].type === socketType,
|
(inp) => inp[1].type === socketType || inp[1].type === "*",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
|
|||||||
@@ -29,11 +29,11 @@
|
|||||||
let {
|
let {
|
||||||
graph,
|
graph,
|
||||||
registry,
|
registry,
|
||||||
settings = $bindable(),
|
|
||||||
activeNode = $bindable(),
|
activeNode = $bindable(),
|
||||||
showGrid = $bindable(true),
|
showGrid = $bindable(true),
|
||||||
snapToGrid = $bindable(true),
|
snapToGrid = $bindable(true),
|
||||||
showHelp = $bindable(false),
|
showHelp = $bindable(false),
|
||||||
|
settings = $bindable(),
|
||||||
settingTypes = $bindable(),
|
settingTypes = $bindable(),
|
||||||
onsave,
|
onsave,
|
||||||
onresult,
|
onresult,
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { create, type Delta } from "jsondiffpatch";
|
import type { Graph } from '@nodarium/types';
|
||||||
import type { Graph } from "@nodarium/types";
|
import { createLogger } from '@nodarium/utils';
|
||||||
import { clone } from "./helpers/index.js";
|
import { create, type Delta } from 'jsondiffpatch';
|
||||||
import { createLogger } from "@nodarium/utils";
|
import { clone } from './helpers/index.js';
|
||||||
|
|
||||||
const diff = create({
|
const diff = create({
|
||||||
objectHash: function (obj, index) {
|
objectHash: function (obj, index) {
|
||||||
if (obj === null) return obj;
|
if (obj === null) return obj;
|
||||||
if ("id" in obj) return obj.id as string;
|
if ('id' in obj) return obj.id as string;
|
||||||
if ("_id" in obj) return obj._id as string;
|
if ('_id' in obj) return obj._id as string;
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.join("-");
|
return obj.join('-');
|
||||||
}
|
}
|
||||||
return "$$index:" + index;
|
return '$$index:' + index;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const log = createLogger("history");
|
const log = createLogger('history');
|
||||||
log.mute();
|
log.mute();
|
||||||
|
|
||||||
export class HistoryManager {
|
export class HistoryManager {
|
||||||
@@ -26,7 +26,7 @@ export class HistoryManager {
|
|||||||
|
|
||||||
private opts = {
|
private opts = {
|
||||||
debounce: 400,
|
debounce: 400,
|
||||||
maxHistory: 100,
|
maxHistory: 100
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor({ maxHistory = 100, debounce = 100 } = {}) {
|
constructor({ maxHistory = 100, debounce = 100 } = {}) {
|
||||||
@@ -40,12 +40,12 @@ export class HistoryManager {
|
|||||||
if (!this.state) {
|
if (!this.state) {
|
||||||
this.state = clone(state);
|
this.state = clone(state);
|
||||||
this.initialState = this.state;
|
this.initialState = this.state;
|
||||||
log.log("initial state saved");
|
log.log('initial state saved');
|
||||||
} else {
|
} else {
|
||||||
const newState = state;
|
const newState = state;
|
||||||
const delta = diff.diff(this.state, newState);
|
const delta = diff.diff(this.state, newState);
|
||||||
if (delta) {
|
if (delta) {
|
||||||
log.log("saving state");
|
log.log('saving state');
|
||||||
// Add the delta to history
|
// Add the delta to history
|
||||||
if (this.index < this.history.length - 1) {
|
if (this.index < this.history.length - 1) {
|
||||||
// Clear the history after the current index if new changes are made
|
// Clear the history after the current index if new changes are made
|
||||||
@@ -61,7 +61,7 @@ export class HistoryManager {
|
|||||||
}
|
}
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
} else {
|
} else {
|
||||||
log.log("no changes");
|
log.log('no changes');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ export class HistoryManager {
|
|||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
if (this.index === -1 && this.initialState) {
|
if (this.index === -1 && this.initialState) {
|
||||||
log.log("reached start, loading initial state");
|
log.log('reached start, loading initial state');
|
||||||
return clone(this.initialState);
|
return clone(this.initialState);
|
||||||
} else {
|
} else {
|
||||||
const delta = this.history[this.index];
|
const delta = this.history[this.index];
|
||||||
@@ -95,7 +95,7 @@ export class HistoryManager {
|
|||||||
this.state = nextState;
|
this.state = nextState;
|
||||||
return clone(nextState);
|
return clone(nextState);
|
||||||
} else {
|
} else {
|
||||||
log.log("reached end");
|
log.log('reached end');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export async function getNodeWasm(id: `${string}/${string}/${string}`) {
|
|||||||
const wasmBytes = await getWasm(id);
|
const wasmBytes = await getWasm(id);
|
||||||
if (!wasmBytes) return null;
|
if (!wasmBytes) return null;
|
||||||
|
|
||||||
const wrapper = createWasmWrapper(wasmBytes);
|
const wrapper = createWasmWrapper(wasmBytes.buffer);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,47 +4,47 @@ import type {
|
|||||||
NodeInput,
|
NodeInput,
|
||||||
NodeRegistry,
|
NodeRegistry,
|
||||||
RuntimeExecutor,
|
RuntimeExecutor,
|
||||||
SyncCache,
|
SyncCache
|
||||||
} from "@nodarium/types";
|
} from '@nodarium/types';
|
||||||
import {
|
import {
|
||||||
concatEncodedArrays,
|
concatEncodedArrays,
|
||||||
createLogger,
|
createLogger,
|
||||||
encodeFloat,
|
encodeFloat,
|
||||||
fastHashArrayBuffer,
|
fastHashArrayBuffer,
|
||||||
type PerformanceStore,
|
type PerformanceStore
|
||||||
} from "@nodarium/utils";
|
} from '@nodarium/utils';
|
||||||
import type { RuntimeNode } from "./types";
|
import type { RuntimeNode } from './types';
|
||||||
|
|
||||||
const log = createLogger("runtime-executor");
|
const log = createLogger('runtime-executor');
|
||||||
log.mute();
|
log.mute();
|
||||||
|
|
||||||
function getValue(input: NodeInput, value?: unknown) {
|
function getValue(input: NodeInput, value?: unknown) {
|
||||||
if (value === undefined && "value" in input) {
|
if (value === undefined && 'value' in input) {
|
||||||
value = input.value;
|
value = input.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.type === "float") {
|
if (input.type === 'float') {
|
||||||
return encodeFloat(value as number);
|
return encodeFloat(value as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (input.type === "vec3") {
|
if (input.type === 'vec3') {
|
||||||
return [
|
return [
|
||||||
0,
|
0,
|
||||||
value.length + 1,
|
value.length + 1,
|
||||||
...value.map((v) => encodeFloat(v)),
|
...value.map((v) => encodeFloat(v)),
|
||||||
1,
|
1,
|
||||||
1,
|
1
|
||||||
] as number[];
|
] as number[];
|
||||||
}
|
}
|
||||||
return [0, value.length + 1, ...value, 1, 1] as number[];
|
return [0, value.length + 1, ...value, 1, 1] as number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "boolean") {
|
if (typeof value === 'boolean') {
|
||||||
return value ? 1 : 0;
|
return value ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "number") {
|
if (typeof value === 'number') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ function getValue(input: NodeInput, value?: unknown) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error({ input, value });
|
||||||
throw new Error(`Unknown input type ${input.type}`);
|
throw new Error(`Unknown input type ${input.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,16 +63,18 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
|
|
||||||
perf?: PerformanceStore;
|
perf?: PerformanceStore;
|
||||||
|
|
||||||
|
private results: Record<string, Int32Array> = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private registry: NodeRegistry,
|
private registry: NodeRegistry,
|
||||||
public cache?: SyncCache<Int32Array>,
|
public cache?: SyncCache<Int32Array>
|
||||||
) {
|
) {
|
||||||
this.cache = undefined;
|
this.cache = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getNodeDefinitions(graph: Graph) {
|
private async getNodeDefinitions(graph: Graph) {
|
||||||
if (this.registry.status !== "ready") {
|
if (this.registry.status !== 'ready') {
|
||||||
throw new Error("Node registry is not ready");
|
throw new Error('Node registry is not ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.registry.load(graph.nodes.map((node) => node.type));
|
await this.registry.load(graph.nodes.map((node) => node.type));
|
||||||
@@ -98,21 +101,18 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
depth: 0,
|
depth: 0,
|
||||||
children: [],
|
children: [],
|
||||||
parents: [],
|
parents: [],
|
||||||
inputNodes: {},
|
inputNodes: {}
|
||||||
}
|
};
|
||||||
return n
|
return n;
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const outputNode = graphNodes.find((node) => node.type.endsWith('/output'));
|
||||||
const outputNode = graphNodes.find((node) =>
|
|
||||||
node.type.endsWith("/output"),
|
|
||||||
);
|
|
||||||
if (!outputNode) {
|
if (!outputNode) {
|
||||||
throw new Error("No output node found");
|
throw new Error('No output node found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeMap = new Map(
|
const nodeMap = new Map(
|
||||||
graphNodes.map((node) => [node.id, node]),
|
graphNodes.map((node) => [node.id, node])
|
||||||
);
|
);
|
||||||
|
|
||||||
// loop through all edges and assign the parent and child nodes to each node
|
// loop through all edges and assign the parent and child nodes to each node
|
||||||
@@ -146,7 +146,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async execute(graph: Graph, settings: Record<string, unknown>) {
|
async execute(graph: Graph, settings: Record<string, unknown>) {
|
||||||
this.perf?.addPoint("runtime");
|
this.perf?.addPoint('runtime');
|
||||||
|
|
||||||
let a = performance.now();
|
let a = performance.now();
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
const [outputNode, nodes] = await this.addMetaData(graph);
|
const [outputNode, nodes] = await this.addMetaData(graph);
|
||||||
let b = performance.now();
|
let b = performance.now();
|
||||||
|
|
||||||
this.perf?.addPoint("collect-metadata", b - a);
|
this.perf?.addPoint('collect-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
|
||||||
@@ -169,13 +169,13 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
|
|
||||||
// we execute the nodes from the bottom up
|
// we execute the nodes from the bottom up
|
||||||
const sortedNodes = nodes.sort(
|
const sortedNodes = nodes.sort(
|
||||||
(a, b) => (b.state?.depth || 0) - (a.state?.depth || 0),
|
(a, b) => (b.state?.depth || 0) - (a.state?.depth || 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
// here we store the intermediate results of the nodes
|
// here we store the intermediate results of the nodes
|
||||||
const results: Record<string, Int32Array> = {};
|
this.results = {};
|
||||||
|
|
||||||
if (settings["randomSeed"]) {
|
if (settings['randomSeed']) {
|
||||||
this.seed = Math.floor(Math.random() * 100000000);
|
this.seed = Math.floor(Math.random() * 100000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
// Collect the inputs for the node
|
// Collect the inputs for the node
|
||||||
const inputs = Object.entries(node_type.inputs || {}).map(
|
const inputs = Object.entries(node_type.inputs || {}).map(
|
||||||
([key, input]) => {
|
([key, input]) => {
|
||||||
if (input.type === "seed") {
|
if (input.type === 'seed') {
|
||||||
return this.seed;
|
return this.seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,12 +204,12 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
// check if the input is connected to another node
|
// check if the input is connected to another node
|
||||||
const inputNode = node.state.inputNodes[key];
|
const inputNode = node.state.inputNodes[key];
|
||||||
if (inputNode) {
|
if (inputNode) {
|
||||||
if (results[inputNode.id] === undefined) {
|
if (this.results[inputNode.id] === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Node ${node.type} is missing input from node ${inputNode.type}`,
|
`Node ${node.type} is missing input from node ${inputNode.type}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return results[inputNode.id];
|
return this.results[inputNode.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the value is stored in the node itself, we use that value
|
// If the value is stored in the node itself, we use that value
|
||||||
@@ -218,45 +218,45 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return getValue(input);
|
return getValue(input);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
b = performance.now();
|
b = performance.now();
|
||||||
|
|
||||||
this.perf?.addPoint("collected-inputs", b - a);
|
this.perf?.addPoint('collected-inputs', b - a);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
a = performance.now();
|
a = performance.now();
|
||||||
const encoded_inputs = concatEncodedArrays(inputs);
|
const encoded_inputs = concatEncodedArrays(inputs);
|
||||||
b = performance.now();
|
b = performance.now();
|
||||||
this.perf?.addPoint("encoded-inputs", b - a);
|
this.perf?.addPoint('encoded-inputs', b - a);
|
||||||
|
|
||||||
a = performance.now();
|
a = performance.now();
|
||||||
let inputHash = `node-${node.id}-${fastHashArrayBuffer(encoded_inputs)}`;
|
let inputHash = `node-${node.id}-${fastHashArrayBuffer(encoded_inputs)}`;
|
||||||
b = performance.now();
|
b = performance.now();
|
||||||
this.perf?.addPoint("hash-inputs", b - a);
|
this.perf?.addPoint('hash-inputs', b - a);
|
||||||
|
|
||||||
let cachedValue = this.cache?.get(inputHash);
|
let cachedValue = this.cache?.get(inputHash);
|
||||||
if (cachedValue !== undefined) {
|
if (cachedValue !== undefined) {
|
||||||
log.log(`Using cached value for ${node_type.id || node.id}`);
|
log.log(`Using cached value for ${node_type.id || node.id}`);
|
||||||
this.perf?.addPoint("cache-hit", 1);
|
this.perf?.addPoint('cache-hit', 1);
|
||||||
results[node.id] = cachedValue as Int32Array;
|
this.results[node.id] = cachedValue as Int32Array;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.perf?.addPoint("cache-hit", 0);
|
this.perf?.addPoint('cache-hit', 0);
|
||||||
|
|
||||||
log.group(`executing ${node_type.id}-${node.id}`);
|
log.group(`executing ${node_type.id}-${node.id}`);
|
||||||
log.log(`Inputs:`, inputs);
|
log.log(`Inputs:`, inputs);
|
||||||
a = performance.now();
|
a = performance.now();
|
||||||
results[node.id] = node_type.execute(encoded_inputs);
|
this.results[node.id] = node_type.execute(encoded_inputs);
|
||||||
log.log("Executed", node.type, node.id)
|
log.log('Executed', node.type, node.id);
|
||||||
b = performance.now();
|
b = performance.now();
|
||||||
|
|
||||||
if (this.cache && node.id !== outputNode.id) {
|
if (this.cache && node.id !== outputNode.id) {
|
||||||
this.cache.set(inputHash, results[node.id]);
|
this.cache.set(inputHash, this.results[node.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.perf?.addPoint("node/" + node_type.id, b - a);
|
this.perf?.addPoint('node/' + node_type.id, b - a);
|
||||||
log.log("Result:", results[node.id]);
|
log.log('Result:', this.results[node.id]);
|
||||||
log.groupEnd();
|
log.groupEnd();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.groupEnd();
|
log.groupEnd();
|
||||||
@@ -265,17 +265,21 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 = this.results[outputNode.id];
|
||||||
|
|
||||||
if (this.cache) {
|
if (this.cache) {
|
||||||
this.cache.size = sortedNodes.length * 2;
|
this.cache.size = sortedNodes.length * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.perf?.endPoint("runtime");
|
this.perf?.endPoint('runtime');
|
||||||
|
|
||||||
return res as unknown as Int32Array;
|
return res as unknown as Int32Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIntermediateResults() {
|
||||||
|
return this.results;
|
||||||
|
}
|
||||||
|
|
||||||
getPerformanceData() {
|
getPerformanceData() {
|
||||||
return this.perf?.get();
|
return this.perf?.get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,11 +90,6 @@
|
|||||||
let graphSettingTypes = $state({
|
let graphSettingTypes = $state({
|
||||||
randomSeed: { type: "boolean", value: false },
|
randomSeed: { type: "boolean", value: false },
|
||||||
});
|
});
|
||||||
$effect(() => {
|
|
||||||
if (graphSettings && graphSettingTypes) {
|
|
||||||
manager?.setSettings($state.snapshot(graphSettings));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function update(
|
async function update(
|
||||||
g: Graph,
|
g: Graph,
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
const { children } = $props<{ children?: Snippet }>();
|
const { children } = $props<{ children?: Snippet }>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="w-screen overflow-x-hidden">
|
<main class="w-screen h-screen overflow-x-hidden">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,88 +1,73 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import NodeHTML from "$lib/graph-interface/node/NodeHTML.svelte";
|
import * as templates from "$lib/graph-templates";
|
||||||
import { localState } from "$lib/helpers/localState.svelte";
|
|
||||||
import Panel from "$lib/sidebar/Panel.svelte";
|
import Panel from "$lib/sidebar/Panel.svelte";
|
||||||
import Sidebar from "$lib/sidebar/Sidebar.svelte";
|
import Sidebar from "$lib/sidebar/Sidebar.svelte";
|
||||||
import { IndexDBCache, RemoteNodeRegistry } from "@nodarium/registry";
|
import GraphInterface from "$lib/graph-interface";
|
||||||
import { type NodeId, type NodeInstance } from "@nodarium/types";
|
import { RemoteNodeRegistry } from "@nodarium/registry";
|
||||||
import Code from "./Code.svelte";
|
import { type Graph } from "@nodarium/types";
|
||||||
import Grid from "$lib/grid";
|
import Grid from "$lib/grid";
|
||||||
import {
|
import { MemoryRuntimeExecutor } from "$lib/runtime";
|
||||||
concatEncodedArrays,
|
import devPlant from "./dev-graph.json";
|
||||||
createWasmWrapper,
|
import { decodeNestedArray } from "@nodarium/utils";
|
||||||
encodeNestedArray,
|
|
||||||
} from "@nodarium/utils";
|
|
||||||
|
|
||||||
const registryCache = new IndexDBCache("node-registry");
|
let result = $state<Int32Array>();
|
||||||
const nodeRegistry = new RemoteNodeRegistry("", registryCache);
|
|
||||||
|
|
||||||
let activeNode = localState<NodeId | undefined>(
|
const nodeRegistry = new RemoteNodeRegistry("");
|
||||||
"node.dev.activeNode",
|
nodeRegistry.overwriteNode("max/plantarium/output", {
|
||||||
undefined,
|
id: "max/plantarium/output",
|
||||||
);
|
meta: {
|
||||||
|
title: "Debug View",
|
||||||
let nodeWasm = $state<ArrayBuffer>();
|
description: "",
|
||||||
let nodeInstance = $state<NodeInstance>();
|
},
|
||||||
let nodeWasmWrapper = $state<ReturnType<typeof createWasmWrapper>>();
|
inputs: {
|
||||||
|
out: {
|
||||||
async function fetchNodeData(nodeId?: NodeId) {
|
type: "*",
|
||||||
nodeWasm = undefined;
|
|
||||||
nodeInstance = undefined;
|
|
||||||
|
|
||||||
if (!nodeId) return;
|
|
||||||
|
|
||||||
const data = await nodeRegistry.fetchNodeDefinition(nodeId);
|
|
||||||
nodeWasm = await nodeRegistry.fetchArrayBuffer("nodes/" + nodeId + ".wasm");
|
|
||||||
nodeInstance = {
|
|
||||||
id: 0,
|
|
||||||
type: nodeId,
|
|
||||||
position: [0, 0] as [number, number],
|
|
||||||
props: {},
|
|
||||||
state: {
|
|
||||||
type: data,
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
nodeWasmWrapper = createWasmWrapper(nodeWasm);
|
execute(input: Int32Array) {
|
||||||
|
result = input;
|
||||||
|
return input;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||||
|
|
||||||
|
let graph = $state(
|
||||||
|
localStorage.getItem("nodes.dev.graph")
|
||||||
|
? JSON.parse(localStorage.getItem("nodes.dev.graph")!)
|
||||||
|
: devPlant,
|
||||||
|
);
|
||||||
|
function handleSave(graph: Graph) {
|
||||||
|
localStorage.setItem("nodes.dev.graph", JSON.stringify(graph));
|
||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
let graphSettings = $state<Record<string, any>>({});
|
||||||
fetchNodeData(activeNode.value);
|
let graphSettingTypes = $state({
|
||||||
|
randomSeed: { type: "boolean", value: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
async function handleResult(res: unknown) {
|
||||||
if (nodeInstance?.props && nodeWasmWrapper) {
|
const result = await runtimeExecutor.execute(graph, graphSettings);
|
||||||
const keys = Object.keys(nodeInstance.state.type?.inputs || {});
|
console.log({ res, result });
|
||||||
let ins = Object.values(nodeInstance.props) as number[];
|
}
|
||||||
if (keys[0] === "plant") {
|
|
||||||
ins = [[0, 0, 0, 0, 0, 0, 0, 0], ...ins];
|
|
||||||
}
|
|
||||||
const inputs = concatEncodedArrays(encodeNestedArray(ins));
|
|
||||||
nodeWasmWrapper?.execute(inputs);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="node-wrapper absolute bottom-8 left-8">
|
|
||||||
{#if nodeInstance}
|
|
||||||
<NodeHTML inView position="relative" z={5} bind:node={nodeInstance} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
<pre>
|
{#if result}
|
||||||
<code>
|
<pre><code>{JSON.stringify(decodeNestedArray(result))}</code></pre>
|
||||||
{JSON.stringify(nodeInstance?.props)}
|
{/if}
|
||||||
</code>
|
|
||||||
</pre>
|
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
|
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
<div class="h-screen w-[80vw] overflow-y-auto">
|
<GraphInterface
|
||||||
{#if nodeWasm}
|
{graph}
|
||||||
<Code wasm={nodeWasm} />
|
registry={nodeRegistry}
|
||||||
{/if}
|
bind:settings={graphSettings}
|
||||||
</div>
|
bind:settingTypes={graphSettingTypes}
|
||||||
|
onsave={(g) => handleSave(g)}
|
||||||
|
onresult={(result) => handleResult(result)}
|
||||||
|
/>
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
|
||||||
@@ -92,22 +77,7 @@
|
|||||||
classes="text-green-400"
|
classes="text-green-400"
|
||||||
title="Node Store"
|
title="Node Store"
|
||||||
icon="i-[tabler--database]"
|
icon="i-[tabler--database]"
|
||||||
>
|
></Panel>
|
||||||
<div class="p-4 flex flex-col gap-2">
|
|
||||||
{#await nodeRegistry.fetchCollection("max/plantarium")}
|
|
||||||
<p>Loading Nodes...</p>
|
|
||||||
{:then result}
|
|
||||||
{#each result.nodes as n}
|
|
||||||
<button
|
|
||||||
class="cursor-pointer p-2 bg-layer-1 {activeNode.value === n.id
|
|
||||||
? 'outline outline-offset-1'
|
|
||||||
: ''}"
|
|
||||||
onclick={() => (activeNode.value = n.id)}>{n.id}</button
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
</Panel>
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
53
app/src/routes/dev/dev-graph.json
Normal file
53
app/src/routes/dev/dev-graph.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"resolution.circle": 26,
|
||||||
|
"resolution.curve": 39
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"position": [
|
||||||
|
220,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"type": "max/plantarium/output",
|
||||||
|
"props": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"position": [
|
||||||
|
195,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"type": "max/plantarium/math",
|
||||||
|
"props": {
|
||||||
|
"op_type": 0,
|
||||||
|
"a": 2,
|
||||||
|
"b": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"position": [
|
||||||
|
170,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"type": "max/plantarium/float",
|
||||||
|
"props": {
|
||||||
|
"value": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"position": [
|
||||||
|
170,
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"type": "max/plantarium/float",
|
||||||
|
"props": {
|
||||||
|
"value": 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": []
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
type AsyncCache,
|
type AsyncCache,
|
||||||
type NodeDefinition,
|
type NodeDefinition,
|
||||||
NodeDefinitionSchema,
|
NodeDefinitionSchema,
|
||||||
|
type NodeId,
|
||||||
type NodeRegistry
|
type NodeRegistry
|
||||||
} from '@nodarium/types';
|
} from '@nodarium/types';
|
||||||
import { createLogger, createWasmWrapper } from '@nodarium/utils';
|
import { createLogger, createWasmWrapper } from '@nodarium/utils';
|
||||||
@@ -153,6 +154,13 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAllNodes() {
|
getAllNodes() {
|
||||||
return [...this.nodes.values()];
|
const allNodes = [...this.nodes.values()];
|
||||||
|
log.info('getting all nodes', allNodes);
|
||||||
|
return allNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async overwriteNode(nodeId: NodeId, node: NodeDefinition) {
|
||||||
|
log.info('Overwritten node', { nodeId, node });
|
||||||
|
this.nodes.set(nodeId, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { z } from "zod";
|
import { z } from 'zod';
|
||||||
|
|
||||||
const DefaultOptionsSchema = z.object({
|
const DefaultOptionsSchema = z.object({
|
||||||
internal: z.boolean().optional(),
|
internal: z.boolean().optional(),
|
||||||
@@ -9,72 +9,77 @@ const DefaultOptionsSchema = z.object({
|
|||||||
accepts: z
|
accepts: z
|
||||||
.array(
|
.array(
|
||||||
z.union([
|
z.union([
|
||||||
z.literal("float"),
|
z.literal('float'),
|
||||||
z.literal("integer"),
|
z.literal('integer'),
|
||||||
z.literal("boolean"),
|
z.literal('boolean'),
|
||||||
z.literal("select"),
|
z.literal('select'),
|
||||||
z.literal("seed"),
|
z.literal('seed'),
|
||||||
z.literal("vec3"),
|
z.literal('vec3'),
|
||||||
z.literal("geometry"),
|
z.literal('geometry'),
|
||||||
z.literal("path"),
|
z.literal('path')
|
||||||
]),
|
])
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
hidden: z.boolean().optional(),
|
hidden: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputFloatSchema = z.object({
|
export const NodeInputFloatSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("float"),
|
type: z.literal('float'),
|
||||||
element: z.literal("slider").optional(),
|
element: z.literal('slider').optional(),
|
||||||
value: z.number().optional(),
|
value: z.number().optional(),
|
||||||
min: z.number().optional(),
|
min: z.number().optional(),
|
||||||
max: z.number().optional(),
|
max: z.number().optional(),
|
||||||
step: z.number().optional(),
|
step: z.number().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputIntegerSchema = z.object({
|
export const NodeInputIntegerSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("integer"),
|
type: z.literal('integer'),
|
||||||
element: z.literal("slider").optional(),
|
element: z.literal('slider').optional(),
|
||||||
value: z.number().optional(),
|
value: z.number().optional(),
|
||||||
min: z.number().optional(),
|
min: z.number().optional(),
|
||||||
max: z.number().optional(),
|
max: z.number().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputBooleanSchema = z.object({
|
export const NodeInputBooleanSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("boolean"),
|
type: z.literal('boolean'),
|
||||||
value: z.boolean().optional(),
|
value: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputSelectSchema = z.object({
|
export const NodeInputSelectSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("select"),
|
type: z.literal('select'),
|
||||||
options: z.array(z.string()).optional(),
|
options: z.array(z.string()).optional(),
|
||||||
value: z.string().optional(),
|
value: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputSeedSchema = z.object({
|
export const NodeInputSeedSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("seed"),
|
type: z.literal('seed'),
|
||||||
value: z.number().optional(),
|
value: z.number().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputVec3Schema = z.object({
|
export const NodeInputVec3Schema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("vec3"),
|
type: z.literal('vec3'),
|
||||||
value: z.array(z.number()).optional(),
|
value: z.array(z.number()).optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputGeometrySchema = z.object({
|
export const NodeInputGeometrySchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("geometry"),
|
type: z.literal('geometry')
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputPathSchema = z.object({
|
export const NodeInputPathSchema = z.object({
|
||||||
...DefaultOptionsSchema.shape,
|
...DefaultOptionsSchema.shape,
|
||||||
type: z.literal("path"),
|
type: z.literal('path')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NodeInputAnySchema = z.object({
|
||||||
|
...DefaultOptionsSchema.shape,
|
||||||
|
type: z.literal('*')
|
||||||
});
|
});
|
||||||
|
|
||||||
export const NodeInputSchema = z.union([
|
export const NodeInputSchema = z.union([
|
||||||
@@ -87,6 +92,7 @@ export const NodeInputSchema = z.union([
|
|||||||
NodeInputVec3Schema,
|
NodeInputVec3Schema,
|
||||||
NodeInputGeometrySchema,
|
NodeInputGeometrySchema,
|
||||||
NodeInputPathSchema,
|
NodeInputPathSchema,
|
||||||
|
NodeInputAnySchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type NodeInput = z.infer<typeof NodeInputSchema>;
|
export type NodeInput = z.infer<typeof NodeInputSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user