Compare commits

..

2 Commits

Author SHA1 Message Date
db77a4fd94 Merge pull request 'refactor: split ui/runtime/serialized node types' (#10) from refactor/split-node-runtime-types into main
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m3s
Reviewed-on: #10
2025-12-03 19:19:17 +01:00
Max Richter
7ae1fae3b9 refactor: split ui/runtime/serialized node types
Closes #6
2025-12-03 19:18:56 +01:00
24 changed files with 306 additions and 343 deletions

View File

@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { HTML } from "@threlte/extras"; import { HTML } from "@threlte/extras";
import { onMount } from "svelte"; import { onMount } from "svelte";
import type { Node, NodeType } from "@nodarium/types"; import type { NodeInstance, NodeId } from "@nodarium/types";
import { getGraphManager, getGraphState } from "../graph/state.svelte"; import { getGraphManager, getGraphState } from "../graph/state.svelte";
type Props = { type Props = {
onnode: (n: Node) => void; onnode: (n: NodeInstance) => void;
}; };
const { onnode }: Props = $props(); const { onnode }: Props = $props();
@@ -15,7 +15,7 @@
let input: HTMLInputElement; let input: HTMLInputElement;
let value = $state<string>(); let value = $state<string>();
let activeNodeId = $state<NodeType>(); let activeNodeId = $state<NodeId>();
const allNodes = graphState.activeSocket const allNodes = graphState.activeSocket
? graph.getPossibleNodes(graphState.activeSocket) ? graph.getPossibleNodes(graphState.activeSocket)
@@ -39,13 +39,14 @@
} }
}); });
function handleNodeCreation(nodeType: Node["type"]) { function handleNodeCreation(nodeType: NodeInstance["type"]) {
if (!graphState.addMenuPosition) return; if (!graphState.addMenuPosition) return;
onnode?.({ onnode?.({
id: -1, id: -1,
type: nodeType, type: nodeType,
position: [...graphState.addMenuPosition], position: [...graphState.addMenuPosition],
props: {}, props: {},
state: {},
}); });
} }

View File

@@ -27,7 +27,7 @@
<script lang="ts"> <script lang="ts">
import { T } from "@threlte/core"; import { T } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras"; import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { Mesh, MeshBasicMaterial, Vector3 } from "three"; import { MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js"; import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js"; import { Vector2 } from "three/src/math/Vector2.js";
import { appSettings } from "$lib/settings/app-settings.svelte"; import { appSettings } from "$lib/settings/app-settings.svelte";

View File

