From cafe9bff84b405824017019f573ddc6dad832b0f Mon Sep 17 00:00:00 2001 From: Max Richter Date: Fri, 26 Apr 2024 15:30:52 +0200 Subject: [PATCH] feat: add help view --- .../lib/graph-interface/BoxSelection.svelte | 2 +- app/src/lib/graph-interface/HelpView.svelte | 83 ++++++++++++ .../background/Background.svelte | 6 +- .../lib/graph-interface/graph/Graph.svelte | 120 ++++++++++-------- .../lib/graph-interface/graph/Wrapper.svelte | 3 +- .../lib/graph-interface/node/NodeHTML.svelte | 1 + .../graph-interface/node/NodeHeader.svelte | 2 +- .../graph-interface/node/NodeParameter.svelte | 6 + app/src/lib/node-registry-client.ts | 35 ++--- app/src/lib/remote-runtime-executor.ts | 18 +++ app/src/lib/runtime-executor.ts | 12 +- app/src/lib/settings/app-settings.ts | 6 + app/src/routes/+page.svelte | 12 +- app/src/routes/runtime/+server.ts | 29 +++++ nodes/max/plantarium/branch/src/input.json | 2 +- packages/types/src/components.ts | 6 +- 16 files changed, 256 insertions(+), 87 deletions(-) create mode 100644 app/src/lib/graph-interface/HelpView.svelte create mode 100644 app/src/lib/remote-runtime-executor.ts create mode 100644 app/src/routes/runtime/+server.ts diff --git a/app/src/lib/graph-interface/BoxSelection.svelte b/app/src/lib/graph-interface/BoxSelection.svelte index bec5359..74790dd 100644 --- a/app/src/lib/graph-interface/BoxSelection.svelte +++ b/app/src/lib/graph-interface/BoxSelection.svelte @@ -24,7 +24,7 @@ .box-selection { width: 40px; height: 20px; - border: solid 0.2px var(--outline); + border: solid 2px var(--outline); border-style: dashed; border-radius: 2px; } diff --git a/app/src/lib/graph-interface/HelpView.svelte b/app/src/lib/graph-interface/HelpView.svelte new file mode 100644 index 0000000..e43cd92 --- /dev/null +++ b/app/src/lib/graph-interface/HelpView.svelte @@ -0,0 +1,83 @@ + + + + +
+ {#if node} + {#if input} + {input} + {:else} + {node.id} + {/if} + {/if} +
+ + diff --git a/app/src/lib/graph-interface/background/Background.svelte b/app/src/lib/graph-interface/background/Background.svelte index a60db13..89909fe 100644 --- a/app/src/lib/graph-interface/background/Background.svelte +++ b/app/src/lib/graph-interface/background/Background.svelte @@ -21,8 +21,6 @@ bw = width / cameraPosition[2]; bh = height / cameraPosition[2]; } - $: backgroundColor = $colors["layer-0"]; - $: lineColor = $colors["outline"]; diff --git a/app/src/lib/graph-interface/graph/Graph.svelte b/app/src/lib/graph-interface/graph/Graph.svelte index b089895..c6836e4 100644 --- a/app/src/lib/graph-interface/graph/Graph.svelte +++ b/app/src/lib/graph-interface/graph/Graph.svelte @@ -1,5 +1,9 @@ - + diff --git a/app/src/lib/graph-interface/node/NodeHTML.svelte b/app/src/lib/graph-interface/node/NodeHTML.svelte index fd6002e..61c30fc 100644 --- a/app/src/lib/graph-interface/node/NodeHTML.svelte +++ b/app/src/lib/graph-interface/node/NodeHTML.svelte @@ -47,6 +47,7 @@ class:selected={isSelected} class:out-of-view={!inView} data-node-id={node.id} + data-node-type={node.type} bind:this={ref} > diff --git a/app/src/lib/graph-interface/node/NodeHeader.svelte b/app/src/lib/graph-interface/node/NodeHeader.svelte index 182a8de..b61464c 100644 --- a/app/src/lib/graph-interface/node/NodeHeader.svelte +++ b/app/src/lib/graph-interface/node/NodeHeader.svelte @@ -51,7 +51,7 @@ }); -
+
{node.type.split("/").pop()}
diff --git a/app/src/lib/graph-interface/node/NodeParameter.svelte b/app/src/lib/graph-interface/node/NodeParameter.svelte index 180f98b..824acee 100644 --- a/app/src/lib/graph-interface/node/NodeParameter.svelte +++ b/app/src/lib/graph-interface/node/NodeParameter.svelte @@ -73,6 +73,8 @@
{#key id && graphId} @@ -87,12 +89,14 @@ {#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
* { pointer-events: none; } diff --git a/app/src/lib/node-registry-client.ts b/app/src/lib/node-registry-client.ts index 8fc35e0..4c6afda 100644 --- a/app/src/lib/node-registry-client.ts +++ b/app/src/lib/node-registry-client.ts @@ -10,10 +10,12 @@ export class RemoteNodeRegistry implements NodeRegistry { status: "loading" | "ready" | "error" = "loading"; private nodes: Map = new Map(); + fetch: typeof fetch = globalThis.fetch.bind(globalThis); + constructor(private url: string) { } async fetchUsers() { - const response = await fetch(`${this.url}/nodes/users.json`); + const response = await this.fetch(`${this.url}/nodes/users.json`); if (!response.ok) { throw new Error(`Failed to load users`); } @@ -21,7 +23,7 @@ export class RemoteNodeRegistry implements NodeRegistry { } 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) { throw new Error(`Failed to load user ${userId}`); } @@ -29,23 +31,15 @@ export class RemoteNodeRegistry implements NodeRegistry { } 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) { throw new Error(`Failed to load collection ${userCollectionId}`); } 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}`) { - const response = await fetch(`${this.url}/nodes/${nodeId}.json`); + const response = await this.fetch(`${this.url}/nodes/${nodeId}.json`); if (!response.ok) { 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 => { 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(); return { ...definition, - id, execute: wrapper.execute }; })); @@ -80,7 +78,10 @@ export class RemoteNodeRegistry implements NodeRegistry { 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"; return nodes diff --git a/app/src/lib/remote-runtime-executor.ts b/app/src/lib/remote-runtime-executor.ts new file mode 100644 index 0000000..9640730 --- /dev/null +++ b/app/src/lib/remote-runtime-executor.ts @@ -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): Promise { + + 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()); + + } +} diff --git a/app/src/lib/runtime-executor.ts b/app/src/lib/runtime-executor.ts index 7a33af7..6e740de 100644 --- a/app/src/lib/runtime-executor.ts +++ b/app/src/lib/runtime-executor.ts @@ -47,12 +47,14 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { } - private getNodeDefinitions(graph: Graph) { + private async getNodeDefinitions(graph: Graph) { if (this.registry.status !== "ready") { throw new Error("Node registry is not ready"); } + await this.registry.load(graph.nodes.map(node => node.type)); + const typeMap = new Map(); for (const node of graph.nodes) { if (!typeMap.has(node.type)) { @@ -65,10 +67,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { return typeMap; } - private addMetaData(graph: Graph) { + private async addMetaData(graph: Graph) { // 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")); if (!outputNode) { @@ -124,7 +126,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { return [outputNode, nodes] as const; } - execute(graph: Graph, settings: Record) { + async execute(graph: Graph, settings: Record) { this.perf?.startRun(); @@ -133,7 +135,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { let a = performance.now(); // 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(); this.perf?.addPoint("metadata", b - a); diff --git a/app/src/lib/settings/app-settings.ts b/app/src/lib/settings/app-settings.ts index c7c92dc..3d4439d 100644 --- a/app/src/lib/settings/app-settings.ts +++ b/app/src/lib/settings/app-settings.ts @@ -5,6 +5,7 @@ export const AppSettings = localStore("node-settings", { showGrid: true, showNodeGrid: true, snapToGrid: true, + showHelp: false, wireframe: false, showIndices: false, showVertices: false, @@ -55,6 +56,11 @@ export const AppSettingTypes = { type: "boolean", label: "Snap to Grid", value: true + }, + showHelp: { + type: "boolean", + label: "Show Help", + value: false } }, debug: { diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index e5dbe9b..f045a5f 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -26,9 +26,11 @@ import NestedSettings from "$lib/settings/panels/NestedSettings.svelte"; import { createPerformanceStore } from "$lib/performance"; import { type PerformanceData } from "$lib/performance/store"; + import { RemoteRuntimeExecutor } from "$lib/remote-runtime-executor"; const nodeRegistry = new RemoteNodeRegistry(""); const workerRuntime = new WorkerRuntimeExecutor(); + // const remoteRuntime = new RemoteRuntimeExecutor("/runtime"); let performanceData: PerformanceData; let viewerPerformance = createPerformanceStore(); @@ -44,6 +46,8 @@ ? JSON.parse(localStorage.getItem("graph")!) : templates.plant; + console.log({ graph }); + let manager: GraphManager; let managerStatus: Writable<"loading" | "error" | "idle">; $: if (manager) { @@ -75,6 +79,7 @@ isWorking = true; try { let a = performance.now(); + // res = await remoteRuntime.execute(_graph, _settings); res = await workerRuntime.execute(_graph, _settings); let b = performance.now(); let perfData = await workerRuntime.getPerformanceData(); @@ -120,9 +125,9 @@ @@ -133,8 +138,9 @@ bind:manager bind:activeNode bind:keymap - showGrid={$AppSettings?.showNodeGrid} - snapToGrid={$AppSettings?.snapToGrid} + showGrid={$AppSettings.showNodeGrid} + snapToGrid={$AppSettings.snapToGrid} + bind:showHelp={$AppSettings.showHelp} bind:settings={graphSettings} bind:settingTypes={graphSettingTypes} on:result={(ev) => handleResult(ev.detail, $graphSettings)} diff --git a/app/src/routes/runtime/+server.ts b/app/src/routes/runtime/+server.ts new file mode 100644 index 0000000..673f7c3 --- /dev/null +++ b/app/src/routes/runtime/+server.ts @@ -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 = { "Content-Type": "application/octet-stream" }; + if (runtime.perf) { + headers["performance"] = JSON.stringify(runtime.perf.get()); + } + + return new Response(res, { headers }); + +} diff --git a/nodes/max/plantarium/branch/src/input.json b/nodes/max/plantarium/branch/src/input.json index b42e9cf..c518e1b 100644 --- a/nodes/max/plantarium/branch/src/input.json +++ b/nodes/max/plantarium/branch/src/input.json @@ -1,5 +1,5 @@ { - "id": "max/plantarium/branches", + "id": "max/plantarium/branch", "outputs": [ "path" ], diff --git a/packages/types/src/components.ts b/packages/types/src/components.ts index df8c0d5..f941f18 100644 --- a/packages/types/src/components.ts +++ b/packages/types/src/components.ts @@ -13,13 +13,13 @@ export interface NodeRegistry { * @throws An error if the nodes could not be loaded * @remarks This method should be called before calling getNode or getAllNodes */ - load: (nodeIds: NodeId[]) => Promise; + load: (nodeIds: (NodeId | string)[]) => Promise; /** * Get a node by id * @param id - The id of the node to get * @returns The node with the given id, or undefined if no such node exists */ - getNode: (id: NodeId) => NodeDefinition | undefined; + getNode: (id: NodeId | string) => NodeDefinition | undefined; /** * Get all nodes * @returns An array of all nodes @@ -33,7 +33,7 @@ export interface RuntimeExecutor { * @param graph - The graph to execute * @returns The result of the execution */ - execute: (graph: Graph, settings: Record) => Int32Array; + execute: (graph: Graph, settings: Record) => Promise; } export interface RuntimeCache {