feat: node store interface
This commit is contained in:
@@ -1,120 +1,101 @@
|
||||
import type { NodeRegistry, NodeType } from "@nodes/types";
|
||||
import { createWasmWrapper } from "@nodes/utils";
|
||||
import { createWasmWrapper } from "@nodes/utils"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
import { createLogger } from "./helpers";
|
||||
export async function getWasm(id: `${string}/${string}/${string}`) {
|
||||
const filePath = path.resolve(`../nodes/${id}/pkg/index_bg.wasm`);
|
||||
|
||||
const nodeTypes: NodeType[] = [
|
||||
{
|
||||
id: "max/plantarium/float",
|
||||
inputs: {
|
||||
"value": { type: "float", value: 0.1, internal: true },
|
||||
},
|
||||
outputs: ["float"],
|
||||
execute: (value) => { return value; }
|
||||
},
|
||||
{
|
||||
id: "max/plantarium/math",
|
||||
inputs: {
|
||||
"op_type": { label: "type", type: "select", options: ["add", "subtract", "multiply", "divide"], value: 0 },
|
||||
"a": { type: "float" },
|
||||
"b": { type: "float" },
|
||||
},
|
||||
outputs: ["float"],
|
||||
execute: ([op_type, a, b]: number[]) => {
|
||||
switch (op_type) {
|
||||
case 0: return a + b;
|
||||
case 1: return a - b;
|
||||
case 2: return a * b;
|
||||
case 3: return a / b;
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
|
||||
const file = await fs.readFile(filePath);
|
||||
|
||||
const bytes = new Uint8Array(file);
|
||||
|
||||
return bytes;
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
|
||||
export async function getNode(id: `${string}/${string}/${string}`) {
|
||||
|
||||
const wrapper = await getNodeWasm(id);
|
||||
|
||||
const definition = wrapper?.get_definition?.();
|
||||
|
||||
if (!definition) return null;
|
||||
|
||||
const { inputs, outputs } = definition;
|
||||
try {
|
||||
return { id, inputs, outputs }
|
||||
} catch (e) {
|
||||
console.log("Failed to parse input types for node", { id });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function getCollectionNodes(userId: `${string}/${string}`) {
|
||||
const nodes = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
||||
return nodes
|
||||
.filter(n => n !== "pkg" && n !== ".template")
|
||||
.map(n => {
|
||||
return {
|
||||
id: `${userId}/${n}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "max/plantarium/output",
|
||||
inputs: {
|
||||
"input": { type: "float" },
|
||||
},
|
||||
outputs: [],
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const log = createLogger("node-registry");
|
||||
export class RemoteNodeRegistry implements NodeRegistry {
|
||||
|
||||
|
||||
status: "loading" | "ready" | "error" = "loading";
|
||||
private nodes: Map<string, NodeType> = new Map();
|
||||
|
||||
constructor(private url: string) { }
|
||||
|
||||
private async loadNode(id: string) {
|
||||
const nodeUrl = `${this.url}/n/${id}`;
|
||||
const [response, wasmResponse] = await Promise.all([fetch(nodeUrl), fetch(`${nodeUrl}/wasm`)]);
|
||||
if (!wasmResponse.ok || !response.ok) {
|
||||
this.status = "error";
|
||||
throw new Error(`Failed to load node ${id}`);
|
||||
}
|
||||
|
||||
// Setup Wasm wrapper
|
||||
const wrapper = createWasmWrapper();
|
||||
const module = new WebAssembly.Module(await wasmResponse.arrayBuffer());
|
||||
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wrapper });
|
||||
wrapper.setInstance(instance);
|
||||
|
||||
const node = await response.json();
|
||||
node.execute = wrapper.execute;
|
||||
return node;
|
||||
}
|
||||
|
||||
async load(nodeIds: string[]) {
|
||||
const a = performance.now();
|
||||
|
||||
nodeIds.push("max/plantarium/random");
|
||||
nodeIds.push("max/plantarium/float");
|
||||
nodeIds.push("max/plantarium/triangle");
|
||||
nodeIds.push("max/plantarium/vec3");
|
||||
nodeIds.push("max/plantarium/output");
|
||||
nodeIds.push("max/plantarium/array");
|
||||
nodeIds.push("max/plantarium/sum");
|
||||
nodeIds.push("max/plantarium/stem");
|
||||
nodeIds.push("max/plantarium/box");
|
||||
nodeIds.push("max/plantarium/math");
|
||||
|
||||
const nodes = await Promise.all(nodeIds.map(id => this.loadNode(id)));
|
||||
|
||||
for (const node of nodes) {
|
||||
this.nodes.set(node.id, node);
|
||||
}
|
||||
|
||||
const duration = performance.now() - a;
|
||||
|
||||
log.log("loaded nodes in", duration, "ms");
|
||||
this.status = "ready";
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
getAllNodes() {
|
||||
return [...this.nodes.values()];
|
||||
export async function getCollection(userId: `${string}/${string}`) {
|
||||
const nodes = await getCollectionNodes(userId);
|
||||
return {
|
||||
id: userId,
|
||||
nodes,
|
||||
}
|
||||
}
|
||||
|
||||
export class MemoryNodeRegistry implements NodeRegistry {
|
||||
export async function getUserCollections(userId: string) {
|
||||
const collections = await fs.readdir(path.resolve(`../nodes/${userId}`));
|
||||
return Promise.all(collections.map(async n => {
|
||||
const nodes = await getCollectionNodes(`${userId}/${n}`);
|
||||
return {
|
||||
id: `${userId}/${n}`,
|
||||
nodes,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
status: "loading" | "ready" | "error" = "ready";
|
||||
|
||||
async load(nodeIds: string[]) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
getNode(id: string) {
|
||||
return nodeTypes.find((nodeType) => nodeType.id === id);
|
||||
}
|
||||
getAllNodes() {
|
||||
return [...nodeTypes];
|
||||
export async function getUser(userId: string) {
|
||||
const collections = await getUserCollections(userId);
|
||||
return {
|
||||
id: userId,
|
||||
collections
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUsers() {
|
||||
const nodes = await fs.readdir(path.resolve("../nodes"));
|
||||
const users = await Promise.all(nodes.map(async n => {
|
||||
const collections = await getUserCollections(n);
|
||||
return {
|
||||
id: n,
|
||||
collections
|
||||
}
|
||||
}))
|
||||
return users;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user