feat: add help view
Some checks failed
Deploy to GitHub Pages / build_site (push) Failing after 1m20s

This commit is contained in:
max_richter 2024-04-26 15:30:52 +02:00
parent d06b33f508
commit cafe9bff84
16 changed files with 256 additions and 87 deletions

View File

@ -24,7 +24,7 @@
.box-selection { .box-selection {
width: 40px; width: 40px;
height: 20px; height: 20px;
border: solid 0.2px var(--outline); border: solid 2px var(--outline);
border-style: dashed; border-style: dashed;
border-radius: 2px; border-radius: 2px;
} }

View File

@ -0,0 +1,83 @@
<script lang="ts">
import type { Node, NodeRegistry } from "@nodes/types";
import { onMount } from "svelte";
let mx = 0;
let my = 0;
let node: Node | undefined = undefined;
let input: string | undefined = undefined;
let wrapper: HTMLDivElement;
export let registry: NodeRegistry;
function handleMouseOver(ev: MouseEvent) {
let target = ev.target as HTMLElement | null;
mx = ev.clientX;
my = ev.clientY;
if (!target) return;
const closest = target.closest("[data-node-type]");
if (!closest) {
node = undefined;
return;
}
let nodeType = closest.getAttribute("data-node-type");
let nodeInput = closest.getAttribute("data-node-input");
if (!nodeType) {
node = undefined;
return;
}
node = registry.getNode(nodeType);
input = nodeInput;
}
onMount(() => {
wrapper?.parentElement?.setAttribute("style", "cursor:help !important");
return () => {
wrapper?.parentElement?.style.removeProperty("cursor");
};
});
</script>
<svelte:window on:mousemove={handleMouseOver} />
<div
class="help-wrapper"
class:visible={node}
style="--my:{my}px; --mx:{mx}px;"
bind:this={wrapper}
>
{#if node}
{#if input}
{input}
{:else}
{node.id}
{/if}
{/if}
</div>
<style>
.help-wrapper {
position: fixed;
pointer-events: none;
transform: translate(var(--mx), var(--my));
background: red;
padding: 10px;
top: 0px;
left: 0px;
width: 50px;
height: 50px;
border: 1px solid black;
z-index: 1000;
display: none;
}
.visible {
display: block;
}
</style>

View File

@ -21,8 +21,6 @@
bw = width / cameraPosition[2]; bw = width / cameraPosition[2];
bh = height / cameraPosition[2]; bh = height / cameraPosition[2];
} }
$: backgroundColor = $colors["layer-0"];
$: lineColor = $colors["outline"];
</script> </script>
<T.Group <T.Group
@ -54,8 +52,8 @@
}, },
}} }}
uniforms.camPos.value={cameraPosition} uniforms.camPos.value={cameraPosition}
uniforms.backgroundColor.value={backgroundColor} uniforms.backgroundColor.value={$colors["layer-0"]}
uniforms.lineColor.value={lineColor} uniforms.lineColor.value={$colors["outline"]}
uniforms.zoomLimits.value={[minZoom, maxZoom]} uniforms.zoomLimits.value={[minZoom, maxZoom]}
uniforms.dimensions.value={[width, height]} uniforms.dimensions.value={[width, height]}
/> />

View File

