feat: make node definitions type safe with zod

This commit is contained in:
2024-04-22 00:33:04 +02:00
parent 4c7c4cac2c
commit ad197db873
28 changed files with 221 additions and 147 deletions

View File

@ -6,6 +6,7 @@
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<title>Nodes</title>
<script>
var store = localStorage.getItem("node-settings");
if (store) {

View File

@ -11,7 +11,7 @@
let value: string = "";
let activeNodeId: string = "";
const allNodes = graph.getNodeTypes();
const allNodes = graph.getNodeDefinitions();
function filterNodes() {
return allNodes.filter((node) => node.id.includes(value));

View File

@ -51,7 +51,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
}
this.inputSockets.set(s);
});
this.execute = throttle(() => this._execute(), 50);
this.execute = throttle(() => this._execute(), 10);
}
serialize(): Graph {
@ -83,7 +83,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
this.emit("result", this.serialize());
}
getNodeTypes() {
getNodeDefinitions() {
return this.registry.getAllNodes();
}
@ -184,7 +184,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph, "result": any, "
// load settings
const settingTypes: Record<string, NodeInput> = {};
const settingValues = graph.settings || {};
const types = this.getNodeTypes();
const types = this.getNodeDefinitions();
for (const type of types) {
if (type.inputs) {
for (const key in type.inputs) {

View File

@ -8,6 +8,7 @@
import Camera from "../Camera.svelte";
import GraphView from "./GraphView.svelte";
import type { Node, Node as NodeType, Socket } from "@nodes/types";
import { NodeDefinitionSchema } from "@nodes/types";
import FloatingEdge from "../edges/FloatingEdge.svelte";
import {
activeNodeId,
@ -20,7 +21,7 @@
import { createKeyMap } from "../../helpers/createKeyMap";
import BoxSelection from "../BoxSelection.svelte";
import AddMenu from "../AddMenu.svelte";
import { get } from "svelte/store";
import { createWasmWrapper } from "@nodes/utils";
export let graph: GraphManager;
@ -759,30 +760,63 @@
addMenuPosition = null;
}
let isDragging = false;
function handleDrop(event: DragEvent) {
event.preventDefault();
isDragging = false;
if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id");
let mx = event.clientX - rect.x;
let my = event.clientY - rect.y;
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
if (nodeOffsetX && nodeOffsetY) {
mx += parseInt(nodeOffsetX);
my += parseInt(nodeOffsetY);
}
if (nodeId) {
let mx = event.clientX - rect.x;
let my = event.clientY - rect.y;
const pos = projectScreenToWorld(mx, my);
graph.loadNode(nodeId).then(() => {
graph.createNode({
type: nodeId,
props: {},
position: pos,
let nodeOffsetX = event.dataTransfer.getData("data/node-offset-x");
let nodeOffsetY = event.dataTransfer.getData("data/node-offset-y");
if (nodeOffsetX && nodeOffsetY) {
mx += parseInt(nodeOffsetX);
my += parseInt(nodeOffsetY);
}
const pos = projectScreenToWorld(mx, my);
graph.loadNode(nodeId).then(() => {
graph.createNode({
type: nodeId,
props: {},
position: pos,
});
});
});
} else if (event.dataTransfer.files.length) {
const files = event.dataTransfer.files;
const reader = new FileReader();
reader.onload = (e) => {
const buffer = e.target?.result;
if (buffer) {
const wrapper = createWasmWrapper(buffer);
const definition = wrapper.get_definition();
const res = NodeDefinitionSchema.parse(definition);
console.log(wrapper, res);
}
};
reader.readAsArrayBuffer(files[0]);
console.log({ files });
}
}
function handleDragEnter(e: DragEvent) {
e.preventDefault();
isDragging = true;
console.log(e);
}
function handlerDragOver(e: DragEvent) {
isDragging = true;
e.preventDefault();
}
function handleDragEnd(e: DragEvent) {
isDragging = false;
e.preventDefault();
}
@ -807,11 +841,20 @@
tabindex="0"
bind:clientWidth={width}
bind:clientHeight={height}
on:dragenter={handleDragEnter}
on:dragover={handlerDragOver}
on:drop={handleDrop}
on:keydown={keymap.handleKeyboardEvent}
on:mousedown={handleMouseDown}
>
<input
type="file"
accept="application/wasm"
disabled={!isDragging}
on:dragend={handleDragEnd}
on:dragleave={handleDragEnd}
/>
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
<Camera bind:camera position={cameraPosition} />
@ -856,4 +899,15 @@
transition: opacity 0.3s ease;
height: 100%;
}
input {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
background: red;
opacity: 0.5;
}
input:disabled {
display: none;
}
</style>

View File

@ -1,4 +1,4 @@
import type { NodeRegistry, NodeType } from "@nodes/types";
import type { NodeRegistry, NodeDefinition } from "@nodes/types";
import { createWasmWrapper } from "@nodes/utils";
import { createLogger } from "./helpers";
@ -6,18 +6,14 @@ const log = createLogger("node-registry");
export class RemoteNodeRegistry implements NodeRegistry {
status: "loading" | "ready" | "error" = "loading";
private nodes: Map<string, NodeType> = new Map();
private nodes: Map<string, NodeDefinition> = new Map();
constructor(private url: string) { }
async loadNode(id: `${string}/${string}/${string}`) {
const wasmResponse = await this.fetchNode(id);
// Setup Wasm wrapper
const wrapper = createWasmWrapper();
const module = new WebAssembly.Module(wasmResponse);
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
wrapper.setInstance(instance);
const wrapper = createWasmWrapper(wasmResponse);
const definition = wrapper.get_definition();

View File

@ -13,9 +13,7 @@ export async function getWasm(id: `${string}/${string}/${string}`) {
const file = await fs.readFile(filePath);
const bytes = new Uint8Array(file);
return bytes;
return new Uint8Array(file);
}
@ -24,10 +22,7 @@ export async function getNodeWasm(id: `${string}/${string}/${string}`) {
const wasmBytes = await getWasm(id);
if (!wasmBytes) return null;
const wrapper = createWasmWrapper();
const module = new WebAssembly.Module(wasmBytes);
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
wrapper.setInstance(instance)
const wrapper = createWasmWrapper(wasmBytes);
return wrapper;
}

View File

@ -1,8 +1,8 @@
<script lang="ts">
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import type { NodeType } from "@nodes/types";
import type { NodeDefinitions } from "@nodes/types";
export let node: NodeType;
export let node: NodeDefinitions;
let dragging = false;

View File

@ -1,19 +1,10 @@
<script lang="ts">
import type { GraphManager } from "$lib/graph-interface/graph-manager";
import Node from "$lib/graph-interface/node/Node.svelte";
import localStore from "$lib/helpers/localStore";
import type { RemoteNodeRegistry } from "$lib/node-registry-client";
import { Canvas } from "@threlte/core";
import BreadCrumbs from "./BreadCrumbs.svelte";
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import DraggableNode from "./DraggableNode.svelte";
export let nodeRegistry: RemoteNodeRegistry;
export let manager: GraphManager;
function handleImport() {
nodeRegistry.load([$activeId]);
}
const activeId = localStore<
`${string}` | `${string}/${string}` | `${string}/${string}/${string}`

View File

@ -1,21 +1,21 @@
import type { Graph, NodeRegistry, NodeType, RuntimeExecutor } from "@nodes/types";
import type { Graph, NodeRegistry, NodeDefinition, RuntimeExecutor } from "@nodes/types";
import { fastHash, concat_encoded, encodeFloat, encode } from "@nodes/utils"
export class MemoryRuntimeExecutor implements RuntimeExecutor {
private typeMap: Map<string, NodeType> = new Map();
private definitionMap: Map<string, NodeDefinition> = new Map();
private cache: Record<string, { eol: number, value: any }> = {};
constructor(private registry: NodeRegistry) { }
private getNodeTypes(graph: Graph) {
private getNodeDefinitions(graph: Graph) {
if (this.registry.status !== "ready") {
throw new Error("Node registry is not ready");
}
const typeMap = new Map<string, NodeType>();
const typeMap = new Map<string, NodeDefinition>();
for (const node of graph.nodes) {
if (!typeMap.has(node.type)) {
const type = this.registry.getNode(node.type);
@ -29,8 +29,8 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
private addMetaData(graph: Graph) {
// First, lets check if all nodes have a type
this.typeMap = this.getNodeTypes(graph);
// First, lets check if all nodes have a definition
this.definitionMap = this.getNodeDefinitions(graph);
const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
if (!outputNode) {
@ -115,7 +115,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
for (const node of sortedNodes) {
const node_type = this.typeMap.get(node.type)!;
const node_type = this.definitionMap.get(node.type)!;
if (node?.tmp && node_type?.execute) {
const inputs: Record<string, string | number | boolean> = {};

View File

@ -105,6 +105,7 @@
top: 0px;
position: absolute;
display: grid;
z-index: 2;
grid-template-columns: 30px 1fr;
height: 100%;
right: 0px;