feat: did some stuff

This commit is contained in:
max_richter 2024-04-05 18:03:23 +02:00
parent 8035b26750
commit b3780fdf96
34 changed files with 355 additions and 54 deletions

24
Cargo.lock generated
View File

@ -1626,6 +1626,18 @@ dependencies = [
"cfb", "cfb",
] ]
[[package]]
name = "input-float"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"plantarium",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -2214,6 +2226,18 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "output"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"plantarium",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"

View File

@ -5,17 +5,19 @@
export let node: Node; export let node: Node;
export let input: NodeInput; export let input: NodeInput;
export let label: string; export let id: string;
export let label: string | undefined;
const graph = getGraphManager(); const graph = getGraphManager();
let value = node?.props?.[label] ?? input.value; let value = node?.props?.[id] ?? input.value;
$: if (node?.props?.[label] !== value) { $: if (node?.props?.[id] !== value) {
node.props = { ...node.props, [label]: value }; node.props = { ...node.props, [id]: value };
graph.save();
graph.execute(); graph.execute();
} }
</script> </script>
<label for="asd">{label}</label> <label for="asd">{label || id}</label>
<Input {input} bind:value /> <Input {input} bind:value />

View File

@ -5,6 +5,7 @@
import { possibleSocketIds } from "../graph/stores"; import { possibleSocketIds } from "../graph/stores";
import { getGraphManager } from "../graph/context"; import { getGraphManager } from "../graph/context";
import NodeInput from "./NodeInput.svelte"; import NodeInput from "./NodeInput.svelte";
import Node from "./Node.svelte";
export let node: Node; export let node: Node;
export let input: NodeInputType; export let input: NodeInputType;
@ -68,9 +69,11 @@
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)} class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
> >
{#key id && graphId} {#key id && graphId}
{#if node?.tmp?.type?.inputs?.[id]?.external !== true}
<div class="content" class:disabled={$inputSockets.has(socketId)}> <div class="content" class:disabled={$inputSockets.has(socketId)}>
<NodeInput {node} {input} label={input?.title || id} /> <NodeInput {node} {input} {id} label={input.title} />
</div> </div>
{/if}
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true} {#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
<div <div

View File

@ -145,7 +145,8 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
this.status.set("loading"); this.status.set("loading");
this.id.set(graph.id); this.id.set(graph.id);
this.nodeRegistry.load(); const nodeIds = Array.from(new Set([...graph.nodes.map(n => n.type)]));
await this.nodeRegistry.load(nodeIds);
for (const node of this.graph.nodes) { for (const node of this.graph.nodes) {
const nodeType = this.nodeRegistry.getNode(node.type); const nodeType = this.nodeRegistry.getNode(node.type);

View File

@ -33,7 +33,7 @@ export function grid(width: number, height: number) {
visible: false, visible: false,
}, },
position: [width * 30, (height - 1) * 40], position: [width * 30, (height - 1) * 40],
type: "output", type: "max/plantarium/output",
props: {}, props: {},
}); });

View File