@ -1,5 +1,9 @@
<script lang="ts"> <script lang="ts">
import { animate, lerp, snapToGrid } from "../helpers/index.js"; import {
animate,
lerp,
snapToGrid as snapPointToGrid,
} from "../helpers/index.js";
import { Canvas } from "@threlte/core"; import { Canvas } from "@threlte/core";
import type { OrthographicCamera } from "three"; import type { OrthographicCamera } from "three";
import Background from "../background/Background.svelte"; import Background from "../background/Background.svelte";
@ -23,20 +27,21 @@
import AddMenu from "../AddMenu.svelte"; import AddMenu from "../AddMenu.svelte";
import { createWasmWrapper } from "@nodes/utils"; import { createWasmWrapper } from "@nodes/utils";
export let graph: GraphManager; import HelpView from "../HelpView.svelte";
export let settings = { export let manager: GraphManager;
snapToGrid: true,
showGrid: true, export let snapToGrid = true;
}; export let showGrid = true;
export let showHelp = false;
let keymap = let keymap =
getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]); getContext<ReturnType<typeof createKeyMap>>("keymap") || createKeyMap([]);
setContext("graphManager", graph); setContext("graphManager", manager);
const status = graph.status; const status = manager.status;
const nodes = graph.nodes; const nodes = manager.nodes;
const edges = graph.edges; const edges = manager.edges;
let wrapper: HTMLDivElement; let wrapper: HTMLDivElement;
let rect: DOMRect; let rect: DOMRect;
@ -114,7 +119,7 @@
if (nodeTypeId in nodeHeightCache) { if (nodeTypeId in nodeHeightCache) {
return nodeHeightCache[nodeTypeId]; return nodeHeightCache[nodeTypeId];
} }
const node = graph.getNodeType(nodeTypeId); const node = manager.getNodeType(nodeTypeId);
if (!node?.inputs) { if (!node?.inputs) {
return 5; return 5;
} }
@ -185,13 +190,13 @@
// remove existing edge // remove existing edge
if (typeof index === "string") { if (typeof index === "string") {
const edges = graph.getEdgesToNode(node); const edges = manager.getEdgesToNode(node);
for (const edge of edges) { for (const edge of edges) {
if (edge[3] === index) { if (edge[3] === index) {
node = edge[0]; node = edge[0];
index = edge[1]; index = edge[1];
position = getSocketPosition(node, index); position = getSocketPosition(node, index);
graph.removeEdge(edge); manager.removeEdge(edge);
break; break;
} }
} }
@ -204,7 +209,7 @@
position, position,
}; };
$possibleSockets = graph $possibleSockets = manager
.getPossibleSockets($activeSocket) .getPossibleSockets($activeSocket)
.map(([node, index]) => { .map(([node, index]) => {
return { return {
@ -315,7 +320,7 @@
// here we are handling dragging of nodes // here we are handling dragging of nodes
if ($activeNodeId !== -1 && mouseDownId !== -1) { if ($activeNodeId !== -1 && mouseDownId !== -1) {
const node = graph.getNode($activeNodeId); const node = manager.getNode($activeNodeId);
if (!node || event.buttons !== 1) return; if (!node || event.buttons !== 1) return;
node.tmp = node.tmp || {}; node.tmp = node.tmp || {};
@ -328,9 +333,9 @@
if (event.ctrlKey) { if (event.ctrlKey) {
const snapLevel = getSnapLevel(); const snapLevel = getSnapLevel();
if (settings.snapToGrid) { if (snapToGrid) {
newX = snapToGrid(newX, 5 / snapLevel); newX = snapPointToGrid(newX, 5 / snapLevel);
newY = snapToGrid(newY, 5 / snapLevel); newY = snapPointToGrid(newY, 5 / snapLevel);
} }
} }
@ -346,7 +351,7 @@
if ($selectedNodes?.size) { if ($selectedNodes?.size) {
for (const nodeId of $selectedNodes) { for (const nodeId of $selectedNodes) {
const n = graph.getNode(nodeId); const n = manager.getNode(nodeId);
if (!n?.tmp) continue; if (!n?.tmp) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX; n.tmp.x = (n?.tmp?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY; n.tmp.y = (n?.tmp?.downY || 0) - vecY;
@ -441,10 +446,10 @@
$activeNodeId = clickedNodeId; $activeNodeId = clickedNodeId;
// select the node // select the node
} else if (event.shiftKey) { } else if (event.shiftKey) {
const activeNode = graph.getNode($activeNodeId); const activeNode = manager.getNode($activeNodeId);
const newNode = graph.getNode(clickedNodeId); const newNode = manager.getNode(clickedNodeId);
if (activeNode && newNode) { if (activeNode && newNode) {
const edge = graph.getNodesBetween(activeNode, newNode); const edge = manager.getNodesBetween(activeNode, newNode);
if (edge) { if (edge) {
const selected = new Set(edge.map((n) => n.id)); const selected = new Set(edge.map((n) => n.id));
selected.add(clickedNodeId); selected.add(clickedNodeId);
@ -459,14 +464,14 @@
} else if (event.ctrlKey) { } else if (event.ctrlKey) {
boxSelection = true; boxSelection = true;
} }
const node = graph.getNode($activeNodeId); const node = manager.getNode($activeNodeId);
if (!node) return; if (!node) return;
node.tmp = node.tmp || {}; node.tmp = node.tmp || {};
node.tmp.downX = node.position[0]; node.tmp.downX = node.position[0];
node.tmp.downY = node.position[1]; node.tmp.downY = node.position[1];
if ($selectedNodes) { if ($selectedNodes) {
for (const nodeId of $selectedNodes) { for (const nodeId of $selectedNodes) {
const n = graph.getNode(nodeId); const n = manager.getNode(nodeId);
if (!n) continue; if (!n) continue;
n.tmp = n.tmp || {}; n.tmp = n.tmp || {};
n.tmp.downX = n.position[0]; n.tmp.downX = n.position[0];
@ -478,10 +483,10 @@
function copyNodes() { function copyNodes() {
if ($activeNodeId === -1 && !$selectedNodes?.size) return; if ($activeNodeId === -1 && !$selectedNodes?.size) return;
let _nodes = [$activeNodeId, ...($selectedNodes?.values() || [])] let _nodes = [$activeNodeId, ...($selectedNodes?.values() || [])]
.map((id) => graph.getNode(id)) .map((id) => manager.getNode(id))
.filter(Boolean) as Node[]; .filter(Boolean) as Node[];
const _edges = graph.getEdgesBetweenNodes(_nodes); const _edges = manager.getEdgesBetweenNodes(_nodes);
_nodes = _nodes.map((_node) => { _nodes = _nodes.map((_node) => {
const node = globalThis.structuredClone({ const node = globalThis.structuredClone({
@ -512,7 +517,7 @@
}) })
.filter(Boolean) as Node[]; .filter(Boolean) as Node[];
const newNodes = graph.createGraph(_nodes, clipboard.edges); const newNodes = manager.createGraph(_nodes, clipboard.edges);
$selectedNodes = new Set(newNodes.map((n) => n.id)); $selectedNodes = new Set(newNodes.map((n) => n.id));
} }
@ -522,15 +527,23 @@
key: "l", key: "l",
description: "Select linked nodes", description: "Select linked nodes",
callback: () => { callback: () => {
const activeNode = graph.getNode($activeNodeId); const activeNode = manager.getNode($activeNodeId);
if (activeNode) { if (activeNode) {
const nodes = graph.getLinkedNodes(activeNode); const nodes = manager.getLinkedNodes(activeNode);
$selectedNodes = new Set(nodes.map((n) => n.id)); $selectedNodes = new Set(nodes.map((n) => n.id));
} }
console.log(activeNode); console.log(activeNode);
}, },
}); });
keymap.addShortcut({
key: "?",
description: "Toggle Help",
callback: () => {
showHelp = !showHelp;
},
});
keymap.addShortcut({ keymap.addShortcut({
key: "c", key: "c",
ctrl: true, ctrl: true,
@ -612,7 +625,7 @@
description: "Undo", description: "Undo",
callback: () => { callback: () => {
if (!isBodyFocused()) return; if (!isBodyFocused()) return;
graph.undo(); manager.undo();
for (const node of $nodes.values()) { for (const node of $nodes.values()) {
updateNodePosition(node); updateNodePosition(node);
} }
@ -625,7 +638,7 @@
description: "Redo", description: "Redo",
callback: () => { callback: () => {
if (!isBodyFocused()) return; if (!isBodyFocused()) return;
graph.redo(); manager.redo();
for (const node of $nodes.values()) { for (const node of $nodes.values()) {
updateNodePosition(node); updateNodePosition(node);
} }
@ -637,30 +650,30 @@
description: "Delete selected nodes", description: "Delete selected nodes",
callback: (event) => { callback: (event) => {
if (!isBodyFocused()) return; if (!isBodyFocused()) return;
graph.startUndoGroup(); manager.startUndoGroup();
if ($activeNodeId !== -1) { if ($activeNodeId !== -1) {
const node = graph.getNode($activeNodeId); const node = manager.getNode($activeNodeId);
if (node) { if (node) {
graph.removeNode(node, { restoreEdges: event.ctrlKey }); manager.removeNode(node, { restoreEdges: event.ctrlKey });
$activeNodeId = -1; $activeNodeId = -1;
} }
} }
if ($selectedNodes) { if ($selectedNodes) {
for (const nodeId of $selectedNodes) { for (const nodeId of $selectedNodes) {
const node = graph.getNode(nodeId); const node = manager.getNode(nodeId);
if (node) { if (node) {
graph.removeNode(node, { restoreEdges: event.ctrlKey }); manager.removeNode(node, { restoreEdges: event.ctrlKey });
} }
} }
$selectedNodes.clear(); $selectedNodes.clear();
$selectedNodes = $selectedNodes; $selectedNodes = $selectedNodes;
} }
graph.saveUndoGroup(); manager.saveUndoGroup();
}, },
}); });
function handleMouseUp(event: MouseEvent) { function handleMouseUp(event: MouseEvent) {
const activeNode = graph.getNode($activeNodeId); const activeNode = manager.getNode($activeNodeId);
const clickedNodeId = getNodeIdFromEvent(event); const clickedNodeId = getNodeIdFromEvent(event);
@ -677,13 +690,13 @@
if (activeNode?.tmp?.isMoving) { if (activeNode?.tmp?.isMoving) {
activeNode.tmp = activeNode.tmp || {}; activeNode.tmp = activeNode.tmp || {};
activeNode.tmp.isMoving = false; activeNode.tmp.isMoving = false;
if (settings.snapToGrid) { if (snapToGrid) {
const snapLevel = getSnapLevel(); const snapLevel = getSnapLevel();
activeNode.position[0] = snapToGrid( activeNode.position[0] = snapPointToGrid(
activeNode?.tmp?.x ?? activeNode.position[0], activeNode?.tmp?.x ?? activeNode.position[0],
5 / snapLevel, 5 / snapLevel,
); );
activeNode.position[1] = snapToGrid( activeNode.position[1] = snapPointToGrid(
activeNode?.tmp?.y ?? activeNode.position[1], activeNode?.tmp?.y ?? activeNode.position[1],
5 / snapLevel, 5 / snapLevel,
); );
@ -692,7 +705,9 @@
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1]; activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
} }
const nodes = [ const nodes = [
...[...($selectedNodes?.values() || [])].map((id) => graph.getNode(id)), ...[...($selectedNodes?.values() || [])].map((id) =>
manager.getNode(id),
),
] as NodeType[]; ] as NodeType[];
const vec = [ const vec = [
@ -728,13 +743,13 @@
$edges = $edges; $edges = $edges;
}); });
graph.save(); manager.save();
} else if ($hoveredSocket && $activeSocket) { } else if ($hoveredSocket && $activeSocket) {
if ( if (
typeof $hoveredSocket.index === "number" && typeof $hoveredSocket.index === "number" &&
typeof $activeSocket.index === "string" typeof $activeSocket.index === "string"
) { ) {
graph.createEdge( manager.createEdge(
$hoveredSocket.node, $hoveredSocket.node,
$hoveredSocket.index || 0, $hoveredSocket.index || 0,
$activeSocket.node, $activeSocket.node,
@ -744,14 +759,14 @@
typeof $activeSocket.index == "number" && typeof $activeSocket.index == "number" &&
typeof $hoveredSocket.index === "string" typeof $hoveredSocket.index === "string"
) { ) {
graph.createEdge( manager.createEdge(
$activeSocket.node, $activeSocket.node,
$activeSocket.index || 0, $activeSocket.index || 0,
$hoveredSocket.node, $hoveredSocket.node,
$hoveredSocket.index, $hoveredSocket.index,
); );
} }
graph.save(); manager.save();
} }
// check if camera moved // check if camera moved
@ -808,8 +823,8 @@
} }
const pos = projectScreenToWorld(mx, my); const pos = projectScreenToWorld(mx, my);
graph.registry.load([nodeId]).then(() => { manager.registry.load([nodeId]).then(() => {
graph.createNode({ manager.createNode({
type: nodeId, type: nodeId,
props, props,
position: pos, position: pos,
@ -819,7 +834,7 @@
const files = event.dataTransfer.files; const files = event.dataTransfer.files;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
const buffer = e.target?.result; const buffer = e.target?.result as Buffer;
if (buffer) { if (buffer) {
const wrapper = createWasmWrapper(buffer); const wrapper = createWasmWrapper(buffer);
const definition = wrapper.get_definition(); const definition = wrapper.get_definition();
@ -885,10 +900,13 @@
/> />
<label for="drop-zone" /> <label for="drop-zone" />
{#if showHelp}
<HelpView registry={manager.registry} />
{/if}
<Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}> <Canvas shadows={false} renderMode="on-demand" colorManagementEnabled={false}>
<Camera bind:camera position={cameraPosition} /> <Camera bind:camera position={cameraPosition} />
{#if settings?.showGrid !== false} {#if showGrid !== false}
<Background {cameraPosition} {maxZoom} {minZoom} {width} {height} /> <Background {cameraPosition} {maxZoom} {minZoom} {width} {height} />
{/if} {/if}
@ -906,7 +924,7 @@
{#if $status === "idle"} {#if $status === "idle"}
{#if addMenuPosition} {#if addMenuPosition}
<AddMenu bind:position={addMenuPosition} {graph} /> <AddMenu bind:position={addMenuPosition} graph={manager} />
{/if} {/if}
{#if $activeSocket} {#if $activeSocket}

View File

@ -26,6 +26,7 @@
export let showGrid = false; export let showGrid = false;
export let snapToGrid = false; export let snapToGrid = false;
export let showHelp = false;
export let settingTypes = {}; export let settingTypes = {};
@ -55,4 +56,4 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
</script> </script>
<GraphEl graph={manager} settings={{ showGrid, snapToGrid }} /> <GraphEl {manager} bind:showGrid bind:snapToGrid bind:showHelp />

View File

@ -47,6 +47,7 @@
class:selected={isSelected} class:selected={isSelected}
class:out-of-view={!inView} class:out-of-view={!inView}
data-node-id={node.id} data-node-id={node.id}
data-node-type={node.type}
bind:this={ref} bind:this={ref}
> >
<NodeHeader {node} /> <NodeHeader {node} />

View File

@ -51,7 +51,7 @@
}); });
</script> </script>
<div class="wrapper" data-node-id={node.id}> <div class="wrapper" data-node-id={node.id} data-node-type={node.type}>
<div class="content"> <div class="content">
{node.type.split("/").pop()} {node.type.split("/").pop()}
</div> </div>

View File

@ -73,6 +73,8 @@
<div <div
class="wrapper" class="wrapper"
data-node-type={node.type}
data-node-input={id}
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)} class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
> >
{#key id && graphId} {#key id && graphId}
@ -87,12 +89,14 @@
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true} {#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
<div <div
data-node-socket
class="large target" class="large target"
on:mousedown={handleMouseDown} on:mousedown={handleMouseDown}
role="button" role="button"
tabindex="0" tabindex="0"
/> />
<div <div
data-node-socket
class="small target" class="small target"
on:mousedown={handleMouseDown} on:mousedown={handleMouseDown}
role="button" role="button"
@ -187,6 +191,8 @@
.content.disabled { .content.disabled {
opacity: 0.2; opacity: 0.2;
}
.content.disabled > * {
pointer-events: none; pointer-events: none;
} }

View File

@ -10,10 +10,12 @@ export class RemoteNodeRegistry implements NodeRegistry {
status: "loading" | "ready" | "error" = "loading"; status: "loading" | "ready" | "error" = "loading";
private nodes: Map<string, NodeDefinition> = new Map(); private nodes: Map<string, NodeDefinition> = new Map();
fetch: typeof fetch = globalThis.fetch.bind(globalThis);
constructor(private url: string) { } constructor(private url: string) { }
async fetchUsers() { async fetchUsers() {
const response = await fetch(`${this.url}/nodes/users.json`); const response = await this.fetch(`${this.url}/nodes/users.json`);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to load users`); throw new Error(`Failed to load users`);
} }
@ -21,7 +23,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
} }
async fetchUser(userId: `${string}`) { async fetchUser(userId: `${string}`) {
const response = await fetch(`${this.url}/nodes/${userId}.json`); const response = await this.fetch(`${this.url}/nodes/${userId}.json`);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to load user ${userId}`); throw new Error(`Failed to load user ${userId}`);
} }
@ -29,23 +31,15 @@ export class RemoteNodeRegistry implements NodeRegistry {
} }
async fetchCollection(userCollectionId: `${string}/${string}`) { async fetchCollection(userCollectionId: `${string}/${string}`) {
const response = await fetch(`${this.url}/nodes/${userCollectionId}.json`); const response = await this.fetch(`${this.url}/nodes/${userCollectionId}.json`);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to load collection ${userCollectionId}`); throw new Error(`Failed to load collection ${userCollectionId}`);
} }
return response.json(); return response.json();
} }
async fetchNode(nodeId: `${string}/${string}/${string}`) {
const response = await fetch(`${this.url}/nodes/${nodeId}.wasm`);
if (!response.ok) {
throw new Error(`Failed to load node wasm ${nodeId}`);
}
return response.arrayBuffer();
}
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) { async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
const response = await fetch(`${this.url}/nodes/${nodeId}.json`); const response = await this.fetch(`${this.url}/nodes/${nodeId}.json`);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to load node definition ${nodeId}`); throw new Error(`Failed to load node definition ${nodeId}`);
} }
@ -58,18 +52,22 @@ export class RemoteNodeRegistry implements NodeRegistry {
const nodes = await Promise.all(nodeIds.map(async id => { const nodes = await Promise.all(nodeIds.map(async id => {
if (this.nodes.has(id)) { if (this.nodes.has(id)) {
return this.nodes.get(id); return this.nodes.get(id)!;
} }
const wasmResponse = await this.fetchNode(id); const response = await this.fetch(`${this.url}/nodes/${id}.wasm`);
if (!response.ok) {
throw new Error(`Failed to load node wasm ${id}`);
}
const wrapper = createWasmWrapper(wasmResponse); const wasmBuffer = await response.arrayBuffer();
const wrapper = createWasmWrapper(wasmBuffer);
const definition = wrapper.get_definition(); const definition = wrapper.get_definition();
return { return {
...definition, ...definition,
id,
execute: wrapper.execute execute: wrapper.execute
}; };
})); }));
@ -80,7 +78,10 @@ export class RemoteNodeRegistry implements NodeRegistry {
const duration = performance.now() - a; const duration = performance.now() - a;
log.log("loaded nodes in", duration, "ms"); log.group("loaded nodes in", duration, "ms");
log.info(nodeIds);
log.info(nodes);
log.groupEnd();
this.status = "ready"; this.status = "ready";
return nodes return nodes

View File

@ -0,0 +1,18 @@
import type { Graph, RuntimeExecutor } from "@nodes/types";
export class RemoteRuntimeExecutor implements RuntimeExecutor {
constructor(private url: string) { }
async execute(graph: Graph, settings: Record<string, any>): Promise<Int32Array> {
const res = await fetch(this.url, { method: "POST", body: JSON.stringify({ graph, settings }) });
if (!res.ok) {
throw new Error(`Failed to execute graph`);
}
return new Int32Array(await res.arrayBuffer());
}
}

View File

@ -47,12 +47,14 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { } constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { }
private 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));
const typeMap = new Map<string, NodeDefinition>(); const typeMap = new Map<string, NodeDefinition>();
for (const node of graph.nodes) { for (const node of graph.nodes) {
if (!typeMap.has(node.type)) { if (!typeMap.has(node.type)) {
@ -65,10 +67,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
return typeMap; return typeMap;
} }
private addMetaData(graph: Graph) { private async addMetaData(graph: Graph) {
// First, lets check if all nodes have a definition // First, lets check if all nodes have a definition
this.definitionMap = this.getNodeDefinitions(graph); this.definitionMap = await this.getNodeDefinitions(graph);
const outputNode = graph.nodes.find(node => node.type.endsWith("/output")); const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
if (!outputNode) { if (!outputNode) {
@ -124,7 +126,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
return [outputNode, nodes] as const; return [outputNode, nodes] as const;
} }
execute(graph: Graph, settings: Record<string, unknown>) { async execute(graph: Graph, settings: Record<string, unknown>) {
this.perf?.startRun(); this.perf?.startRun();
@ -133,7 +135,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
let a = performance.now(); let a = performance.now();
// Then we add some metadata to the graph // Then we add some metadata to the graph
const [outputNode, nodes] = this.addMetaData(graph); const [outputNode, nodes] = await this.addMetaData(graph);
let b = performance.now(); let b = performance.now();
this.perf?.addPoint("metadata", b - a); this.perf?.addPoint("metadata", b - a);

View File

@ -5,6 +5,7 @@ export const AppSettings = localStore("node-settings", {
showGrid: true, showGrid: true,
showNodeGrid: true, showNodeGrid: true,
snapToGrid: true, snapToGrid: true,
showHelp: false,
wireframe: false, wireframe: false,
showIndices: false, showIndices: false,
showVertices: false, showVertices: false,
@ -55,6 +56,11 @@ export const AppSettingTypes = {
type: "boolean", type: "boolean",
label: "Snap to Grid", label: "Snap to Grid",
value: true value: true
},
showHelp: {
type: "boolean",
label: "Show Help",
value: false
} }
}, },
debug: { debug: {

View File

@ -26,9 +26,11 @@
import NestedSettings from "$lib/settings/panels/NestedSettings.svelte"; import NestedSettings from "$lib/settings/panels/NestedSettings.svelte";
import { createPerformanceStore } from "$lib/performance"; import { createPerformanceStore } from "$lib/performance";
import { type PerformanceData } from "$lib/performance/store"; import { type PerformanceData } from "$lib/performance/store";
import { RemoteRuntimeExecutor } from "$lib/remote-runtime-executor";
const nodeRegistry = new RemoteNodeRegistry(""); const nodeRegistry = new RemoteNodeRegistry("");
const workerRuntime = new WorkerRuntimeExecutor(); const workerRuntime = new WorkerRuntimeExecutor();
// const remoteRuntime = new RemoteRuntimeExecutor("/runtime");
let performanceData: PerformanceData; let performanceData: PerformanceData;
let viewerPerformance = createPerformanceStore(); let viewerPerformance = createPerformanceStore();
@ -44,6 +46,8 @@
? JSON.parse(localStorage.getItem("graph")!) ? JSON.parse(localStorage.getItem("graph")!)
: templates.plant; : templates.plant;
console.log({ graph });
let manager: GraphManager; let manager: GraphManager;
let managerStatus: Writable<"loading" | "error" | "idle">; let managerStatus: Writable<"loading" | "error" | "idle">;
$: if (manager) { $: if (manager) {
@ -75,6 +79,7 @@
isWorking = true; isWorking = true;
try { try {
let a = performance.now(); let a = performance.now();
// res = await remoteRuntime.execute(_graph, _settings);
res = await workerRuntime.execute(_graph, _settings); res = await workerRuntime.execute(_graph, _settings);
let b = performance.now(); let b = performance.now();
let perfData = await workerRuntime.getPerformanceData(); let perfData = await workerRuntime.getPerformanceData();
@ -120,9 +125,9 @@
<Grid.Row> <Grid.Row>
<Grid.Cell> <Grid.Cell>
<Viewer <Viewer
centerCamera={$AppSettings.centerCamera}
result={res} result={res}
perf={viewerPerformance} perf={viewerPerformance}
centerCamera={$AppSettings.centerCamera}
/> />
</Grid.Cell> </Grid.Cell>
<Grid.Cell> <Grid.Cell>
@ -133,8 +138,9 @@
bind:manager bind:manager
bind:activeNode bind:activeNode
bind:keymap bind:keymap
showGrid={$AppSettings?.showNodeGrid} showGrid={$AppSettings.showNodeGrid}
snapToGrid={$AppSettings?.snapToGrid} snapToGrid={$AppSettings.snapToGrid}
bind:showHelp={$AppSettings.showHelp}
bind:settings={graphSettings} bind:settings={graphSettings}
bind:settingTypes={graphSettingTypes} bind:settingTypes={graphSettingTypes}
on:result={(ev) => handleResult(ev.detail, $graphSettings)} on:result={(ev) => handleResult(ev.detail, $graphSettings)}

View File

@ -0,0 +1,29 @@
import type { RequestHandler } from "./$types";
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
import { RemoteNodeRegistry } from "$lib/node-registry-client";
import { createPerformanceStore } from "$lib/performance";
const registry = new RemoteNodeRegistry("");
const runtime = new MemoryRuntimeExecutor(registry);
const performanceStore = createPerformanceStore();
runtime.perf = performanceStore;
export const POST: RequestHandler = async ({ request, fetch }) => {
const { graph, settings } = await request.json();
registry.fetch = fetch;
await registry.load(graph.nodes.map(node => node.type))
const res = await runtime.execute(graph, settings);
let headers: Record<string, string> = { "Content-Type": "application/octet-stream" };
if (runtime.perf) {
headers["performance"] = JSON.stringify(runtime.perf.get());
}
return new Response(res, { headers });
}

View File

@ -1,5 +1,5 @@
{ {
"id": "max/plantarium/branches", "id": "max/plantarium/branch",
"outputs": [ "outputs": [
"path" "path"
], ],

View File

@ -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: NodeId[]) => Promise<NodeDefinition[]>; load: (nodeIds: (NodeId | string)[]) => 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: NodeId) => 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
@ -33,7 +33,7 @@ export interface RuntimeExecutor {
* @param graph - The graph to execute * @param graph - The graph to execute
* @returns The result of the execution * @returns The result of the execution
*/ */
execute: (graph: Graph, settings: Record<string, unknown>) => Int32Array; execute: (graph: Graph, settings: Record<string, unknown>) => Promise<Int32Array>;
} }
export interface RuntimeCache { export interface RuntimeCache {