@@ -1,11 +1,11 @@
import type { import type {
Edge, Edge,
Graph, Graph,
Node, NodeInstance,
NodeDefinition, NodeDefinition,
NodeInput, NodeInput,
NodeRegistry, NodeRegistry,
NodeType, NodeId,
Socket, Socket,
} from "@nodarium/types"; } from "@nodarium/types";
import { fastHashString } from "@nodarium/utils"; import { fastHashString } from "@nodarium/utils";
@@ -68,7 +68,7 @@ export class GraphManager extends EventEmitter<{
graph: Graph = { id: 0, nodes: [], edges: [] }; graph: Graph = { id: 0, nodes: [], edges: [] };
id = $state(0); id = $state(0);
nodes = new SvelteMap<number, Node>(); nodes = new SvelteMap<number, NodeInstance>();
edges = $state<Edge[]>([]); edges = $state<Edge[]>([]);
@@ -101,7 +101,7 @@ export class GraphManager extends EventEmitter<{
position: [...node.position], position: [...node.position],
type: node.type, type: node.type,
props: node.props, props: node.props,
})) as Node[]; })) as NodeInstance[];
const edges = this.edges.map((edge) => [ const edges = this.edges.map((edge) => [
edge[0].id, edge[0].id,
edge[1], edge[1],
@@ -133,8 +133,8 @@ export class GraphManager extends EventEmitter<{
return this.registry.getAllNodes(); return this.registry.getAllNodes();
} }
getLinkedNodes(node: Node) { getLinkedNodes(node: NodeInstance) {
const nodes = new Set<Node>(); const nodes = new Set<NodeInstance>();
const stack = [node]; const stack = [node];
while (stack.length) { while (stack.length) {
const n = stack.pop(); const n = stack.pop();
@@ -148,10 +148,10 @@ export class GraphManager extends EventEmitter<{
return [...nodes.values()]; return [...nodes.values()];
} }
getEdgesBetweenNodes(nodes: Node[]): [number, number, number, string][] { getEdgesBetweenNodes(nodes: NodeInstance[]): [number, number, number, string][] {
const edges = []; const edges = [];
for (const node of nodes) { for (const node of nodes) {
const children = node.tmp?.children || []; const children = node.state?.children || [];
for (const child of children) { for (const child of children) {
if (nodes.includes(child)) { if (nodes.includes(child)) {
const edge = this.edges.find( const edge = this.edges.find(
@@ -174,14 +174,15 @@ 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: Node) => { graph.nodes.map((node) => {
const nodeType = this.registry.getNode(node.type); const nodeType = this.registry.getNode(node.type);
const n = node as NodeInstance;
if (nodeType) { if (nodeType) {
node.tmp = { n.state = {
type: nodeType, type: nodeType,
}; };
} }
return [node.id, node]; return [node.id, n];
}), }),
); );
@@ -191,12 +192,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) { if (!from || !to) {
throw new Error("Edge references non-existing node"); throw new Error("Edge references non-existing node");
} }
from.tmp = from.tmp || {}; from.state.children = from.state.children || [];
from.tmp.children = from.tmp.children || []; from.state.children.push(to);
from.tmp.children.push(to); to.state.parents = to.state.parents || [];
to.tmp = to.tmp || {}; to.state.parents.push(from);
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
return [from, edge[1], to, edge[3]] as Edge; return [from, edge[1], to, edge[3]] as Edge;
}); });
@@ -232,8 +231,10 @@ export class GraphManager extends EventEmitter<{
this.status = "error"; this.status = "error";
return; return;
} }
node.tmp = node.tmp || {}; // Turn into runtime node
node.tmp.type = nodeType; const n = node as NodeInstance;
n.state = {};
n.state.type = nodeType;
} }
// load settings // load settings
@@ -292,7 +293,7 @@ export class GraphManager extends EventEmitter<{
return this.registry.getNode(id); return this.registry.getNode(id);
} }
async loadNodeType(id: NodeType) { async loadNodeType(id: NodeId) {
await this.registry.load([id]); await this.registry.load([id]);
const nodeType = this.registry.getNode(id); const nodeType = this.registry.getNode(id);
@@ -321,19 +322,19 @@ export class GraphManager extends EventEmitter<{
this.emit("settings", { types: settingTypes, values: settingValues }); this.emit("settings", { types: settingTypes, values: settingValues });
} }
getChildren(node: Node) { getChildren(node: NodeInstance) {
const children = []; const children = [];
const stack = node.tmp?.children?.slice(0); const stack = node.state?.children?.slice(0);
while (stack?.length) { while (stack?.length) {
const child = stack.pop(); const child = stack.pop();
if (!child) continue; if (!child) continue;
children.push(child); children.push(child);
stack.push(...(child.tmp?.children || [])); stack.push(...(child.state?.children || []));
} }
return children; return children;
} }
getNodesBetween(from: Node, to: Node): Node[] | undefined { getNodesBetween(from: NodeInstance, to: NodeInstance): NodeInstance[] | undefined {
// < - - - - from // < - - - - from
const toParents = this.getParentsOfNode(to); const toParents = this.getParentsOfNode(to);
// < - - - - from - - - - to // < - - - - from - - - - to
@@ -350,7 +351,7 @@ export class GraphManager extends EventEmitter<{
} }
} }
removeNode(node: Node, { restoreEdges = false } = {}) { removeNode(node: NodeInstance, { restoreEdges = false } = {}) {
const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id); const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id);
const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id); const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id);
for (const edge of [...edgesToNode, ...edgesFromNode]) { for (const edge of [...edgesToNode, ...edgesFromNode]) {
@@ -363,8 +364,8 @@ export class GraphManager extends EventEmitter<{
for (const [to, toSocket] of inputSockets) { for (const [to, toSocket] of inputSockets) {
for (const [from, fromSocket] of outputSockets) { for (const [from, fromSocket] of outputSockets) {
const outputType = from.tmp?.type?.outputs?.[fromSocket]; const outputType = from.state?.type?.outputs?.[fromSocket];
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type; const inputType = to?.state?.type?.inputs?.[toSocket]?.type;
if (outputType === inputType) { if (outputType === inputType) {
this.createEdge(from, fromSocket, to, toSocket, { this.createEdge(from, fromSocket, to, toSocket, {
applyUpdate: false, applyUpdate: false,
@@ -380,9 +381,9 @@ export class GraphManager extends EventEmitter<{
this.save(); this.save();
} }
smartConnect(from: Node, to: Node): Edge | undefined { smartConnect(from: NodeInstance, to: NodeInstance): Edge | undefined {
const inputs = Object.entries(to.tmp?.type?.inputs ?? {}); const inputs = Object.entries(to.state?.type?.inputs ?? {});
const outputs = from.tmp?.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[0];
for (let o = 0; o < outputs.length; o++) { for (let o = 0; o < outputs.length; o++) {
@@ -398,7 +399,7 @@ export class GraphManager extends EventEmitter<{
return Math.max(0, ...this.nodes.keys()) + 1; return Math.max(0, ...this.nodes.keys()) + 1;
} }
createGraph(nodes: Node[], edges: [number, number, number, string][]) { createGraph(nodes: NodeInstance[], edges: [number, number, number, string][]) {
// map old ids to new ids // map old ids to new ids
const idMap = new Map<number, number>(); const idMap = new Map<number, number>();
@@ -422,13 +423,11 @@ export class GraphManager extends EventEmitter<{
throw new Error("Edge references non-existing node"); throw new Error("Edge references non-existing node");
} }
to.tmp = to.tmp || {}; to.state.parents = to.state.parents || [];
to.tmp.parents = to.tmp.parents || []; to.state.parents.push(from);
to.tmp.parents.push(from);
from.tmp = from.tmp || {}; from.state.children = from.state.children || [];
from.tmp.children = from.tmp.children || []; from.state.children.push(to);
from.tmp.children.push(to);
return [from, edge[1], to, edge[3]] as Edge; return [from, edge[1], to, edge[3]] as Edge;
}); });
@@ -448,9 +447,9 @@ export class GraphManager extends EventEmitter<{
position, position,
props = {}, props = {},
}: { }: {
type: Node["type"]; type: NodeInstance["type"];
position: Node["position"]; position: NodeInstance["position"];
props: Node["props"]; props: NodeInstance["props"];
}) { }) {
const nodeType = this.registry.getNode(type); const nodeType = this.registry.getNode(type);
if (!nodeType) { if (!nodeType) {
@@ -458,11 +457,11 @@ export class GraphManager extends EventEmitter<{
return; return;
} }
const node: Node = $state({ const node: NodeInstance = $state({
id: this.createNodeId(), id: this.createNodeId(),
type, type,
position, position,
tmp: { type: nodeType }, state: { type: nodeType },
props, props,
}); });
@@ -474,9 +473,9 @@ export class GraphManager extends EventEmitter<{
} }
createEdge( createEdge(
from: Node, from: NodeInstance,
fromSocket: number, fromSocket: number,
to: Node, to: NodeInstance,
toSocket: string, toSocket: string,
{ applyUpdate = true } = {}, { applyUpdate = true } = {},
): Edge | undefined { ): Edge | undefined {
@@ -493,10 +492,10 @@ export class GraphManager extends EventEmitter<{
} }
// check if socket types match // check if socket types match
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket]; const fromSocketType = from.state?.type?.outputs?.[fromSocket];
const toSocketType = [to.tmp?.type?.inputs?.[toSocket]?.type]; const toSocketType = [to.state?.type?.inputs?.[toSocket]?.type];
if (to.tmp?.type?.inputs?.[toSocket]?.accepts) { if (to.state?.type?.inputs?.[toSocket]?.accepts) {
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || [])); toSocketType.push(...(to?.state?.type?.inputs?.[toSocket]?.accepts || []));
} }
if (!areSocketsCompatible(fromSocketType, toSocketType)) { if (!areSocketsCompatible(fromSocketType, toSocketType)) {
@@ -517,13 +516,10 @@ export class GraphManager extends EventEmitter<{
this.edges.push(edge); this.edges.push(edge);
from.tmp = from.tmp || {}; from.state.children = from.state.children || [];
from.tmp.children = from.tmp.children || []; from.state.children.push(to);
from.tmp.children.push(to); to.state.parents = to.state.parents || [];
to.state.parents.push(from);
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
if (applyUpdate) { if (applyUpdate) {
this.save(); this.save();
@@ -566,9 +562,9 @@ export class GraphManager extends EventEmitter<{
logger.log("saving graphs", state); logger.log("saving graphs", state);
} }
getParentsOfNode(node: Node) { getParentsOfNode(node: NodeInstance) {
const parents = []; const parents = [];
const stack = node.tmp?.parents?.slice(0); const stack = node.state?.parents?.slice(0);
while (stack?.length) { while (stack?.length) {
if (parents.length > 1000000) { if (parents.length > 1000000) {
logger.warn("Infinite loop detected"); logger.warn("Infinite loop detected");
@@ -577,7 +573,7 @@ export class GraphManager extends EventEmitter<{
const parent = stack.pop(); const parent = stack.pop();
if (!parent) continue; if (!parent) continue;
parents.push(parent); parents.push(parent);
stack.push(...(parent.tmp?.parents || [])); stack.push(...(parent.state?.parents || []));
} }
return parents.reverse(); return parents.reverse();
} }
@@ -585,7 +581,7 @@ export class GraphManager extends EventEmitter<{
getPossibleNodes(socket: Socket): NodeDefinition[] { getPossibleNodes(socket: Socket): NodeDefinition[] {
const allDefinitions = this.getNodeDefinitions(); const allDefinitions = this.getNodeDefinitions();
const nodeType = socket.node.tmp?.type; const nodeType = socket.node.state?.type;
if (!nodeType) { if (!nodeType) {
return []; return [];
} }
@@ -612,11 +608,11 @@ export class GraphManager extends EventEmitter<{
} }
getPossibleSockets({ node, index }: Socket): [Node, string | number][] { getPossibleSockets({ node, index }: Socket): [NodeInstance, string | number][] {
const nodeType = node?.tmp?.type; const nodeType = node?.state?.type;
if (!nodeType) return []; if (!nodeType) return [];
const sockets: [Node, string | number][] = []; const sockets: [NodeInstance, string | number][] = [];
// if index is a string, we are an input looking for outputs // if index is a string, we are an input looking for outputs
if (typeof index === "string") { if (typeof index === "string") {
@@ -629,7 +625,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType?.inputs?.[index].type; const ownType = nodeType?.inputs?.[index].type;
for (const node of nodes) { for (const node of nodes) {
const nodeType = node?.tmp?.type; const nodeType = node?.state?.type;
const inputs = nodeType?.outputs; const inputs = nodeType?.outputs;
if (!inputs) continue; if (!inputs) continue;
for (let index = 0; index < inputs.length; index++) { for (let index = 0; index < inputs.length; index++) {
@@ -657,7 +653,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType.outputs?.[index]; const ownType = nodeType.outputs?.[index];
for (const node of nodes) { for (const node of nodes) {
const inputs = node?.tmp?.type?.inputs; const inputs = node?.state?.type?.inputs;
if (!inputs) continue; if (!inputs) continue;
for (const key in inputs) { for (const key in inputs) {
const otherType = [inputs[key].type]; const otherType = [inputs[key].type];
@@ -692,17 +688,15 @@ export class GraphManager extends EventEmitter<{
if (!_edge) return; if (!_edge) return;
edge[0].tmp = edge[0].tmp || {}; if (edge[0].state.children) {
if (edge[0].tmp.children) { edge[0].state.children = edge[0].state.children.filter(
edge[0].tmp.children = edge[0].tmp.children.filter( (n: NodeInstance) => n.id !== id2,
(n: Node) => n.id !== id2,
); );
} }
edge[2].tmp = edge[2].tmp || {}; if (edge[2].state.parents) {
if (edge[2].tmp.parents) { edge[2].state.parents = edge[2].state.parents.filter(
edge[2].tmp.parents = edge[2].tmp.parents.filter( (n: NodeInstance) => n.id !== id0,
(n: Node) => n.id !== id0,
); );
} }
@@ -714,7 +708,7 @@ export class GraphManager extends EventEmitter<{
} }
getEdgesToNode(node: Node) { getEdgesToNode(node: NodeInstance) {
return this.edges return this.edges
.filter((edge) => edge[2].id === node.id) .filter((edge) => edge[2].id === node.id)
.map((edge) => { .map((edge) => {
@@ -723,10 +717,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return; if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const; return [from, edge[1], to, edge[3]] as const;
}) })
.filter(Boolean) as unknown as [Node, number, Node, string][]; .filter(Boolean) as unknown as [NodeInstance, number, NodeInstance, string][];
} }
getEdgesFromNode(node: Node) { getEdgesFromNode(node: NodeInstance) {
return this.edges return this.edges
.filter((edge) => edge[0].id === node.id) .filter((edge) => edge[0].id === node.id)
.map((edge) => { .map((edge) => {
@@ -735,6 +729,6 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return; if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const; return [from, edge[1], to, edge[3]] as const;
}) })
.filter(Boolean) as unknown as [Node, number, Node, string][]; .filter(Boolean) as unknown as [NodeInstance, number, NodeInstance, string][];
} }
} }

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Edge, Node } from "@nodarium/types"; import type { Edge, NodeInstance } from "@nodarium/types";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { createKeyMap } from "../../helpers/createKeyMap"; import { createKeyMap } from "../../helpers/createKeyMap";
import AddMenu from "../components/AddMenu.svelte"; import AddMenu from "../components/AddMenu.svelte";
@@ -40,7 +40,7 @@
return [pos1[0], pos1[1], pos2[0], pos2[1]]; return [pos1[0], pos1[1], pos2[0], pos2[1]];
} }
function handleNodeCreation(node: Node) { function handleNodeCreation(node: NodeInstance) {
const newNode = graph.createNode({ const newNode = graph.createNode({
type: node.type, type: node.type,
position: node.position, position: node.position,
@@ -51,11 +51,11 @@
if (graphState.activeSocket) { if (graphState.activeSocket) {
if (typeof graphState.activeSocket.index === "number") { if (typeof graphState.activeSocket.index === "number") {
const socketType = const socketType =
graphState.activeSocket.node.tmp?.type?.outputs?.[ graphState.activeSocket.node.state?.type?.outputs?.[
graphState.activeSocket.index graphState.activeSocket.index
]; ];
const input = Object.entries(newNode?.tmp?.type?.inputs || {}).find( const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
(inp) => inp[1].type === socketType, (inp) => inp[1].type === socketType,
); );
@@ -69,11 +69,11 @@
} }
} else { } else {
const socketType = const socketType =
graphState.activeSocket.node.tmp?.type?.inputs?.[ graphState.activeSocket.node.state?.type?.inputs?.[
graphState.activeSocket.index graphState.activeSocket.index
]; ];
const output = newNode.tmp?.type?.outputs?.find((out) => { const output = newNode.state?.type?.outputs?.find((out) => {
if (socketType?.type === out) return true; if (socketType?.type === out) return true;
if (socketType?.accepts?.includes(out as any)) return true; if (socketType?.accepts?.includes(out as any)) return true;
return false; return false;

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Graph, Node, NodeRegistry } from "@nodarium/types"; import type { Graph, NodeInstance, NodeRegistry } from "@nodarium/types";
import GraphEl from "./Graph.svelte"; import GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.svelte"; import { GraphManager } from "../graph-manager.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap"; import { createKeyMap } from "$lib/helpers/createKeyMap";
@@ -12,7 +12,7 @@
settings?: Record<string, any>; settings?: Record<string, any>;
activeNode?: Node; activeNode?: NodeInstance;
showGrid?: boolean; showGrid?: boolean;
snapToGrid?: boolean; snapToGrid?: boolean;
showHelp?: boolean; showHelp?: boolean;

View File

@@ -1,4 +1,4 @@
import { GraphSchema, type NodeType, type Node } from "@nodarium/types"; import { GraphSchema, type NodeId, type NodeInstance } from "@nodarium/types";
import type { GraphManager } from "../graph-manager.svelte"; import type { GraphManager } from "../graph-manager.svelte";
import type { GraphState } from "./state.svelte"; import type { GraphState } from "./state.svelte";
import { animate, lerp } from "$lib/helpers"; import { animate, lerp } from "$lib/helpers";
@@ -17,7 +17,7 @@ export class FileDropEventManager {
event.preventDefault(); event.preventDefault();
this.state.isDragging = false; this.state.isDragging = false;
if (!event.dataTransfer) return; if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType; const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
let mx = event.clientX - this.state.rect.x; let mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y; let my = event.clientY - this.state.rect.y;
@@ -131,45 +131,45 @@ export class MouseEventManager {
if (clickedNodeId !== -1) { if (clickedNodeId !== -1) {
if (activeNode) { if (activeNode) {
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) { if (!activeNode?.state?.isMoving && !event.ctrlKey && !event.shiftKey) {
this.state.activeNodeId = clickedNodeId; this.state.activeNodeId = clickedNodeId;
this.state.clearSelection(); this.state.clearSelection();
} }
} }
} }
if (activeNode?.tmp?.isMoving) { if (activeNode?.state?.isMoving) {
activeNode.tmp = activeNode.tmp || {}; activeNode.state = activeNode.state || {};
activeNode.tmp.isMoving = false; activeNode.state.isMoving = false;
if (this.state.snapToGrid) { if (this.state.snapToGrid) {
const snapLevel = this.state.getSnapLevel(); const snapLevel = this.state.getSnapLevel();
activeNode.position[0] = snapPointToGrid( activeNode.position[0] = snapPointToGrid(
activeNode?.tmp?.x ?? activeNode.position[0], activeNode?.state?.x ?? activeNode.position[0],
5 / snapLevel, 5 / snapLevel,
); );
activeNode.position[1] = snapPointToGrid( activeNode.position[1] = snapPointToGrid(
activeNode?.tmp?.y ?? activeNode.position[1], activeNode?.state?.y ?? activeNode.position[1],
5 / snapLevel, 5 / snapLevel,
); );
} else { } else {
activeNode.position[0] = activeNode?.tmp?.x ?? activeNode.position[0]; activeNode.position[0] = activeNode?.state?.x ?? activeNode.position[0];
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1]; activeNode.position[1] = activeNode?.state?.y ?? activeNode.position[1];
} }
const nodes = [ const nodes = [
...[...(this.state.selectedNodes?.values() || [])].map((id) => ...[...(this.state.selectedNodes?.values() || [])].map((id) =>
this.graph.getNode(id), this.graph.getNode(id),
), ),
] as Node[]; ] as NodeInstance[];
const vec = [ const vec = [
activeNode.position[0] - (activeNode?.tmp.x || 0), activeNode.position[0] - (activeNode?.state.x || 0),
activeNode.position[1] - (activeNode?.tmp.y || 0), activeNode.position[1] - (activeNode?.state.y || 0),
]; ];
for (const node of nodes) { for (const node of nodes) {
if (!node) continue; if (!node) continue;
node.tmp = node.tmp || {}; node.state = node.state || {};
const { x, y } = node.tmp; const { x, y } = node.state;
if (x !== undefined && y !== undefined) { if (x !== undefined && y !== undefined) {
node.position[0] = x + vec[0]; node.position[0] = x + vec[0];
node.position[1] = y + vec[1]; node.position[1] = y + vec[1];
@@ -179,14 +179,14 @@ export class MouseEventManager {
animate(500, (a: number) => { animate(500, (a: number) => {
for (const node of nodes) { for (const node of nodes) {
if ( if (
node?.tmp && node?.state &&
node.tmp["x"] !== undefined && node.state["x"] !== undefined &&
node.tmp["y"] !== undefined node.state["y"] !== undefined
) { ) {
node.tmp.x = lerp(node.tmp.x, node.position[0], a); node.state.x = lerp(node.state.x, node.position[0], a);
node.tmp.y = lerp(node.tmp.y, node.position[1], a); node.state.y = lerp(node.state.y, node.position[1], a);
this.state.updateNodePosition(node); this.state.updateNodePosition(node);
if (node?.tmp?.isMoving) { if (node?.state?.isMoving) {
return false; return false;
} }
} }
@@ -318,17 +318,17 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId); const node = this.graph.getNode(this.state.activeNodeId);
if (!node) return; if (!node) return;
node.tmp = node.tmp || {}; node.state = node.state || {};
node.tmp.downX = node.position[0]; node.state.downX = node.position[0];
node.tmp.downY = node.position[1]; node.state.downY = node.position[1];
if (this.state.selectedNodes) { if (this.state.selectedNodes) {
for (const nodeId of this.state.selectedNodes) { for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId); const n = this.graph.getNode(nodeId);
if (!n) continue; if (!n) continue;
n.tmp = n.tmp || {}; n.state = n.state || {};
n.tmp.downX = n.position[0]; n.state.downX = n.position[0];
n.tmp.downY = n.position[1]; n.state.downY = n.position[1];
} }
} }
@@ -382,7 +382,7 @@ export class MouseEventManager {
const y1 = Math.min(mouseD[1], this.state.mousePosition[1]); const y1 = Math.min(mouseD[1], this.state.mousePosition[1]);
const y2 = Math.max(mouseD[1], this.state.mousePosition[1]); const y2 = Math.max(mouseD[1], this.state.mousePosition[1]);
for (const node of this.graph.nodes.values()) { for (const node of this.graph.nodes.values()) {
if (!node?.tmp) continue; if (!node?.state) continue;
const x = node.position[0]; const x = node.position[0];
const y = node.position[1]; const y = node.position[1];
const height = this.state.getNodeHeight(node.type); const height = this.state.getNodeHeight(node.type);
@@ -400,10 +400,10 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId); const node = this.graph.getNode(this.state.activeNodeId);
if (!node || event.buttons !== 1) return; if (!node || event.buttons !== 1) return;
node.tmp = node.tmp || {}; node.state = node.state || {};
const oldX = node.tmp.downX || 0; const oldX = node.state.downX || 0;
const oldY = node.tmp.downY || 0; const oldY = node.state.downY || 0;
let newX = let newX =
oldX + (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2]; oldX + (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2];
@@ -418,10 +418,10 @@ export class MouseEventManager {
} }
} }
if (!node.tmp.isMoving) { if (!node.state.isMoving) {
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2); const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
if (dist > 0.2) { if (dist > 0.2) {
node.tmp.isMoving = true; node.state.isMoving = true;
} }
} }
@@ -431,15 +431,15 @@ export class MouseEventManager {
if (this.state.selectedNodes?.size) { if (this.state.selectedNodes?.size) {
for (const nodeId of this.state.selectedNodes) { for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId); const n = this.graph.getNode(nodeId);
if (!n?.tmp) continue; if (!n?.state) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX; n.state.x = (n?.state?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY; n.state.y = (n?.state?.downY || 0) - vecY;
this.state.updateNodePosition(n); this.state.updateNodePosition(n);
} }
} }
node.tmp.x = newX; node.state.x = newX;
node.tmp.y = newY; node.state.y = newY;
this.state.updateNodePosition(node); this.state.updateNodePosition(node);

View File

@@ -1,4 +1,4 @@
import type { Node, Socket } from "@nodarium/types"; import type { NodeInstance, Socket } from "@nodarium/types";
import { getContext, setContext } from "svelte"; import { getContext, setContext } from "svelte";
import { SvelteSet } from "svelte/reactivity"; import { SvelteSet } from "svelte/reactivity";
import type { GraphManager } from "../graph-manager.svelte"; import type { GraphManager } from "../graph-manager.svelte";
@@ -38,7 +38,7 @@ export class GraphState {
cameraPosition: [number, number, number] = $state([0, 0, 4]); cameraPosition: [number, number, number] = $state([0, 0, 4]);
clipboard: null | { clipboard: null | {
nodes: Node[]; nodes: NodeInstance[];
edges: [number, number, number, string][]; edges: [number, number, number, string][];
} = null; } = null;
@@ -95,26 +95,26 @@ export class GraphState {
} }
updateNodePosition(node: Node) { updateNodePosition(node: NodeInstance) {
if (node?.tmp?.ref && node?.tmp?.mesh) { if (node.state.ref && node.state.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) { if (node.state["x"] !== undefined && node.state["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`); node.state.ref.style.setProperty("--nx", `${node.state.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`); node.state.ref.style.setProperty("--ny", `${node.state.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10; node.state.mesh.position.x = node.state.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2; node.state.mesh.position.z = node.state.y + this.getNodeHeight(node.type) / 2;
if ( if (
node.tmp.x === node.position[0] && node.state.x === node.position[0] &&
node.tmp.y === node.position[1] node.state.y === node.position[1]
) { ) {
delete node.tmp.x; delete node.state.x;
delete node.tmp.y; delete node.state.y;
} }
this.graph.edges = [...this.graph.edges]; this.graph.edges = [...this.graph.edges];
} else { } else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`); node.state.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`); node.state.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10; node.state.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z = node.state.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2; node.position[1] + this.getNodeHeight(node.type) / 2;
} }
} }
@@ -134,19 +134,19 @@ export class GraphState {
} }
getSocketPosition( getSocketPosition(
node: Node, node: NodeInstance,
index: string | number, index: string | number,
): [number, number] { ): [number, number] {
if (typeof index === "number") { if (typeof index === "number") {
return [ return [
(node?.tmp?.x ?? node.position[0]) + 20, (node?.state?.x ?? node.position[0]) + 20,
(node?.tmp?.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.tmp?.type?.inputs || {}).indexOf(index); const _index = Object.keys(node.state?.type?.inputs || {}).indexOf(index);
return [ return [
node?.tmp?.x ?? node.position[0], node?.state?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index, (node?.state?.y ?? node.position[1]) + 10 + 10 * _index,
]; ];
} }
} }
@@ -174,32 +174,6 @@ export class GraphState {
return height; return height;
} }
setNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
}
}
}
copyNodes() { copyNodes() {
if (this.activeNodeId === -1 && !this.selectedNodes?.size) if (this.activeNodeId === -1 && !this.selectedNodes?.size)
return; return;
@@ -231,12 +205,11 @@ export class GraphState {
const nodes = this.clipboard.nodes const nodes = this.clipboard.nodes
.map((node) => { .map((node) => {
node.tmp = node.tmp || {};
node.position[0] = this.mousePosition[0] - node.position[0]; node.position[0] = this.mousePosition[0] - node.position[0];
node.position[1] = this.mousePosition[1] - node.position[1]; node.position[1] = this.mousePosition[1] - node.position[1];
return node; return node;
}) })
.filter(Boolean) as Node[]; .filter(Boolean) as NodeInstance[];
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges); const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
this.selectedNodes.clear(); this.selectedNodes.clear();
@@ -327,7 +300,7 @@ export class GraphState {
return clickedNodeId; return clickedNodeId;
} }
isNodeInView(node: Node) { isNodeInView(node: NodeInstance) {
const height = this.getNodeHeight(node.type); const height = this.getNodeHeight(node.type);
const width = 20; const width = 20;
return ( return (

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Node } from "@nodarium/types"; import type { NodeInstance } from "@nodarium/types";
import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte"; import { getGraphState } from "../graph/state.svelte";
import { T } from "@threlte/core"; import { T } from "@threlte/core";
import { type Mesh } from "three"; import { type Mesh } from "three";
@@ -13,7 +12,7 @@
const graphState = getGraphState(); const graphState = getGraphState();
type Props = { type Props = {
node: Node; node: NodeInstance;
inView: boolean; inView: boolean;
z: number; z: number;
}; };
@@ -35,9 +34,8 @@
const height = graphState.getNodeHeight(node.type); const height = graphState.getNodeHeight(node.type);
$effect(() => { $effect(() => {
if (!node.tmp) node.tmp = {}; if (meshRef && !node.state?.mesh) {
if (meshRef && !node.tmp?.mesh) { node.state.mesh = meshRef;
node.tmp.mesh = meshRef;
graphState.updateNodePosition(node); graphState.updateNodePosition(node);
} }
}); });

View File

@@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Node } from "@nodarium/types"; import type { NodeInstance, SerializedNode } from "@nodarium/types";
import NodeHeader from "./NodeHeader.svelte"; import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte"; import NodeParameter from "./NodeParameter.svelte";
import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte"; import { getGraphState } from "../graph/state.svelte";
let ref: HTMLDivElement; let ref: HTMLDivElement;
@@ -10,7 +9,7 @@
const graphState = getGraphState(); const graphState = getGraphState();
type Props = { type Props = {
node: Node; node: SerializedNode | NodeInstance;
position?: "absolute" | "fixed" | "relative"; position?: "absolute" | "fixed" | "relative";
isActive?: boolean; isActive?: boolean;
isSelected?: boolean; isSelected?: boolean;
@@ -31,15 +30,21 @@
const zOffset = Math.random() - 0.5; const zOffset = Math.random() - 0.5;
const zLimit = 2 - zOffset; const zLimit = 2 - zOffset;
const parameters = Object.entries(node?.tmp?.type?.inputs || {}).filter( const parameters =
"state" in node
? Object.entries(node.state?.type?.inputs || {}).filter(
(p) => (p) =>
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true, p[1].type !== "seed" &&
); !("setting" in p[1]) &&
p[1]?.hidden !== true,
)
: [];
onMount(() => { $effect(() => {
node.tmp = node.tmp || {}; if ("state" in node && !node.state.ref) {
node.tmp.ref = ref; node.state.ref = ref;
graphState?.updateNodePosition(node); graphState?.updateNodePosition(node);
}
}); });
</script> </script>

View File

@@ -1,24 +1,27 @@
<script lang="ts"> <script lang="ts">
import { getGraphState } from "../graph/state.svelte.js"; import { getGraphState } from "../graph/state.svelte.js";
import { createNodePath } from "../helpers/index.js"; import { createNodePath } from "../helpers/index.js";
import type { Node } from "@nodarium/types"; import type { NodeInstance, SerializedNode } from "@nodarium/types";
const graphState = getGraphState(); const graphState = getGraphState();
const { node }: { node: Node } = $props(); const { node }: { node: NodeInstance | SerializedNode } = $props();
function handleMouseDown(event: MouseEvent) { function handleMouseDown(event: MouseEvent) {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if ("state" in node) {
graphState.setDownSocket?.({ graphState.setDownSocket?.({
node, node,
index: 0, index: 0,
position: graphState.getSocketPosition?.(node, 0), position: graphState.getSocketPosition?.(node, 0),
}); });
} }
}
const cornerTop = 10; const cornerTop = 10;
const rightBump = !!node?.tmp?.type?.outputs?.length; const rightBump =
"state" in node ? !!node?.state?.type?.outputs?.length : false;
const aspectRatio = 0.25; const aspectRatio = 0.25;
const path = createNodePath({ const path = createNodePath({
@@ -29,14 +32,6 @@
rightBump, rightBump,
aspectRatio, aspectRatio,
}); });
// const pathDisabled = createNodePath({
// depth: 0,
// height: 15,
// y: 50,
// cornerTop,
// rightBump,
// aspectRatio,
// });
const pathHover = createNodePath({ const pathHover = createNodePath({
depth: 8.5, depth: 8.5,
height: 50, height: 50,

View File

@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Node, NodeInput } from "@nodarium/types"; import type { NodeInstance, NodeInput } from "@nodarium/types";
import { Input } from "@nodarium/ui"; import { Input } from "@nodarium/ui";
import type { GraphManager } from "../graph-manager.svelte"; import type { GraphManager } from "../graph-manager.svelte";
type Props = { type Props = {
node: Node; node: NodeInstance;
input: NodeInput; input: NodeInput;
id: string; id: string;
elementId?: string; elementId?: string;

View File

@@ -1,14 +1,15 @@
<script lang="ts"> <script lang="ts">
import type { import type {
NodeInput as NodeInputType, NodeInput as NodeInputType,
Node as NodeType, NodeInstance,
SerializedNode,
} from "@nodarium/types"; } from "@nodarium/types";
import { createNodePath } from "../helpers/index.js"; import { createNodePath } from "../helpers/index.js";
import NodeInput from "./NodeInput.svelte"; import NodeInput from "./NodeInput.svelte";
import { getGraphManager, getGraphState } from "../graph/state.svelte.js"; import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
type Props = { type Props = {
node: NodeType; node: NodeInstance | SerializedNode;
input: NodeInputType; input: NodeInputType;
id: string; id: string;
isLast?: boolean; isLast?: boolean;
@@ -18,7 +19,7 @@
let { node = $bindable(), input, id, isLast }: Props = $props(); let { node = $bindable(), input, id, isLast }: Props = $props();
const inputType = node?.tmp?.type?.inputs?.[id]!; const inputType = node?.state?.type?.inputs?.[id]!;
const socketId = `${node.id}-${id}`; const socketId = `${node.id}-${id}`;
@@ -37,7 +38,7 @@
}); });
} }
const leftBump = node.tmp?.type?.inputs?.[id].internal !== true; const leftBump = node.state?.type?.inputs?.[id].internal !== true;
const cornerBottom = isLast ? 5 : 0; const cornerBottom = isLast ? 5 : 0;
const aspectRatio = 0.5; const aspectRatio = 0.5;
@@ -83,7 +84,7 @@
{/if} {/if}
</div> </div>
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true} {#if node?.state?.type?.inputs?.[id]?.internal !== true}
<div data-node-socket class="large target"></div> <div data-node-socket class="large target"></div>
<div <div
data-node-socket data-node-socket

View File

@@ -16,9 +16,6 @@ export function grid(width: number, height: number) {
graph.nodes.push({ graph.nodes.push({
id: i, id: i,
tmp: {
visible: false,
},
position: [x * 30, y * 40], position: [x * 30, y * 40],
props: i == 0 ? { value: 0 } : { op_type: 0, a: 1, b: 0.05 }, props: i == 0 ? { value: 0 } : { op_type: 0, a: 1, b: 0.05 },
type: i == 0 ? "max/plantarium/float" : "max/plantarium/math", type: i == 0 ? "max/plantarium/float" : "max/plantarium/math",
@@ -29,9 +26,6 @@ export function grid(width: number, height: number) {
graph.nodes.push({ graph.nodes.push({
id: amount, id: amount,
tmp: {
visible: false,
},
position: [width * 30, (height - 1) * 40], position: [width * 30, (height - 1) * 40],
type: "max/plantarium/output", type: "max/plantarium/output",
props: {}, props: {},

View File

@@ -1,8 +1,8 @@
import type { Graph, Node } from "@nodarium/types"; import type { Graph, SerializedNode } from "@nodarium/types";
export function tree(depth: number): Graph { export function tree(depth: number): Graph {
const nodes: Node[] = [ const nodes: SerializedNode[] = [
{ {
id: 0, id: 0,
type: "max/plantarium/output", type: "max/plantarium/output",

View File

@@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte"; import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import type { NodeDefinition } from "@nodarium/types"; import type { SerializedNode } from "@nodarium/types";
export let node: NodeDefinition; const { node }: { node: SerializedNode } = $props();
let dragging = false; let dragging = $state(false);
let nodeData = { let nodeData = $state({
id: 0, id: 0,
type: node?.id, type: node?.id,
position: [0, 0] as [number, number], position: [0, 0] as [number, number],
@@ -14,14 +14,14 @@
tmp: { tmp: {
type: node, type: node,
}, },
}; });
function handleDragStart(e: DragEvent) { function handleDragStart(e: DragEvent) {
dragging = true; dragging = true;
const box = (e?.target as HTMLElement)?.getBoundingClientRect(); const box = (e?.target as HTMLElement)?.getBoundingClientRect();
if (e.dataTransfer === null) return; if (e.dataTransfer === null) return;
e.dataTransfer.effectAllowed = "move"; e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("data/node-id", node.id); e.dataTransfer.setData("data/node-id", node.id.toString());
if (nodeData.props) { if (nodeData.props) {
e.dataTransfer.setData("data/node-props", JSON.stringify(nodeData.props)); e.dataTransfer.setData("data/node-props", JSON.stringify(nodeData.props));
} }
@@ -38,13 +38,13 @@
<div class="node-wrapper" class:dragging> <div class="node-wrapper" class:dragging>
<div <div
on:dragend={() => { ondragend={() => {
dragging = false; dragging = false;
}} }}
draggable={true} draggable={true}
role="button" role="button"
tabindex="0" tabindex="0"
on:dragstart={handleDragStart} ondragstart={handleDragStart}
> >
<NodeHtml inView={true} position={"relative"} z={5} bind:node={nodeData} /> <NodeHtml inView={true} position={"relative"} z={5} bind:node={nodeData} />
</div> </div>

View File

@@ -1,6 +1,5 @@
import type { import type {
Graph, Graph,
Node,
NodeDefinition, NodeDefinition,
NodeInput, NodeInput,
NodeRegistry, NodeRegistry,
@@ -14,6 +13,7 @@ import {
fastHashArrayBuffer, fastHashArrayBuffer,
type PerformanceStore, type PerformanceStore,
} from "@nodarium/utils"; } from "@nodarium/utils";
import type { RuntimeNode } from "./types";
const log = createLogger("runtime-executor"); const log = createLogger("runtime-executor");
log.mute(); log.mute();
@@ -92,18 +92,27 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// First, lets check if all nodes have a definition // First, lets check if all nodes have a definition
this.definitionMap = await this.getNodeDefinitions(graph); this.definitionMap = await this.getNodeDefinitions(graph);
const outputNode = graph.nodes.find((node) => const graphNodes = graph.nodes.map(node => {
const n = node as RuntimeNode;
n.state = {
depth: 0,
children: [],
parents: [],
inputNodes: {},
}
return n
})
const outputNode = graphNodes.find((node) =>
node.type.endsWith("/output"), node.type.endsWith("/output"),
) as Node; );
if (!outputNode) { if (!outputNode) {
throw new Error("No output node found"); throw new Error("No output node found");
} }
outputNode.tmp = outputNode.tmp || {}; const nodeMap = new Map(
outputNode.tmp.depth = 0; graphNodes.map((node) => [node.id, node]),
const nodeMap = new Map<number, Node>(
graph.nodes.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
@@ -112,14 +121,9 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
const parent = nodeMap.get(parentId); const parent = nodeMap.get(parentId);
const child = nodeMap.get(childId); const child = nodeMap.get(childId);
if (parent && child) { if (parent && child) {
parent.tmp = parent.tmp || {}; parent.state.children.push(child);
parent.tmp.children = parent.tmp.children || []; child.state.parents.push(parent);
parent.tmp.children.push(child); child.state.inputNodes[childInput] = parent;
child.tmp = child.tmp || {};
child.tmp.parents = child.tmp.parents || [];
child.tmp.parents.push(parent);
child.tmp.inputNodes = child.tmp.inputNodes || {};
child.tmp.inputNodes[childInput] = parent;
} }
} }
@@ -130,20 +134,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
while (stack.length) { while (stack.length) {
const node = stack.pop(); const node = stack.pop();
if (!node) continue; if (!node) continue;
node.tmp = node.tmp || {}; for (const parent of node.state.parents) {
if (node?.tmp?.depth === undefined) { parent.state = parent.state || {};
node.tmp.depth = 0; parent.state.depth = node.state.depth + 1;
}
if (node?.tmp?.parents !== undefined) {
for (const parent of node.tmp.parents) {
parent.tmp = parent.tmp || {};
if (parent.tmp?.depth === undefined) {
parent.tmp.depth = node.tmp.depth + 1;
stack.push(parent); stack.push(parent);
} else {
parent.tmp.depth = Math.max(parent.tmp.depth, node.tmp.depth + 1);
}
}
} }
nodes.push(node); nodes.push(node);
} }
@@ -175,7 +169,7 @@ 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.tmp?.depth || 0) - (a.tmp?.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
@@ -188,7 +182,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
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_type || !node.tmp || !node_type.execute) { if (!node_type || !node.state || !node_type.execute) {
log.warn(`Node ${node.id} has no definition`); log.warn(`Node ${node.id} has no definition`);
continue; continue;
} }
@@ -208,7 +202,7 @@ 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.tmp?.inputNodes?.[key]; const inputNode = node.state.inputNodes[key];
if (inputNode) { if (inputNode) {
if (results[inputNode.id] === undefined) { if (results[inputNode.id] === undefined) {
throw new Error( throw new Error(

View File

@@ -0,0 +1,10 @@
import type { SerializedNode } from "@nodarium/types";
type RuntimeState = {
depth: number
parents: RuntimeNode[],
children: RuntimeNode[],
inputNodes: Record<string, RuntimeNode>
}
export type RuntimeNode = SerializedNode & { state: RuntimeState }

View File

@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { Node, NodeInput } from "@nodarium/types"; import type { NodeInstance, NodeInput } from "@nodarium/types";
import NestedSettings from "$lib/settings/NestedSettings.svelte"; import NestedSettings from "$lib/settings/NestedSettings.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte"; import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
type Props = { type Props = {
manager: GraphManager; manager: GraphManager;
node: Node; node: NodeInstance;
}; };
const { manager, node = $bindable() }: Props = $props(); const { manager, node = $bindable() }: Props = $props();
@@ -19,19 +19,19 @@
}) })
.map(([key, value]) => { .map(([key, value]) => {
//@ts-ignore //@ts-ignore
value.__node_type = node?.tmp?.type.id; value.__node_type = node.state?.type.id;
//@ts-ignore //@ts-ignore
value.__node_input = key; value.__node_input = key;
return [key, value]; return [key, value];
}), }),
); );
} }
const nodeDefinition = filterInputs(node.tmp?.type?.inputs); const nodeDefinition = filterInputs(node.state?.type?.inputs);
type Store = Record<string, number | number[]>; type Store = Record<string, number | number[]>;
let store = $state<Store>(createStore(node?.props, nodeDefinition)); let store = $state<Store>(createStore(node?.props, nodeDefinition));
function createStore( function createStore(
props: Node["props"], props: NodeInstance["props"],
inputs: Record<string, NodeInput>, inputs: Record<string, NodeInput>,
): Store { ): Store {
const store: Store = {}; const store: Store = {};

View File

@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { Node } from "@nodarium/types"; import type { NodeInstance } from "@nodarium/types";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte"; import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
import ActiveNodeSelected from "./ActiveNodeSelected.svelte"; import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
type Props = { type Props = {
manager: GraphManager; manager: GraphManager;
node: Node | undefined; node: NodeInstance | undefined;
}; };
const { manager, node }: Props = $props(); const { manager, node }: Props = $props();

View File

@@ -2,7 +2,7 @@
import Grid from "$lib/grid"; import Grid from "$lib/grid";
import GraphInterface from "$lib/graph-interface"; import GraphInterface from "$lib/graph-interface";
import * as templates from "$lib/graph-templates"; import * as templates from "$lib/graph-templates";
import type { Graph, Node } from "@nodarium/types"; import type { Graph, NodeInstance } from "@nodarium/types";
import Viewer from "$lib/result-viewer/Viewer.svelte"; import Viewer from "$lib/result-viewer/Viewer.svelte";
import { import {
appSettings, appSettings,
@@ -42,7 +42,7 @@
appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime, appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime,
); );
let activeNode = $state<Node | undefined>(undefined); let activeNode = $state<NodeInstance | undefined>(undefined);
let scene = $state<Group>(null!); let scene = $state<Group>(null!);
let graph = $state( let graph = $state(

View File

@@ -1,4 +1,4 @@
import type { Graph, NodeDefinition, NodeType } from "./types"; import type { Graph, NodeDefinition, NodeId } from "./types";
export interface NodeRegistry { export interface NodeRegistry {
/** /**
@@ -13,13 +13,13 @@ export interface NodeRegistry {
* @throws An error if the nodes could not be loaded * @throws An error if the nodes could not be loaded
* @remarks This method should be called before calling getNode or getAllNodes * @remarks This method should be called before calling getNode or getAllNodes
*/ */
load: (nodeIds: NodeType[]) => Promise<NodeDefinition[]>; load: (nodeIds: NodeId[]) => Promise<NodeDefinition[]>;
/** /**
* Get a node by id * Get a node by id
* @param id - The id of the node to get * @param id - The id of the node to get
* @returns The node with the given id, or undefined if no such node exists * @returns The node with the given id, or undefined if no such node exists
*/ */
getNode: (id: NodeType | string) => NodeDefinition | undefined; getNode: (id: NodeId | string) => NodeDefinition | undefined;
/** /**
* Get all nodes * Get all nodes
* @returns An array of all nodes * @returns An array of all nodes

View File

@@ -6,10 +6,11 @@ export type {
AsyncCache, AsyncCache,
} from "./components"; } from "./components";
export type { export type {
Node, SerializedNode,
NodeInstance,
NodeDefinition, NodeDefinition,
Socket, Socket,
NodeType, NodeId,
Edge, Edge,
Graph, Graph,
} from "./types"; } from "./types";

View File

@@ -1,23 +1,19 @@
import { z } from "zod"; import { z } from "zod";
import { NodeInputSchema } from "./inputs"; import { NodeInputSchema } from "./inputs";
export const NodeTypeSchema = z export const NodeIdSchema = z
.string() .string()
.regex(/^[^/]+\/[^/]+\/[^/]+$/, "Invalid NodeId format") .regex(/^[^/]+\/[^/]+\/[^/]+$/, "Invalid NodeId format")
.transform((value) => value as `${string}/${string}/${string}`); .transform((value) => value as `${string}/${string}/${string}`);
export type NodeType = z.infer<typeof NodeTypeSchema>; export type NodeId = z.infer<typeof NodeIdSchema>;
export type Node = { export type NodeRuntimeState = {
/**
* .tmp only exists at runtime
*/
tmp?: {
depth?: number; depth?: number;
mesh?: any; mesh?: any;
parents?: Node[]; parents?: NodeInstance[];
children?: Node[]; children?: NodeInstance[];
inputNodes?: Record<string, Node>; inputNodes?: Record<string, NodeInstance>;
type?: NodeDefinition; type?: NodeDefinition;
downX?: number; downX?: number;
downY?: number; downY?: number;
@@ -26,11 +22,14 @@ export type Node = {
ref?: HTMLElement; ref?: HTMLElement;
visible?: boolean; visible?: boolean;
isMoving?: boolean; isMoving?: boolean;
}; };
} & z.infer<typeof NodeSchema>;
export type NodeInstance = {
state: NodeRuntimeState;
} & SerializedNode;
export const NodeDefinitionSchema = z.object({ export const NodeDefinitionSchema = z.object({
id: NodeTypeSchema, id: NodeIdSchema,
inputs: z.record(z.string(), NodeInputSchema).optional(), inputs: z.record(z.string(), NodeInputSchema).optional(),
outputs: z.array(z.string()).optional(), outputs: z.array(z.string()).optional(),
meta: z meta: z
@@ -43,7 +42,7 @@ export const NodeDefinitionSchema = z.object({
export const NodeSchema = z.object({ export const NodeSchema = z.object({
id: z.number(), id: z.number(),
type: NodeTypeSchema, type: NodeIdSchema,
props: z props: z
.record(z.string(), z.union([z.number(), z.array(z.number())])) .record(z.string(), z.union([z.number(), z.array(z.number())]))
.optional(), .optional(),
@@ -56,17 +55,20 @@ export const NodeSchema = z.object({
position: z.tuple([z.number(), z.number()]), position: z.tuple([z.number(), z.number()]),
}); });
export type SerializedNode = z.infer<typeof NodeSchema>;
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & { export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
execute(input: Int32Array): Int32Array; execute(input: Int32Array): Int32Array;
}; };
export type Socket = { export type Socket = {
node: Node; node: NodeInstance;
index: number | string; index: number | string;
position: [number, number]; position: [number, number];
}; };
export type Edge = [Node, number, Node, string]; export type Edge = [NodeInstance, number, NodeInstance, string];
export const GraphSchema = z.object({ export const GraphSchema = z.object({
id: z.number(), id: z.number(),
@@ -81,4 +83,4 @@ export const GraphSchema = z.object({
edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])), edges: z.array(z.tuple([z.number(), z.number(), z.number(), z.string()])),
}); });
export type Graph = z.infer<typeof GraphSchema> & { nodes: Node[] }; export type Graph = z.infer<typeof GraphSchema>;

View File

@@ -6,12 +6,7 @@
key: string | string[]; key: string | string[];
} }
let { let { ctrl = false, shift = false, alt = false, key }: Props = $props();
ctrl = false,
shift = false,
alt = false,
key
}: Props = $props();
</script> </script>
<div class="command"> <div class="command">
@@ -38,7 +33,7 @@
} }
span::after { span::after {
content: " +"; content: ' +';
opacity: 0.5; opacity: 0.5;
} }
</style> </style>