@ -5,7 +5,7 @@ export function tree(depth: number): Graph {
const nodes: Node[] = [ const nodes: Node[] = [
{ {
id: 0, id: 0,
type: "output", type: "max/plantarium/output",
position: [0, 0] position: [0, 0]
}, },
{ {

View File

@ -1,6 +1,7 @@
import type { NodeRegistry, NodeType } from "@nodes/types"; import type { NodeRegistry, NodeType } from "@nodes/types";
import * as d from "plantarium-nodes-math"; import * as d from "plantarium-nodes-math";
import { createLogger } from "./helpers";
const nodeTypes: NodeType[] = [ const nodeTypes: NodeType[] = [
{ {
@ -9,7 +10,7 @@ const nodeTypes: NodeType[] = [
"value": { type: "float", value: 0.1, internal: true }, "value": { type: "float", value: 0.1, internal: true },
}, },
outputs: ["float"], outputs: ["float"],
execute: ({ value }) => { return [0, value] } execute: (value) => { return value; }
}, },
{ {
id: "max/plantarium/math", id: "max/plantarium/math",
@ -19,7 +20,7 @@ const nodeTypes: NodeType[] = [
"b": { type: "float" }, "b": { type: "float" },
}, },
outputs: ["float"], outputs: ["float"],
execute: ({ op_type, a, b }: { op_type: number, a: number, b: number }) => { execute: (op_type: number, a: number, b: number) => {
switch (op_type) { switch (op_type) {
case 0: return a + b; case 0: return a + b;
case 1: return a - b; case 1: return a - b;
@ -29,7 +30,7 @@ const nodeTypes: NodeType[] = [
} }
}, },
{ {
id: "output", id: "max/plantarium/output",
inputs: { inputs: {
"input": { type: "float" }, "input": { type: "float" },
}, },
@ -37,6 +38,8 @@ const nodeTypes: NodeType[] = [
} }
] ]
const log = createLogger("node-registry");
export class RemoteNodeRegistry implements NodeRegistry { export class RemoteNodeRegistry implements NodeRegistry {
private nodes: Map<string, NodeType> = new Map(); private nodes: Map<string, NodeType> = new Map();
@ -44,13 +47,39 @@ export class RemoteNodeRegistry implements NodeRegistry {
constructor(private url: string) { } constructor(private url: string) { }
async load(nodeIds: string[]) { async load(nodeIds: string[]) {
const a = performance.now();
for (const id of nodeIds) { for (const id of nodeIds) {
const response = await fetch(`${this.url}/nodes/${id}`); const nodeUrl = `${this.url}/n/${id}`;
const node = this.getNode(id); const response = await fetch(nodeUrl);
if (node) { const wasmResponse = await fetch(`${nodeUrl}/wasm`);
const wrapperReponse = await fetch(`${nodeUrl}/wrapper`);
if (!wrapperReponse.ok) {
throw new Error(`Failed to load node ${id}`);
}
let wrapperCode = await wrapperReponse.text();
wrapperCode = wrapperCode.replace("wasm = val;", `if(wasm) return;
wasm = val;`);
const wasmWrapper = await import(/*@vite-ignore*/`data:text/javascript;base64,${btoa(wrapperCode)}`);
const module = new WebAssembly.Module(await wasmResponse.arrayBuffer());
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wasmWrapper });
wasmWrapper.__wbg_set_wasm(instance.exports);
if (!response.ok) {
throw new Error(`Failed to load node ${id}`);
}
const node = await response.json();
node.execute = (...args) => {
console.log("Executing", id, args);
return wasmWrapper.execute(...args)
};
this.nodes.set(id, node); this.nodes.set(id, node);
} }
}
const duration = performance.now() - a;
log.log("loaded nodes in", duration, "ms");
} }
getNode(id: string) { getNode(id: string) {

View File

@ -23,7 +23,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// First, lets check if all nodes have a type // First, lets check if all nodes have a type
const typeMap = this.getNodeTypes(graph); const typeMap = this.getNodeTypes(graph);
const outputNode = graph.nodes.find(node => node.type === "output"); const outputNode = graph.nodes.find(node => node.type.endsWith("/output"));
if (!outputNode) { if (!outputNode) {
throw new Error("No output node found"); throw new Error("No output node found");
} }
@ -125,13 +125,13 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
} }
// execute the node and store the result // execute the node and store the result
results[node.id] = node.tmp.type.execute(inputs) as number;; results[node.id] = node.tmp.type.execute(...Object.values(inputs)) as number;
} }
} }
// return the result of the parent of the output node // return the result of the parent of the output node
const res = results[outputNode.tmp?.parents?.[0].id as number] as string const res = results[outputNode.id] as string
return res; return res;

View File

@ -3,12 +3,14 @@
import { GraphManager } from "$lib/graph-manager"; import { GraphManager } from "$lib/graph-manager";
import Graph from "$lib/components/graph/Graph.svelte"; import Graph from "$lib/components/graph/Graph.svelte";
import { MemoryRuntimeExecutor } from "$lib/runtime-executor"; import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
import { MemoryNodeRegistry } from "$lib/node-registry"; import { MemoryNodeRegistry, RemoteNodeRegistry } from "$lib/node-registry";
import { LinearSRGBColorSpace } from "three"; import { LinearSRGBColorSpace } from "three";
import Details from "$lib/components/Details.svelte"; import Details from "$lib/components/Details.svelte";
import { JsonView } from "@zerodevx/svelte-json-view"; import { JsonView } from "@zerodevx/svelte-json-view";
const nodeRegistry = new MemoryNodeRegistry(); const memNodeRegistry = new MemoryNodeRegistry();
const nodeRegistry = new RemoteNodeRegistry("http://localhost:5174");
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry); const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
const graphManager = new GraphManager(nodeRegistry, runtimeExecutor); const graphManager = new GraphManager(nodeRegistry, runtimeExecutor);

View File

@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log

View File

@ -0,0 +1,30 @@
[package]
name = "input-float"
version = "0.1.0"
authors = ["Max Richter <jim-x@web.de>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.84"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
plantarium = { version = "0.1.0", path = "../../../../packages/plantarium" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

View File

@ -0,0 +1,5 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features"
}
}

View File

@ -0,0 +1,29 @@
mod utils;
use plantarium::unwrap_float;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn get_outputs() -> Vec<String> {
vec!["float".to_string()]
}
#[wasm_bindgen]
pub fn get_id() -> String {
"float".to_string()
}
#[wasm_bindgen]
pub fn get_input_types() -> String {
utils::set_panic_hook();
r#"{
"value": { "type": "float", "value": 0.1, "internal": true }
}"#
.to_string()
}
#[wasm_bindgen]
pub fn execute(var_value: JsValue) -> f64 {
utils::set_panic_hook();
return unwrap_float(var_value);
}

View File

@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@ -0,0 +1,13 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn pass() {
assert_eq!(1 + 1, 2);
}

View File

@ -24,13 +24,28 @@ pub fn get_input_types() -> String {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn execute(var_op_type: u8, var_a: String, var_b: String) -> String { pub fn execute(var_op_type: u8, var_a: JsValue, var_b: JsValue) -> String {
utils::set_panic_hook(); utils::set_panic_hook();
let a: String;
let b: String;
if var_a.is_string() {
a = unwrap_string(var_a);
} else {
a = unwrap_float(var_a).to_string();
}
if var_b.is_string() {
b = unwrap_string(var_b);
} else {
b = unwrap_float(var_b).to_string();
}
// Interpolate strings into JSON format // Interpolate strings into JSON format
let json_string = format!( let json_string = format!(
r#"{{"parameter": "math", "op_type": {}, "a": {}, "b": {}}}"#, r#"{{"parameter": "math", "op_type": {}, "a": {}, "b": {}}}"#,
var_op_type, var_a, var_b var_op_type, a, b
); );
json_string json_string

View File

@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log

View File

@ -0,0 +1,30 @@
[package]
name = "output"
version = "0.1.0"
authors = ["Max Richter <jim-x@web.de>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.84"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
plantarium = { version = "0.1.0", path = "../../../../packages/plantarium" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

View File

@ -0,0 +1,5 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features"
}
}

View File

@ -0,0 +1,40 @@
mod utils;
use plantarium::unwrap_string;
use wasm_bindgen::prelude::*;
// lifted from the `console_log` example
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn get_outputs() -> Vec<String> {
vec![]
}
#[wasm_bindgen]
pub fn get_id() -> String {
"float".to_string()
}
#[wasm_bindgen]
pub fn get_input_types() -> String {
utils::set_panic_hook();
r#"{
"input": { "type": "float", "value": 0.0, "external": true }
}"#
.to_string()
}
#[wasm_bindgen]
pub fn execute(var_value: JsValue) -> String {
utils::set_panic_hook();
let str = unwrap_string(var_value);
log(&format!("str: {}", str));
return str;
}

View File

@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@ -0,0 +1,13 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")]
extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn pass() {
assert_eq!(1 + 1, 2);
}

View File

@ -17,5 +17,5 @@
{:else if input.type === "boolean"} {:else if input.type === "boolean"}
<Checkbox bind:value /> <Checkbox bind:value />
{:else if input.type === "select"} {:else if input.type === "select"}
<Select labels={input.labels} bind:value /> <Select bind:value labels={input.labels} />
{/if} {/if}

View File

@ -7,3 +7,13 @@
<input type="number" bind:value {min} {max} {step} /> <input type="number" bind:value {min} {max} {step} />
<style>
input {
background: var(--background-color-lighter);
color: var(--text-color);
font-family: var(--font-family);
padding: 0.8em 1em;
border-radius: 5px;
border: none;
}
</style>

View File

@ -1,16 +1,25 @@
export async function getNodeWrapper(id: `${string}/${string}/${string}`) { export async function getNodeWrapper(id: `${string}/${string}/${string}`) {
let wrapperCode = await (await fetch(`/${id}/wrapper`)).text(); const wrapperReponse = await fetch(`/n/${id}/wrapper`);
if (!wrapperReponse.ok) {
throw new Error(`Failed to load node ${id}`);
}
let wrapperCode = await wrapperReponse.text();
wrapperCode = wrapperCode.replace("wasm = val;", `if(wasm) return; wrapperCode = wrapperCode.replace("wasm = val;", `if(wasm) return;
wasm = val;`); wasm = val;`);
const wasmWrapper = await import(/*vite-ignore*/`data:text/javascript;base64,${btoa(wrapperCode)}`); const wasmWrapper = await import(/*@vite-ignore*/`data:text/javascript;base64,${btoa(wrapperCode)}`);
return wasmWrapper; return wasmWrapper;
} }
export async function getNodeWasm(id: `${string}/${string}/${string}`): Promise<WebAssembly.Instance> { export async function getNodeWasm(id: `${string}/${string}/${string}`): Promise<WebAssembly.Instance> {
const wasmResponse = await fetch(`/${id}/wasm`); const wasmResponse = await fetch(`/n/${id}/wasm`);
if (!wasmResponse.ok) {
throw new Error(`Failed to load node ${id}`);
}
const wasmWrapper = await getNodeWrapper(id); const wasmWrapper = await getNodeWrapper(id);

View File

@ -1,19 +0,0 @@
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async function GET({ fetch, params }) {
const wasmResponse = await fetch(`/${params.user}/${params.collection}/${params.node}/wasm`);
let wrapperCode = await (await fetch(`/${params.user}/${params.collection}/${params.node}/wrapper`)).text();
wrapperCode = wrapperCode.replace("wasm = val;", `if(wasm) return;
wasm = val;`);
const wasmWrapper = await import(`data:text/javascript;base64,${btoa(wrapperCode)}`);
const module = new WebAssembly.Module(await wasmResponse.arrayBuffer());
const instance = new WebAssembly.Instance(module, { ["./index_bg.js"]: wasmWrapper });
wasmWrapper.__wbg_set_wasm(instance.exports);
const id = wasmWrapper.get_id();
const outputs = wasmWrapper.get_outputs();
const inputTypes = JSON.parse(wasmWrapper.get_input_types());
return json({ id, outputs, inputs: inputTypes, });
}

View File

@ -0,0 +1,17 @@
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
import { getNode } from "$lib/registry";
export const GET: RequestHandler = async function GET({ fetch, params }) {
globalThis.fetch = fetch;
const nodeId = `${params.user}/${params.collection}/${params.node}` as const;
try {
const node = await getNode(nodeId);
return json(node);
} catch (err) {
console.log(err)
return new Response("Not found", { status: 404 });
}
}

View File

@ -6,6 +6,12 @@ export const GET: RequestHandler = async function GET({ fetch, params }) {
const filePath = path.resolve(`../../nodes/${params.user}/${params.collection}/${params.node}/pkg/index_bg.wasm`); const filePath = path.resolve(`../../nodes/${params.user}/${params.collection}/${params.node}/pkg/index_bg.wasm`);
try {
await fs.access(filePath);
} catch (e) {
return new Response("Not found", { status: 404 });
}
const file = await fs.readFile(filePath); const file = await fs.readFile(filePath);
const bytes = new Uint8Array(file); const bytes = new Uint8Array(file);

View File

@ -6,6 +6,13 @@ export const GET: RequestHandler = async function GET({ params }) {
const filePath = path.resolve(`../../nodes/${params.user}/${params.collection}/${params.node}/pkg/index_bg.js`); const filePath = path.resolve(`../../nodes/${params.user}/${params.collection}/${params.node}/pkg/index_bg.js`);
try {
await fs.access(filePath);
} catch (e) {
console.log("Not Found", filePath);
return new Response("Not found", { status: 404 });
}
const file = await fs.readFile(filePath); const file = await fs.readFile(filePath);
return new Response(file, { status: 200, headers: { "Content-Type": "text/javascript" } }); return new Response(file, { status: 200, headers: { "Content-Type": "text/javascript" } });

View File

@ -21,9 +21,6 @@ pub fn unwrap_string(val: JsValue) -> String {
return val.as_string().unwrap(); return val.as_string().unwrap();
} }
pub fn evaluate_parameter(val: JsValue) -> String { pub fn evaluate_parameter(_val: String) -> i32 {
if val.is_undefined() || val.is_null() { return 2;
panic!("Value is undefined");
}
return val.as_string().unwrap();
} }

View File

@ -34,7 +34,7 @@ export type NodeType = {
meta?: { meta?: {
title?: string; title?: string;
}, },
execute?: (inputs: Record<string, string | number | boolean>) => unknown; execute?: (...args: (string | number | boolean)[]) => unknown;
} }
export type Socket = { export type Socket = {

View File

@ -26,6 +26,7 @@ type NodeInputSelect = {
type DefaultOptions = { type DefaultOptions = {
internal?: boolean; internal?: boolean;
external?: boolean;
title?: string; title?: string;
} }