23 Commits

Author SHA1 Message Date
8e5412c25c Merge remote-tracking branch 'origin/main' into feat/remove-wasm-bindgen 2026-01-19 16:26:06 +01:00
450262b4ae fix(app): remove unused func
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m7s
2026-01-19 14:24:47 +01:00
11de746c01 feat(app): allow disabling of runtime/registry caches
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m58s
2026-01-19 14:22:14 +01:00
83cb2bd950 feat: move analytics script to env
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m2s
2026-01-19 14:04:00 +01:00
Niklas Koll
e84c715f4c chore: Add flake and direnv stuff 2026-01-19 12:51:33 +01:00
Niklas Koll
f5cea555cd chore: Add flake and direnv stuff
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m7s
2026-01-19 12:50:12 +01:00
Max Richter
ecbcc814ed chore: remove store code 2026-01-19 01:29:28 +01:00
Max Richter
be97387252 feat: trying to remove wasm-bindgen 2026-01-19 01:29:12 +01:00
Max Richter
987ece2a4b fix: update performance bars to work with tailwind
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m15s
2026-01-18 18:55:18 +01:00
Max Richter
8d2e3f006b fix: make graph source work 2026-01-18 18:54:53 +01:00
Max Richter
80d3e117b4 feat: update sidebar to svelte-5
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m1s
2026-01-18 18:39:02 +01:00
Max Richter
8a540522dd chore: replace unocss with tailwind
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m4s
2026-01-18 17:11:47 +01:00
Max Richter
a11214072f chore: some updates
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m6s
2026-01-18 16:27:42 +01:00
d068828b68 refactor: rename state.svelte.ts to graph-state.svelte.ts
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m59s
2025-12-09 20:00:52 +01:00
3565a18364 feat: cache everything in node store not only wasm 2025-12-05 14:19:29 +01:00
73be4fdd73 feat: better handle node position updates 2025-12-05 14:19:11 +01:00
702c3ee6cf feat: better handle camera positioning 2025-12-05 14:18:56 +01:00
98672eb702 fix: error that changes in active node panel did not get saved 2025-12-05 12:28:30 +01:00
3eafdc50b1 feat: keep benchmark result if panel is hidden 2025-12-05 11:49:10 +01:00
Max Richter
548e445eb7 fix: correctly show hide geometries in geometrypool
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m4s
2025-12-03 22:59:06 +01:00
db77a4fd94 Merge pull request 'refactor: split ui/runtime/serialized node types' (#10) from refactor/split-node-runtime-types into main
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 2m3s
Reviewed-on: #10
2025-12-03 19:19:17 +01:00
Max Richter
7ae1fae3b9 refactor: split ui/runtime/serialized node types
Closes #6
2025-12-03 19:18:56 +01:00
1126cf8f9f feat: dont use custom edge geometry
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m55s
2025-12-03 10:33:24 +01:00
205 changed files with 5380 additions and 7674 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ node_modules/
# Added by cargo
/target
.direnv/

367
Cargo.lock generated
View File

@@ -1,176 +1,80 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "autocfg"
version = "1.2.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "box"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "branch"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "float"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "glam"
version = "0.27.0"
version = "0.30.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9"
checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9"
[[package]]
name = "gravity"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros",
"nodarium_utils",
"noise",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "instance"
version = "0.1.0"
dependencies = [
"glam",
"nodarium_macros",
"nodarium_utils",
]
[[package]]
name = "itoa"
version = "1.0.11"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "math"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "max-plantarium-triangle"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "max-plantarium-vec3"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "nodarium_instance"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "nodarium_macros"
@@ -180,7 +84,7 @@ dependencies = [
"quote",
"serde",
"serde_json",
"syn 1.0.109",
"syn",
]
[[package]]
@@ -195,29 +99,19 @@ dependencies = [
name = "nodarium_utils"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"noise",
"noise 0.9.0",
"serde",
"serde_json",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "nodes-noise"
name = "noise"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros",
"nodarium_utils",
"noise",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
"noise 0.9.0",
]
[[package]]
@@ -233,48 +127,35 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "output"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde_json",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "proc-macro2"
version = "1.0.81"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [
"proc-macro2",
]
@@ -307,103 +188,76 @@ dependencies = [
name = "random"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "rotate"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.198"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.116"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"ryu",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "stem"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"nodarium_macros",
"nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "syn"
version = "1.0.109"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
@@ -411,119 +265,30 @@ dependencies = [
]
[[package]]
name = "syn"
version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
name = "triangle"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
"nodarium_macros",
"nodarium_utils",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
name = "vec3"
version = "0.1.0"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
"nodarium_macros",
"nodarium_utils",
"serde",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
name = "zmij"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.60",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"

1
app/.env Normal file
View File

@@ -0,0 +1 @@
PUBLIC_ANALYTIC_SCRIPT=""

View File

@@ -13,34 +13,35 @@
"@nodarium/registry": "link:../packages/registry",
"@nodarium/ui": "link:../packages/ui",
"@nodarium/utils": "link:../packages/utils",
"@sveltejs/kit": "^2.49.0",
"@threlte/core": "8.3.0",
"@threlte/extras": "9.7.0",
"@types/three": "^0.181.0",
"@unocss/reset": "^66.5.9",
"@sveltejs/kit": "^2.50.0",
"@tailwindcss/vite": "^4.1.18",
"@threlte/core": "8.3.1",
"@threlte/extras": "9.7.1",
"comlink": "^4.4.2",
"file-saver": "^2.0.5",
"idb": "^8.0.3",
"jsondiffpatch": "^0.7.3",
"three": "^0.181.2"
"tailwindcss": "^4.1.18",
"three": "^0.182.0",
"wabt": "^1.0.39"
},
"devDependencies": {
"@iconify-json/tabler": "^1.2.23",
"@iconify-json/tabler": "^1.2.26",
"@iconify/tailwind4": "^1.2.1",
"@nodarium/types": "link:../packages/types",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tsconfig/svelte": "^5.0.6",
"@types/file-saver": "^2.0.7",
"@unocss/preset-icons": "^66.5.9",
"svelte": "^5.43.14",
"svelte-check": "^4.3.4",
"@types/three": "^0.182.0",
"svelte": "^5.46.4",
"svelte-check": "^4.3.5",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"unocss": "^66.5.9",
"vite": "^7.2.4",
"vite": "^7.3.1",
"vite-plugin-comlink": "^5.3.0",
"vite-plugin-glsl": "^1.5.4",
"vite-plugin-glsl": "^1.5.5",
"vite-plugin-wasm": "^3.5.0",
"vitest": "^4.0.13"
"vitest": "^4.0.17"
}
}

6
app/src/app.css Normal file
View File

@@ -0,0 +1,6 @@
@import "tailwindcss";
@source "../../packages/ui/**/*.svelte";
@plugin "@iconify/tailwind4" {
prefix: "i";
icon-sets: from-folder(custom, "./src/lib/icons")
};

View File

@@ -5,7 +5,6 @@
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/svelte.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script defer src="https://umami.max-richter.dev/script.js" data-website-id="585c442b-0524-4874-8955-f9853b44b17e"></script>
%sveltekit.head%
<title>Nodes</title>
<script>

2
app/src/lib/config.ts Normal file
View File

@@ -0,0 +1,2 @@
import { PUBLIC_ANALYTIC_SCRIPT } from "$env/static/public";
export const ANALYTIC_SCRIPT = PUBLIC_ANALYTIC_SCRIPT;

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { HTML } from "@threlte/extras";
import { onMount } from "svelte";
import type { Node, NodeType } from "@nodarium/types";
import { getGraphManager, getGraphState } from "../graph/state.svelte";
import type { NodeInstance, NodeId } from "@nodarium/types";
import { getGraphManager, getGraphState } from "../graph-state.svelte";
type Props = {
onnode: (n: Node) => void;
onnode: (n: NodeInstance) => void;
};
const { onnode }: Props = $props();
@@ -15,7 +15,7 @@
let input: HTMLInputElement;
let value = $state<string>();
let activeNodeId = $state<NodeType>();
let activeNodeId = $state<NodeId>();
const allNodes = graphState.activeSocket
? graph.getPossibleNodes(graphState.activeSocket)
@@ -39,13 +39,14 @@
}
});
function handleNodeCreation(nodeType: Node["type"]) {
function handleNodeCreation(nodeType: NodeInstance["type"]) {
if (!graphState.addMenuPosition) return;
onnode?.({
id: -1,
type: nodeType,
position: [...graphState.addMenuPosition],
props: {},
state: {},
});
}

View File

@@ -26,11 +26,10 @@
<script lang="ts">
import { T } from "@threlte/core";
import { MeshLineMaterial } from "@threlte/extras";
import { Mesh, MeshBasicMaterial, Vector3 } from "three";
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js";
import { createEdgeGeometry } from "./createEdgeGeometry.js";
import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = {
@@ -45,7 +44,7 @@
const thickness = $derived(Math.max(0.001, 0.00082 * Math.exp(0.055 * z)));
let mesh = $state<Mesh>();
let points = $state<Vector3[]>([]);
let lastId: string | null = null;
@@ -69,14 +68,10 @@
curve.v2.set(new_x / 2, new_y);
curve.v3.set(new_x, new_y);
const points = curve
points = curve
.getPoints(samples)
.map((p) => new Vector3(p.x, 0, p.y))
.flat();
if (mesh) {
mesh.geometry = createEdgeGeometry(points);
}
}
$effect(() => {
@@ -106,6 +101,7 @@
<T.CircleGeometry args={[0.5, 16]} />
</T.Mesh>
<T.Mesh bind:ref={mesh} position.x={x1} position.z={y1} position.y={0.1}>
<T.Mesh position.x={x1} position.z={y1} position.y={0.1}>
<MeshLineGeometry {points} />
<MeshLineMaterial width={thickness} color={lineColor} />
</T.Mesh>

View File

@@ -1,112 +0,0 @@
import { BufferAttribute, BufferGeometry, Vector3 } from 'three';
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils.js';
export function createEdgeGeometry(points: Vector3[]) {
const length = points[0].distanceTo(points[points.length - 1]);
const startRadius = 8;
const constantWidth = 2;
const taperFraction = 0.8 / length;
function ease(t: number) {
return t * t * (3 - 2 * t);
}
let shapeFunction = (alpha: number) => {
if (alpha < taperFraction) {
const easedAlpha = ease(alpha / taperFraction);
return startRadius + (constantWidth - startRadius) * easedAlpha;
} else if (alpha > 1 - taperFraction) {
const easedAlpha = ease((alpha - (1 - taperFraction)) / taperFraction);
return constantWidth + (startRadius - constantWidth) * easedAlpha;
} else {
return constantWidth;
}
};
// When the component first runs we create the buffer geometry and allocate the buffer attributes
let pointCount = points.length
let counters: number[] = []
let counterIndex = 0
let side: number[] = []
let widthArray: number[] = []
let doubleIndex = 0
let uvArray: number[] = []
let uvIndex = 0
let indices: number[] = []
let indicesIndex = 0
for (let j = 0; j < pointCount; j++) {
const c = j / points.length
counters[counterIndex + 0] = c
counters[counterIndex + 1] = c
counterIndex += 2
setXY(side, doubleIndex, 1, -1)
let width = shapeFunction((j / (pointCount - 1)))
setXY(widthArray, doubleIndex, width, width)
doubleIndex += 2
setXYZW(uvArray, uvIndex, j / (pointCount - 1), 0, j / (pointCount - 1), 1)
uvIndex += 4
if (j < pointCount - 1) {
const n = j * 2
setXYZ(indices, indicesIndex, n + 0, n + 1, n + 2)
setXYZ(indices, indicesIndex + 3, n + 2, n + 1, n + 3)
indicesIndex += 6
}
}
const geometry = new BufferGeometry()
// create these buffer attributes at the correct length but leave them empty for now
geometry.setAttribute('position', new BufferAttribute(new Float32Array(pointCount * 6), 3))
geometry.setAttribute('previous', new BufferAttribute(new Float32Array(pointCount * 6), 3))
geometry.setAttribute('next', new BufferAttribute(new Float32Array(pointCount * 6), 3))
// create and populate these buffer attributes
geometry.setAttribute('counters', new BufferAttribute(new Float32Array(counters), 1))
geometry.setAttribute('side', new BufferAttribute(new Float32Array(side), 1))
geometry.setAttribute('width', new BufferAttribute(new Float32Array(widthArray), 1))
geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvArray), 2))
geometry.setIndex(new BufferAttribute(new Uint16Array(indices), 1))
let positions: number[] = []
let previous: number[] = []
let next: number[] = []
let positionIndex = 0
let previousIndex = 0
let nextIndex = 0
setXYZXYZ(previous, previousIndex, points[0].x, points[0].y, points[0].z)
previousIndex += 6
for (let j = 0; j < pointCount; j++) {
const p = points[j]
setXYZXYZ(positions, positionIndex, p.x, p.y, p.z)
positionIndex += 6
if (j < pointCount - 1) {
setXYZXYZ(previous, previousIndex, p.x, p.y, p.z)
previousIndex += 6
}
if (j > 0 && j + 1 <= pointCount) {
setXYZXYZ(next, nextIndex, p.x, p.y, p.z)
nextIndex += 6
}
}
setXYZXYZ(
next,
nextIndex,
points[pointCount - 1].x,
points[pointCount - 1].y,
points[pointCount - 1].z
)
const positionAttribute = (geometry.getAttribute('position') as BufferAttribute).set(positions)
const previousAttribute = (geometry.getAttribute('previous') as BufferAttribute).set(previous)
const nextAttribute = (geometry.getAttribute('next') as BufferAttribute).set(next)
positionAttribute.needsUpdate = true
previousAttribute.needsUpdate = true
nextAttribute.needsUpdate = true
geometry.computeBoundingSphere()
return geometry;
}

View File

@@ -1,11 +1,11 @@
import type {
Edge,
Graph,
Node,
NodeInstance,
NodeDefinition,
NodeInput,
NodeRegistry,
NodeType,
NodeId,
Socket,
} from "@nodarium/types";
import { fastHashString } from "@nodarium/utils";
@@ -68,7 +68,7 @@ export class GraphManager extends EventEmitter<{
graph: Graph = { id: 0, nodes: [], edges: [] };
id = $state(0);
nodes = new SvelteMap<number, Node>();
nodes = new SvelteMap<number, NodeInstance>();
edges = $state<Edge[]>([]);
@@ -101,7 +101,7 @@ export class GraphManager extends EventEmitter<{
position: [...node.position],
type: node.type,
props: node.props,
})) as Node[];
})) as NodeInstance[];
const edges = this.edges.map((edge) => [
edge[0].id,
edge[1],
@@ -133,8 +133,8 @@ export class GraphManager extends EventEmitter<{
return this.registry.getAllNodes();
}
getLinkedNodes(node: Node) {
const nodes = new Set<Node>();
getLinkedNodes(node: NodeInstance) {
const nodes = new Set<NodeInstance>();
const stack = [node];
while (stack.length) {
const n = stack.pop();
@@ -148,10 +148,10 @@ export class GraphManager extends EventEmitter<{
return [...nodes.values()];
}
getEdgesBetweenNodes(nodes: Node[]): [number, number, number, string][] {
getEdgesBetweenNodes(nodes: NodeInstance[]): [number, number, number, string][] {
const edges = [];
for (const node of nodes) {
const children = node.tmp?.children || [];
const children = node.state?.children || [];
for (const child of children) {
if (nodes.includes(child)) {
const edge = this.edges.find(
@@ -174,14 +174,15 @@ export class GraphManager extends EventEmitter<{
private _init(graph: Graph) {
const nodes = new Map(
graph.nodes.map((node: Node) => {
graph.nodes.map((node) => {
const nodeType = this.registry.getNode(node.type);
const n = node as NodeInstance;
if (nodeType) {
node.tmp = {
n.state = {
type: nodeType,
};
}
return [node.id, node];
return [node.id, n];
}),
);
@@ -191,12 +192,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) {
throw new Error("Edge references non-existing node");
}
from.tmp = from.tmp || {};
from.tmp.children = from.tmp.children || [];
from.tmp.children.push(to);
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
from.state.children = from.state.children || [];
from.state.children.push(to);
to.state.parents = to.state.parents || [];
to.state.parents.push(from);
return [from, edge[1], to, edge[3]] as Edge;
});
@@ -232,8 +231,10 @@ export class GraphManager extends EventEmitter<{
this.status = "error";
return;
}
node.tmp = node.tmp || {};
node.tmp.type = nodeType;
// Turn into runtime node
const n = node as NodeInstance;
n.state = {};
n.state.type = nodeType;
}
// load settings
@@ -292,7 +293,7 @@ export class GraphManager extends EventEmitter<{
return this.registry.getNode(id);
}
async loadNodeType(id: NodeType) {
async loadNodeType(id: NodeId) {
await this.registry.load([id]);
const nodeType = this.registry.getNode(id);
@@ -321,19 +322,19 @@ export class GraphManager extends EventEmitter<{
this.emit("settings", { types: settingTypes, values: settingValues });
}
getChildren(node: Node) {
getChildren(node: NodeInstance) {
const children = [];
const stack = node.tmp?.children?.slice(0);
const stack = node.state?.children?.slice(0);
while (stack?.length) {
const child = stack.pop();
if (!child) continue;
children.push(child);
stack.push(...(child.tmp?.children || []));
stack.push(...(child.state?.children || []));
}
return children;
}
getNodesBetween(from: Node, to: Node): Node[] | undefined {
getNodesBetween(from: NodeInstance, to: NodeInstance): NodeInstance[] | undefined {
// < - - - - from
const toParents = this.getParentsOfNode(to);
// < - - - - from - - - - to
@@ -350,7 +351,7 @@ export class GraphManager extends EventEmitter<{
}
}
removeNode(node: Node, { restoreEdges = false } = {}) {
removeNode(node: NodeInstance, { restoreEdges = false } = {}) {
const edgesToNode = this.edges.filter((edge) => edge[2].id === node.id);
const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id);
for (const edge of [...edgesToNode, ...edgesFromNode]) {
@@ -363,8 +364,8 @@ export class GraphManager extends EventEmitter<{
for (const [to, toSocket] of inputSockets) {
for (const [from, fromSocket] of outputSockets) {
const outputType = from.tmp?.type?.outputs?.[fromSocket];
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type;
const outputType = from.state?.type?.outputs?.[fromSocket];
const inputType = to?.state?.type?.inputs?.[toSocket]?.type;
if (outputType === inputType) {
this.createEdge(from, fromSocket, to, toSocket, {
applyUpdate: false,
@@ -380,9 +381,9 @@ export class GraphManager extends EventEmitter<{
this.save();
}
smartConnect(from: Node, to: Node): Edge | undefined {
const inputs = Object.entries(to.tmp?.type?.inputs ?? {});
const outputs = from.tmp?.type?.outputs ?? [];
smartConnect(from: NodeInstance, to: NodeInstance): Edge | undefined {
const inputs = Object.entries(to.state?.type?.inputs ?? {});
const outputs = from.state?.type?.outputs ?? [];
for (let i = 0; i < inputs.length; i++) {
const [inputName, input] = inputs[0];
for (let o = 0; o < outputs.length; o++) {
@@ -398,7 +399,7 @@ export class GraphManager extends EventEmitter<{
return Math.max(0, ...this.nodes.keys()) + 1;
}
createGraph(nodes: Node[], edges: [number, number, number, string][]) {
createGraph(nodes: NodeInstance[], edges: [number, number, number, string][]) {
// map old ids to new ids
const idMap = new Map<number, number>();
@@ -422,13 +423,11 @@ export class GraphManager extends EventEmitter<{
throw new Error("Edge references non-existing node");
}
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
to.state.parents = to.state.parents || [];
to.state.parents.push(from);
from.tmp = from.tmp || {};
from.tmp.children = from.tmp.children || [];
from.tmp.children.push(to);
from.state.children = from.state.children || [];
from.state.children.push(to);
return [from, edge[1], to, edge[3]] as Edge;
});
@@ -448,9 +447,9 @@ export class GraphManager extends EventEmitter<{
position,
props = {},
}: {
type: Node["type"];
position: Node["position"];
props: Node["props"];
type: NodeInstance["type"];
position: NodeInstance["position"];
props: NodeInstance["props"];
}) {
const nodeType = this.registry.getNode(type);
if (!nodeType) {
@@ -458,11 +457,11 @@ export class GraphManager extends EventEmitter<{
return;
}
const node: Node = $state({
const node: NodeInstance = $state({
id: this.createNodeId(),
type,
position,
tmp: { type: nodeType },
state: { type: nodeType },
props,
});
@@ -474,9 +473,9 @@ export class GraphManager extends EventEmitter<{
}
createEdge(
from: Node,
from: NodeInstance,
fromSocket: number,
to: Node,
to: NodeInstance,
toSocket: string,
{ applyUpdate = true } = {},
): Edge | undefined {
@@ -493,10 +492,10 @@ export class GraphManager extends EventEmitter<{
}
// check if socket types match
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket];
const toSocketType = [to.tmp?.type?.inputs?.[toSocket]?.type];
if (to.tmp?.type?.inputs?.[toSocket]?.accepts) {
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || []));
const fromSocketType = from.state?.type?.outputs?.[fromSocket];
const toSocketType = [to.state?.type?.inputs?.[toSocket]?.type];
if (to.state?.type?.inputs?.[toSocket]?.accepts) {
toSocketType.push(...(to?.state?.type?.inputs?.[toSocket]?.accepts || []));
}
if (!areSocketsCompatible(fromSocketType, toSocketType)) {
@@ -517,13 +516,10 @@ export class GraphManager extends EventEmitter<{
this.edges.push(edge);
from.tmp = from.tmp || {};
from.tmp.children = from.tmp.children || [];
from.tmp.children.push(to);
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
from.state.children = from.state.children || [];
from.state.children.push(to);
to.state.parents = to.state.parents || [];
to.state.parents.push(from);
if (applyUpdate) {
this.save();
@@ -566,9 +562,9 @@ export class GraphManager extends EventEmitter<{
logger.log("saving graphs", state);
}
getParentsOfNode(node: Node) {
getParentsOfNode(node: NodeInstance) {
const parents = [];
const stack = node.tmp?.parents?.slice(0);
const stack = node.state?.parents?.slice(0);
while (stack?.length) {
if (parents.length > 1000000) {
logger.warn("Infinite loop detected");
@@ -577,7 +573,7 @@ export class GraphManager extends EventEmitter<{
const parent = stack.pop();
if (!parent) continue;
parents.push(parent);
stack.push(...(parent.tmp?.parents || []));
stack.push(...(parent.state?.parents || []));
}
return parents.reverse();
}
@@ -585,7 +581,7 @@ export class GraphManager extends EventEmitter<{
getPossibleNodes(socket: Socket): NodeDefinition[] {
const allDefinitions = this.getNodeDefinitions();
const nodeType = socket.node.tmp?.type;
const nodeType = socket.node.state?.type;
if (!nodeType) {
return [];
}
@@ -612,11 +608,11 @@ export class GraphManager extends EventEmitter<{
}
getPossibleSockets({ node, index }: Socket): [Node, string | number][] {
const nodeType = node?.tmp?.type;
getPossibleSockets({ node, index }: Socket): [NodeInstance, string | number][] {
const nodeType = node?.state?.type;
if (!nodeType) return [];
const sockets: [Node, string | number][] = [];
const sockets: [NodeInstance, string | number][] = [];
// if index is a string, we are an input looking for outputs
if (typeof index === "string") {
@@ -629,7 +625,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType?.inputs?.[index].type;
for (const node of nodes) {
const nodeType = node?.tmp?.type;
const nodeType = node?.state?.type;
const inputs = nodeType?.outputs;
if (!inputs) continue;
for (let index = 0; index < inputs.length; index++) {
@@ -657,7 +653,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType.outputs?.[index];
for (const node of nodes) {
const inputs = node?.tmp?.type?.inputs;
const inputs = node?.state?.type?.inputs;
if (!inputs) continue;
for (const key in inputs) {
const otherType = [inputs[key].type];
@@ -692,17 +688,15 @@ export class GraphManager extends EventEmitter<{
if (!_edge) return;
edge[0].tmp = edge[0].tmp || {};
if (edge[0].tmp.children) {
edge[0].tmp.children = edge[0].tmp.children.filter(
(n: Node) => n.id !== id2,
if (edge[0].state.children) {
edge[0].state.children = edge[0].state.children.filter(
(n: NodeInstance) => n.id !== id2,
);
}
edge[2].tmp = edge[2].tmp || {};
if (edge[2].tmp.parents) {
edge[2].tmp.parents = edge[2].tmp.parents.filter(
(n: Node) => n.id !== id0,
if (edge[2].state.parents) {
edge[2].state.parents = edge[2].state.parents.filter(
(n: NodeInstance) => n.id !== id0,
);
}
@@ -714,7 +708,7 @@ export class GraphManager extends EventEmitter<{
}
getEdgesToNode(node: Node) {
getEdgesToNode(node: NodeInstance) {
return this.edges
.filter((edge) => edge[2].id === node.id)
.map((edge) => {
@@ -723,10 +717,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const;
})
.filter(Boolean) as unknown as [Node, number, Node, string][];
.filter(Boolean) as unknown as [NodeInstance, number, NodeInstance, string][];
}
getEdgesFromNode(node: Node) {
getEdgesFromNode(node: NodeInstance) {
return this.edges
.filter((edge) => edge[0].id === node.id)
.map((edge) => {
@@ -735,6 +729,6 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const;
})
.filter(Boolean) as unknown as [Node, number, Node, string][];
.filter(Boolean) as unknown as [NodeInstance, number, NodeInstance, string][];
}
}

View File

@@ -1,7 +1,7 @@
import type { Node, Socket } from "@nodarium/types";
import type { NodeInstance, Socket } from "@nodarium/types";
import { getContext, setContext } from "svelte";
import { SvelteSet } from "svelte/reactivity";
import type { GraphManager } from "../graph-manager.svelte";
import type { GraphManager } from "./graph-manager.svelte";
import type { OrthographicCamera } from "three";
@@ -24,7 +24,24 @@ export function setGraphManager(manager: GraphManager) {
export class GraphState {
constructor(private graph: GraphManager) { }
constructor(private graph: GraphManager) {
$effect.root(() => {
$effect(() => {
localStorage.setItem("cameraPosition", `[${this.cameraPosition[0]},${this.cameraPosition[1]},${this.cameraPosition[2]}]`)
})
})
const storedPosition = localStorage.getItem("cameraPosition")
if (storedPosition) {
try {
const d = JSON.parse(storedPosition);
this.cameraPosition[0] = d[0];
this.cameraPosition[1] = d[1];
this.cameraPosition[2] = d[2];
} catch (e) {
console.log("Failed to parsed stored camera position", e);
}
}
}
width = $state(100);
height = $state(100);
@@ -38,7 +55,7 @@ export class GraphState {
cameraPosition: [number, number, number] = $state([0, 0, 4]);
clipboard: null | {
nodes: Node[];
nodes: NodeInstance[];
edges: [number, number, number, string][];
} = null;
@@ -80,42 +97,24 @@ export class GraphState {
isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
setCameraTransform(
x = this.cameraPosition[0],
y = this.cameraPosition[1],
z = this.cameraPosition[2],
) {
if (this.camera) {
this.camera.position.x = x;
this.camera.position.z = y;
this.camera.zoom = z;
updateNodePosition(node: NodeInstance) {
if (
node.state.x === node.position[0] &&
node.state.y === node.position[1]
) {
delete node.state.x;
delete node.state.y;
}
this.cameraPosition = [x, y, z];
localStorage.setItem("cameraPosition", JSON.stringify(this.cameraPosition));
}
updateNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
if (node.state["x"] !== undefined && node.state["y"] !== undefined) {
if (node.state.ref) {
node.state.ref.style.setProperty("--nx", `${node.state.x * 10}px`);
node.state.ref.style.setProperty("--ny", `${node.state.y * 10}px`);
}
} else {
if (node.state.ref) {
node.state.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.state.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
}
}
}
@@ -134,19 +133,19 @@ export class GraphState {
}
getSocketPosition(
node: Node,
node: NodeInstance,
index: string | number,
): [number, number] {
if (typeof index === "number") {
return [
(node?.tmp?.x ?? node.position[0]) + 20,
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index,
(node?.state?.x ?? node.position[0]) + 20,
(node?.state?.y ?? node.position[1]) + 2.5 + 10 * index,
];
} else {
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
const _index = Object.keys(node.state?.type?.inputs || {}).indexOf(index);
return [
node?.tmp?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index,
node?.state?.x ?? node.position[0],
(node?.state?.y ?? node.position[1]) + 10 + 10 * _index,
];
}
}
@@ -174,32 +173,6 @@ export class GraphState {
return height;
}
setNodePosition(node: Node) {
if (node?.tmp?.ref && node?.tmp?.mesh) {
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) {
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`);
node.tmp.mesh.position.x = node.tmp.x + 10;
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2;
if (
node.tmp.x === node.position[0] &&
node.tmp.y === node.position[1]
) {
delete node.tmp.x;
delete node.tmp.y;
}
this.graph.edges = [...this.graph.edges];
} else {
node.tmp.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
node.tmp.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
node.tmp.mesh.position.x = node.position[0] + 10;
node.tmp.mesh.position.z =
node.position[1] + this.getNodeHeight(node.type) / 2;
}
}
}
copyNodes() {
if (this.activeNodeId === -1 && !this.selectedNodes?.size)
return;
@@ -231,12 +204,11 @@ export class GraphState {
const nodes = this.clipboard.nodes
.map((node) => {
node.tmp = node.tmp || {};
node.position[0] = this.mousePosition[0] - node.position[0];
node.position[1] = this.mousePosition[1] - node.position[1];
return node;
})
.filter(Boolean) as Node[];
.filter(Boolean) as NodeInstance[];
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
this.selectedNodes.clear();
@@ -327,7 +299,7 @@ export class GraphState {
return clickedNodeId;
}
isNodeInView(node: Node) {
isNodeInView(node: NodeInstance) {
const height = this.getNodeHeight(node.type);
const width = 20;
return (

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import type { Edge, Node } from "@nodarium/types";
import { onMount } from "svelte";
import type { Edge, NodeInstance } from "@nodarium/types";
import { createKeyMap } from "../../helpers/createKeyMap";
import AddMenu from "../components/AddMenu.svelte";
import Background from "../background/Background.svelte";
@@ -10,7 +9,7 @@
import Camera from "../components/Camera.svelte";
import { Canvas } from "@threlte/core";
import HelpView from "../components/HelpView.svelte";
import { getGraphManager, getGraphState } from "./state.svelte";
import { getGraphManager, getGraphState } from "../graph-state.svelte";
import { HTML } from "@threlte/extras";
import { FileDropEventManager, MouseEventManager } from "./events";
import { maxZoom, minZoom } from "./constants";
@@ -40,7 +39,7 @@
return [pos1[0], pos1[1], pos2[0], pos2[1]];
}
function handleNodeCreation(node: Node) {
function handleNodeCreation(node: NodeInstance) {
const newNode = graph.createNode({
type: node.type,
position: node.position,
@@ -51,11 +50,11 @@
if (graphState.activeSocket) {
if (typeof graphState.activeSocket.index === "number") {
const socketType =
graphState.activeSocket.node.tmp?.type?.outputs?.[
graphState.activeSocket.node.state?.type?.outputs?.[
graphState.activeSocket.index
];
const input = Object.entries(newNode?.tmp?.type?.inputs || {}).find(
const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
(inp) => inp[1].type === socketType,
);
@@ -69,11 +68,11 @@
}
} else {
const socketType =
graphState.activeSocket.node.tmp?.type?.inputs?.[
graphState.activeSocket.node.state?.type?.inputs?.[
graphState.activeSocket.index
];
const output = newNode.tmp?.type?.outputs?.find((out) => {
const output = newNode.state?.type?.outputs?.find((out) => {
if (socketType?.type === out) return true;
if (socketType?.accepts?.includes(out as any)) return true;
return false;
@@ -93,15 +92,6 @@
graphState.activeSocket = null;
graphState.addMenuPosition = null;
}
onMount(() => {
if (localStorage.getItem("cameraPosition")) {
const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
if (Array.isArray(cPosition)) {
graphState.setCameraTransform(cPosition[0], cPosition[1], cPosition[2]);
}
}
});
</script>
<svelte:window

View File

@@ -1,9 +1,13 @@
<script lang="ts">
import type { Graph, Node, NodeRegistry } from "@nodarium/types";
import type { Graph, NodeInstance, NodeRegistry } from "@nodarium/types";
import GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap";
import { GraphState, setGraphManager, setGraphState } from "./state.svelte";
import {
GraphState,
setGraphManager,
setGraphState,
} from "../graph-state.svelte";
import { setupKeymaps } from "../keymaps";
type Props = {
@@ -12,7 +16,7 @@
settings?: Record<string, any>;
activeNode?: Node;
activeNode?: NodeInstance;
showGrid?: boolean;
snapToGrid?: boolean;
showHelp?: boolean;

View File

@@ -1,6 +1,6 @@
import { GraphSchema, type NodeType, type Node } from "@nodarium/types";
import { GraphSchema, type NodeId, type NodeInstance } from "@nodarium/types";
import type { GraphManager } from "../graph-manager.svelte";
import type { GraphState } from "./state.svelte";
import type { GraphState } from "../graph-state.svelte";
import { animate, lerp } from "$lib/helpers";
import { snapToGrid as snapPointToGrid } from "../helpers";
import { maxZoom, minZoom, zoomSpeed } from "./constants";
@@ -17,7 +17,7 @@ export class FileDropEventManager {
event.preventDefault();
this.state.isDragging = false;
if (!event.dataTransfer) return;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeType;
const nodeId = event.dataTransfer.getData("data/node-id") as NodeId;
let mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y;
@@ -131,45 +131,45 @@ export class MouseEventManager {
if (clickedNodeId !== -1) {
if (activeNode) {
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) {
if (!activeNode?.state?.isMoving && !event.ctrlKey && !event.shiftKey) {
this.state.activeNodeId = clickedNodeId;
this.state.clearSelection();
}
}
}
if (activeNode?.tmp?.isMoving) {
activeNode.tmp = activeNode.tmp || {};
activeNode.tmp.isMoving = false;
if (activeNode?.state?.isMoving) {
activeNode.state = activeNode.state || {};
activeNode.state.isMoving = false;
if (this.state.snapToGrid) {
const snapLevel = this.state.getSnapLevel();
activeNode.position[0] = snapPointToGrid(
activeNode?.tmp?.x ?? activeNode.position[0],
activeNode?.state?.x ?? activeNode.position[0],
5 / snapLevel,
);
activeNode.position[1] = snapPointToGrid(
activeNode?.tmp?.y ?? activeNode.position[1],
activeNode?.state?.y ?? activeNode.position[1],
5 / snapLevel,
);
} else {
activeNode.position[0] = activeNode?.tmp?.x ?? activeNode.position[0];
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1];
activeNode.position[0] = activeNode?.state?.x ?? activeNode.position[0];
activeNode.position[1] = activeNode?.state?.y ?? activeNode.position[1];
}
const nodes = [
...[...(this.state.selectedNodes?.values() || [])].map((id) =>
this.graph.getNode(id),
),
] as Node[];
] as NodeInstance[];
const vec = [
activeNode.position[0] - (activeNode?.tmp.x || 0),
activeNode.position[1] - (activeNode?.tmp.y || 0),
activeNode.position[0] - (activeNode?.state.x || 0),
activeNode.position[1] - (activeNode?.state.y || 0),
];
for (const node of nodes) {
if (!node) continue;
node.tmp = node.tmp || {};
const { x, y } = node.tmp;
node.state = node.state || {};
const { x, y } = node.state;
if (x !== undefined && y !== undefined) {
node.position[0] = x + vec[0];
node.position[1] = y + vec[1];
@@ -179,14 +179,14 @@ export class MouseEventManager {
animate(500, (a: number) => {
for (const node of nodes) {
if (
node?.tmp &&
node.tmp["x"] !== undefined &&
node.tmp["y"] !== undefined
node?.state &&
node.state["x"] !== undefined &&
node.state["y"] !== undefined
) {
node.tmp.x = lerp(node.tmp.x, node.position[0], a);
node.tmp.y = lerp(node.tmp.y, node.position[1], a);
node.state.x = lerp(node.state.x, node.position[0], a);
node.state.y = lerp(node.state.y, node.position[1], a);
this.state.updateNodePosition(node);
if (node?.tmp?.isMoving) {
if (node?.state?.isMoving) {
return false;
}
}
@@ -318,17 +318,17 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId);
if (!node) return;
node.tmp = node.tmp || {};
node.tmp.downX = node.position[0];
node.tmp.downY = node.position[1];
node.state = node.state || {};
node.state.downX = node.position[0];
node.state.downY = node.position[1];
if (this.state.selectedNodes) {
for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId);
if (!n) continue;
n.tmp = n.tmp || {};
n.tmp.downX = n.position[0];
n.tmp.downY = n.position[1];
n.state = n.state || {};
n.state.downX = n.position[0];
n.state.downY = n.position[1];
}
}
@@ -382,7 +382,7 @@ export class MouseEventManager {
const y1 = Math.min(mouseD[1], this.state.mousePosition[1]);
const y2 = Math.max(mouseD[1], this.state.mousePosition[1]);
for (const node of this.graph.nodes.values()) {
if (!node?.tmp) continue;
if (!node?.state) continue;
const x = node.position[0];
const y = node.position[1];
const height = this.state.getNodeHeight(node.type);
@@ -400,10 +400,10 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId);
if (!node || event.buttons !== 1) return;
node.tmp = node.tmp || {};
node.state = node.state || {};
const oldX = node.tmp.downX || 0;
const oldY = node.tmp.downY || 0;
const oldX = node.state.downX || 0;
const oldY = node.state.downY || 0;
let newX =
oldX + (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2];
@@ -418,10 +418,10 @@ export class MouseEventManager {
}
}
if (!node.tmp.isMoving) {
if (!node.state.isMoving) {
const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
if (dist > 0.2) {
node.tmp.isMoving = true;
node.state.isMoving = true;
}
}
@@ -431,15 +431,15 @@ export class MouseEventManager {
if (this.state.selectedNodes?.size) {
for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId);
if (!n?.tmp) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY;
if (!n?.state) continue;
n.state.x = (n?.state?.downX || 0) - vecX;
n.state.y = (n?.state?.downY || 0) - vecY;
this.state.updateNodePosition(n);
}
}
node.tmp.x = newX;
node.tmp.y = newY;
node.state.x = newX;
node.state.y = newY;
this.state.updateNodePosition(node);
@@ -455,7 +455,8 @@ export class MouseEventManager {
this.state.cameraDown[1] -
(my - this.state.mouseDown[1]) / this.state.cameraPosition[2];
this.state.setCameraTransform(newX, newY);
this.state.cameraPosition[0] = newX;
this.state.cameraPosition[1] = newY;
}
@@ -486,15 +487,13 @@ export class MouseEventManager {
const zoomRatio = newZoom / this.state.cameraPosition[2];
// Update camera position and zoom level
this.state.setCameraTransform(
this.state.mousePosition[0] -
this.state.cameraPosition[0] = this.state.mousePosition[0] -
(this.state.mousePosition[0] - this.state.cameraPosition[0]) /
zoomRatio,
this.state.mousePosition[1] -
zoomRatio;
this.state.cameraPosition[1] = this.state.mousePosition[1] -
(this.state.mousePosition[1] - this.state.cameraPosition[1]) /
zoomRatio,
newZoom,
);
this.state.cameraPosition[2] = newZoom;
}
}

View File

@@ -2,7 +2,7 @@ import { animate, lerp } from "$lib/helpers";
import type { createKeyMap } from "$lib/helpers/createKeyMap";
import FileSaver from "file-saver";
import type { GraphManager } from "./graph-manager.svelte";
import type { GraphState } from "./graph/state.svelte";
import type { GraphState } from "./graph-state.svelte";
type Keymap = ReturnType<typeof createKeyMap>;
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) {
@@ -88,11 +88,9 @@ export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: Gr
const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
animate(500, (a: number) => {
graphState.setCameraTransform(
lerp(camX, average[0], ease(a)),
lerp(camY, average[1], ease(a)),
lerp(camZ, 2, ease(a)),
);
graphState.cameraPosition[0] = lerp(camX, average[0], ease(a));
graphState.cameraPosition[1] = lerp(camY, average[1], ease(a));
graphState.cameraPosition[2] = lerp(camZ, 2, ease(a))
if (graphState.mouseDown) return false;
});
},

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import type { Node } from "@nodarium/types";
import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte";
import type { NodeInstance } from "@nodarium/types";
import { getGraphState } from "../graph-state.svelte";
import { T } from "@threlte/core";
import { type Mesh } from "three";
import NodeFrag from "./Node.frag";
@@ -13,11 +12,11 @@
const graphState = getGraphState();
type Props = {
node: Node;
node: NodeInstance;
inView: boolean;
z: number;
};
let { node, inView, z }: Props = $props();
let { node = $bindable(), inView, z }: Props = $props();
const isActive = $derived(graphState.activeNodeId === node.id);
const isSelected = $derived(graphState.selectedNodes.has(node.id));
@@ -35,17 +34,16 @@
const height = graphState.getNodeHeight(node.type);
$effect(() => {
if (!node.tmp) node.tmp = {};
if (meshRef && !node.tmp?.mesh) {
node.tmp.mesh = meshRef;
if (meshRef && !node.state?.mesh) {
node.state.mesh = meshRef;
graphState.updateNodePosition(node);
}
});
</script>
<T.Mesh
position.x={node.position[0] + 10}
position.z={node.position[1] + height / 2}
position.x={(node.state.x ?? node.position[0]) + 10}
position.z={(node.state.y ?? node.position[1]) + height / 2}
position.y={0.8}
rotation.x={-Math.PI / 2}
bind:ref={meshRef}
@@ -69,4 +67,4 @@
/>
</T.Mesh>
<NodeHtml {node} {inView} {isActive} {isSelected} {z} />
<NodeHtml bind:node {inView} {isActive} {isSelected} {z} />

View File

@@ -1,16 +1,15 @@
<script lang="ts">
import type { Node } from "@nodarium/types";
import type { NodeInstance } from "@nodarium/types";
import NodeHeader from "./NodeHeader.svelte";
import NodeParameter from "./NodeParameter.svelte";
import { onMount } from "svelte";
import { getGraphState } from "../graph/state.svelte";
import { getGraphState } from "../graph-state.svelte";
let ref: HTMLDivElement;
const graphState = getGraphState();
type Props = {
node: Node;
node: NodeInstance;
position?: "absolute" | "fixed" | "relative";
isActive?: boolean;
isSelected?: boolean;
@@ -31,15 +30,16 @@
const zOffset = Math.random() - 0.5;
const zLimit = 2 - zOffset;
const parameters = Object.entries(node?.tmp?.type?.inputs || {}).filter(
const parameters = Object.entries(node.state?.type?.inputs || {}).filter(
(p) =>
p[1].type !== "seed" && !("setting" in p[1]) && p[1]?.hidden !== true,
);
onMount(() => {
node.tmp = node.tmp || {};
node.tmp.ref = ref;
graphState?.updateNodePosition(node);
$effect(() => {
if ("state" in node && !node.state.ref) {
node.state.ref = ref;
graphState?.updateNodePosition(node);
}
});
</script>

View File

@@ -1,24 +1,26 @@
<script lang="ts">
import { getGraphState } from "../graph/state.svelte.js";
import { getGraphState } from "../graph-state.svelte";
import { createNodePath } from "../helpers/index.js";
import type { Node } from "@nodarium/types";
import type { NodeInstance } from "@nodarium/types";
const graphState = getGraphState();
const { node }: { node: Node } = $props();
const { node }: { node: NodeInstance } = $props();
function handleMouseDown(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
graphState.setDownSocket?.({
node,
index: 0,
position: graphState.getSocketPosition?.(node, 0),
});
if ("state" in node) {
graphState.setDownSocket?.({
node,
index: 0,
position: graphState.getSocketPosition?.(node, 0),
});
}
}
const cornerTop = 10;
const rightBump = !!node?.tmp?.type?.outputs?.length;
const rightBump = !!node?.state?.type?.outputs?.length;
const aspectRatio = 0.25;
const path = createNodePath({
@@ -29,14 +31,6 @@
rightBump,
aspectRatio,
});
// const pathDisabled = createNodePath({
// depth: 0,
// height: 15,
// y: 50,
// cornerTop,
// rightBump,
// aspectRatio,
// });
const pathHover = createNodePath({
depth: 8.5,
height: 50,

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import type { Node, NodeInput } from "@nodarium/types";
import type { NodeInstance, NodeInput } from "@nodarium/types";
import { Input } from "@nodarium/ui";
import type { GraphManager } from "../graph-manager.svelte";
type Props = {
node: Node;
node: NodeInstance;
input: NodeInput;
id: string;
elementId?: string;

View File

@@ -1,15 +1,12 @@
<script lang="ts">
import type {
NodeInput as NodeInputType,
Node as NodeType,
} from "@nodarium/types";
import { createNodePath } from "../helpers/index.js";
import NodeInput from "./NodeInput.svelte";
import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
import type { NodeInput, NodeInstance } from "@nodarium/types";
import { createNodePath } from "../helpers";
import NodeInputEl from "./NodeInput.svelte";
import { getGraphManager, getGraphState } from "../graph-state.svelte";
type Props = {
node: NodeType;
input: NodeInputType;
node: NodeInstance;
input: NodeInput;
id: string;
isLast?: boolean;
};
@@ -18,7 +15,7 @@
let { node = $bindable(), input, id, isLast }: Props = $props();
const inputType = node?.tmp?.type?.inputs?.[id]!;
const inputType = node?.state?.type?.inputs?.[id]!;
const socketId = `${node.id}-${id}`;
@@ -37,7 +34,7 @@
});
}
const leftBump = node.tmp?.type?.inputs?.[id].internal !== true;
const leftBump = node.state?.type?.inputs?.[id].internal !== true;
const cornerBottom = isLast ? 5 : 0;
const aspectRatio = 0.5;
@@ -79,11 +76,11 @@
<label for={elementId}>{input.label || id}</label>
{/if}
{#if inputType.external !== true}
<NodeInput {graph} {elementId} bind:node {input} {id} />
<NodeInputEl {graph} {elementId} bind:node {input} {id} />
{/if}
</div>
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
{#if node?.state?.type?.inputs?.[id]?.internal !== true}
<div data-node-socket class="large target"></div>
<div
data-node-socket

View File

@@ -16,9 +16,6 @@ export function grid(width: number, height: number) {
graph.nodes.push({
id: i,
tmp: {
visible: false,
},
position: [x * 30, y * 40],
props: i == 0 ? { value: 0 } : { op_type: 0, a: 1, b: 0.05 },
type: i == 0 ? "max/plantarium/float" : "max/plantarium/math",
@@ -29,9 +26,6 @@ export function grid(width: number, height: number) {
graph.nodes.push({
id: amount,
tmp: {
visible: false,
},
position: [width * 30, (height - 1) * 40],
type: "max/plantarium/output",
props: {},

View File

@@ -1,8 +1,8 @@
import type { Graph, Node } from "@nodarium/types";
import type { Graph, SerializedNode } from "@nodarium/types";
export function tree(depth: number): Graph {
const nodes: Node[] = [
const nodes: SerializedNode[] = [
{
id: 0,
type: "max/plantarium/output",

View File

@@ -1,12 +1,16 @@
<script lang="ts">
import { getContext } from "svelte";
import { getContext, type Snippet } from "svelte";
let index = -1;
let index = $state(-1);
let wrapper: HTMLDivElement;
$: if (index === -1) {
index = getContext<() => number>("registerCell")();
}
const { children } = $props<{ children?: Snippet }>();
$effect(() => {
if (index === -1) {
index = getContext<() => number>("registerCell")();
}
});
const sizes = getContext<{ value: string[] }>("sizes");
@@ -31,8 +35,8 @@
</script>
<svelte:window
on:mouseup={() => (mouseDown = false)}
on:mousemove={handleMouseMove}
onmouseup={() => (mouseDown = false)}
onmousemove={handleMouseMove}
/>
{#if index > 0}
@@ -40,12 +44,12 @@
class="seperator"
role="button"
tabindex="0"
on:mousedown={handleMouseDown}
onmousedown={handleMouseDown}
></div>
{/if}
<div class="cell" bind:this={wrapper}>
<slot />
{@render children?.()}
</div>
<style>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { setContext } from "svelte";
import { setContext, type Snippet } from "svelte";
export let id = "grid-0";
const { children, id } = $props<{ children?: Snippet; id?: string }>();
setContext("grid-id", id);
</script>
<slot {id} />
{@render children({ id })}

View File

@@ -126,7 +126,7 @@ export function humanizeDuration(durationInMilliseconds: number) {
}
if (millis > 0 || durationString === '') {
durationString += millis + 'ms';
durationString += Math.floor(millis) + 'ms';
}
return durationString.trim();

View File

@@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="17" y="8" width="5" height="7" rx="1" stroke="currentColor" stroke-width="2"/>
<rect x="2" y="3" width="5" height="7" rx="1" stroke="currentColor" stroke-width="2"/>
<rect x="2" y="14" width="5" height="7" rx="1" stroke="currentColor" stroke-width="2"/>

Before

Width:  |  Height:  |  Size: 519 B

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -3,7 +3,7 @@ import fs from "fs/promises";
import path from "path";
export async function getWasm(id: `${string}/${string}/${string}`) {
const filePath = path.resolve(`../nodes/${id}/pkg/index_bg.wasm`);
const filePath = path.resolve(`../nodes/${id}/pkg/node.wasm`);
try {
await fs.access(filePath);

View File

@@ -1,10 +1,11 @@
<script lang="ts">
import { Select } from "@nodarium/ui";
import type { Writable } from "svelte/store";
let activeStore = 0;
export let activeId: Writable<string>;
$: [activeUser, activeCollection, activeNode] = $activeId.split(`/`);
let activeStore = $state(0);
let { activeId }: { activeId: string } = $props();
const [activeUser, activeCollection, activeNode] = $derived(
activeId.split(`/`),
);
</script>
<div class="breadcrumbs">
@@ -12,16 +13,16 @@
<Select id="root" options={["root"]} bind:value={activeStore}></Select>
{#if activeCollection}
<button
on:click={() => {
$activeId = activeUser;
onclick={() => {
activeId = activeUser;
}}
>
{activeUser}
</button>
{#if activeNode}
<button
on:click={() => {
$activeId = `${activeUser}/${activeCollection}`;
onclick={() => {
activeId = `${activeUser}/${activeCollection}`;
}}
>
{activeCollection}

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import type { NodeDefinition } from "@nodarium/types";
import type { NodeDefinition, NodeId, NodeInstance } from "@nodarium/types";
export let node: NodeDefinition;
const { node }: { node: NodeDefinition } = $props();
let dragging = false;
let dragging = $state(false);
let nodeData = {
let nodeData = $state<NodeInstance>({
id: 0,
type: node?.id,
type: node.id as unknown as NodeId,
position: [0, 0] as [number, number],
props: {},
tmp: {
state: {
type: node,
},
};
});
function handleDragStart(e: DragEvent) {
dragging = true;
const box = (e?.target as HTMLElement)?.getBoundingClientRect();
if (e.dataTransfer === null) return;
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("data/node-id", node.id);
e.dataTransfer.setData("data/node-id", node.id.toString());
if (nodeData.props) {
e.dataTransfer.setData("data/node-props", JSON.stringify(nodeData.props));
}
@@ -38,15 +38,15 @@
<div class="node-wrapper" class:dragging>
<div
on:dragend={() => {
ondragend={() => {
dragging = false;
}}
draggable={true}
role="button"
tabindex="0"
on:dragstart={handleDragStart}
ondragstart={handleDragStart}
>
<NodeHtml inView={true} position={"relative"} z={5} bind:node={nodeData} />
<NodeHtml bind:node={nodeData} inView={true} position={"relative"} z={5} />
</div>
</div>

View File

@@ -1,19 +1,16 @@
<script lang="ts">
import { writable } from "svelte/store";
import BreadCrumbs from "./BreadCrumbs.svelte";
import DraggableNode from "./DraggableNode.svelte";
import type { RemoteNodeRegistry } from "@nodarium/registry";
export let registry: RemoteNodeRegistry;
const { registry }: { registry: RemoteNodeRegistry } = $props();
const activeId = writable("max/plantarium");
let activeId = $state("max/plantarium");
let showBreadCrumbs = false;
// const activeId = localStore<
// `${string}` | `${string}/${string}` | `${string}/${string}/${string}`
// >("nodes.store.activeId", "");
$: [activeUser, activeCollection, activeNode] = $activeId.split(`/`);
const [activeUser, activeCollection, activeNode] = $derived(
activeId.split(`/`),
);
</script>
{#if showBreadCrumbs}
@@ -27,8 +24,8 @@
{:then users}
{#each users as user}
<button
on:click={() => {
$activeId = user.id;
onclick={() => {
activeId = user.id;
}}>{user.id}</button
>
{/each}
@@ -41,8 +38,8 @@
{:then user}
{#each user.collections as collection}
<button
on:click={() => {
$activeId = collection.id;
onclick={() => {
activeId = collection.id;
}}
>
{collection.id.split(`/`)[1]}

View File

@@ -1,8 +1,12 @@
<script lang="ts">
export let labels: string[] = [];
export let values: number[] = [];
type Props = {
labels: string[];
values: number[];
};
$: total = values.reduce((acc, v) => acc + v, 0);
const { labels, values }: Props = $props();
const total = $derived(values.reduce((acc, v) => acc + v, 0));
let colors = ["red", "green", "blue"];
</script>
@@ -10,7 +14,10 @@
<div class="wrapper">
<div class="bars">
{#each values as value, i}
<div class="bar bg-{colors[i]}" style="width: {(value / total) * 100}%;">
<div
class="bar bg-{colors[i]}-400"
style="width: {(value / total) * 100}%;"
>
{Math.round(value)}ms
</div>
{/each}
@@ -18,12 +25,11 @@
<div class="labels mt-2">
{#each values as _label, i}
<div class="text-{colors[i]}">{labels[i]}</div>
<div class="text-{colors[i]}-400">{labels[i]}</div>
{/each}
</div>
<span
class="bg-red bg-green bg-yellow bg-blue text-red text-green text-yellow text-blue"
class="bg-red-400 bg-green-400 bg-blue-400 text-red-400 text-green-400 text-blue-400"
></span>
</div>

View File

@@ -1,52 +1,59 @@
<script lang="ts">
export let points: number[];
type Props = {
points: number[];
type?: string;
title?: string;
max?: number;
min?: number;
};
export let type = "ms";
export let title = "Performance";
export let max: number | undefined = undefined;
export let min: number | undefined = undefined;
let {
points,
type = "ms",
title = "Performance",
max,
min,
}: Props = $props();
function getMax(m?: number) {
let internalMax = $derived(max ?? Math.max(...points));
let internalMin = $derived(min ?? Math.min(...points))!;
const maxText = $derived.by(() => {
if (type === "%") {
return 100;
}
if (m !== undefined) {
if (m < 1) {
return Math.floor(m * 100) / 100;
if (internalMax !== undefined) {
if (internalMax < 1) {
return Math.floor(internalMax * 100) / 100;
}
if (m < 10) {
return Math.floor(m * 10) / 10;
if (internalMax < 10) {
return Math.floor(internalMax * 10) / 10;
}
return Math.floor(m);
return Math.floor(internalMax);
}
return 1;
}
});
function constructPath() {
max = max !== undefined ? max : Math.max(...points);
min = min !== undefined ? min : Math.min(...points);
const mi = min as number;
const ma = max as number;
return points
const path = $derived(
points
.map((point, i) => {
const x = (i / (points.length - 1)) * 100;
const y = 100 - ((point - mi) / (ma - mi)) * 100;
const y =
100 - ((point - internalMin) / (internalMax - internalMin)) * 100;
return `${x},${y}`;
})
.join(" ");
}
.join(" "),
);
</script>
<div class="wrapper">
<p>{title}</p>
<span class="min">{Math.floor(min || 0)}{type}</span>
<span class="max">{getMax(max)}{type}</span>
<span class="min">{Math.floor(internalMin || 0)}{type}</span>
<span class="max">{maxText}{type}</span>
<svg preserveAspectRatio="none" viewBox="0 0 100 100">
{#key points}
<polyline vector-effect="non-scaling-stroke" points={constructPath()} />
{/key}
<polyline vector-effect="non-scaling-stroke" points={path} />
</svg>
</div>

View File

@@ -2,23 +2,13 @@
import Monitor from "./Monitor.svelte";
import { humanizeNumber } from "$lib/helpers";
import { Checkbox } from "@nodarium/ui";
import localStore from "$lib/helpers/localStore";
import { type PerformanceData } from "@nodarium/utils";
import type { PerformanceData } from "@nodarium/utils";
import BarSplit from "./BarSplit.svelte";
export let data: PerformanceData;
const { data }: { data: PerformanceData } = $props();
let activeType = localStore<string>("nodes.performance.active-type", "total");
let showAverage = true;
function getAverage(key: string) {
return (
data
.map((run) => run[key]?.[0])
.filter((v) => v !== undefined)
.reduce((acc, run) => acc + run, 0) / data.length
);
}
let activeType = $state("total");
let showAverage = $state(true);
function round(v: number) {
if (v < 1) {
@@ -30,45 +20,15 @@
return Math.floor(v);
}
function getAverages() {
let lastRun = data.at(-1);
if (!lastRun) return {};
return Object.keys(lastRun).reduce(
(acc, key) => {
acc[key] = getAverage(key);
return acc;
},
{} as Record<string, number>,
);
}
function getLast(key: string) {
return data.at(-1)?.[key]?.[0] || 0;
}
function getLasts() {
return data.at(-1) || {};
}
function getTotalPerformance(onlyLast = false) {
if (onlyLast) {
return (
getLast("runtime") +
getLast("update-geometries") +
getLast("worker-transfer")
);
function getTitle(t: string) {
if (t.includes("/")) {
return `Node ${t.split("/").slice(-1).join("/")}`;
}
return (
getAverage("runtime") +
getAverage("update-geometries") +
getAverage("worker-transfer")
);
}
function getCacheRatio(onlyLast = false) {
let ratio = onlyLast ? getLast("cache-hit") : getAverage("cache-hit");
return Math.floor(ratio * 100);
return t
.split("-")
.map((v) => v[0].toUpperCase() + v.slice(1))
.join(" ");
}
const viewerKeys = [
@@ -78,10 +38,53 @@
"split-result",
];
function getPerformanceData(onlyLast: boolean = false) {
let data = onlyLast ? getLasts() : getAverages();
// --- Small helpers that query `data` directly ---
function getAverage(key: string) {
const vals = data
.map((run) => run[key]?.[0])
.filter((v) => v !== undefined) as number[];
return Object.entries(data)
if (vals.length === 0) return 0;
return vals.reduce((acc, v) => acc + v, 0) / vals.length;
}
function getLast(key: string) {
return data.at(-1)?.[key]?.[0] || 0;
}
const averages = $derived.by(() => {
const lr = data.at(-1);
if (!lr) return {} as Record<string, number>;
return Object.keys(lr).reduce((acc: Record<string, number>, key) => {
acc[key] = getAverage(key);
return acc;
}, {});
});
const lasts = $derived.by(() => data.at(-1) || {});
const totalPerformance = $derived.by(() => {
const onlyLast =
getLast("runtime") +
getLast("update-geometries") +
getLast("worker-transfer");
const average =
getAverage("runtime") +
getAverage("update-geometries") +
getAverage("worker-transfer");
return { onlyLast, average };
});
const cacheRatio = $derived.by(() => {
return {
onlyLast: Math.floor(getLast("cache-hit") * 100),
average: Math.floor(getAverage("cache-hit") * 100),
};
});
const performanceData = $derived.by(() => {
const source = showAverage ? averages : lasts;
return Object.entries(source)
.filter(
([key]) =>
!key.startsWith("node/") &&
@@ -90,19 +93,18 @@
!viewerKeys.includes(key),
)
.sort((a, b) => b[1] - a[1]);
}
});
function getNodePerformanceData(onlyLast: boolean = false) {
let data = onlyLast ? getLasts() : getAverages();
return Object.entries(data)
const nodePerformanceData = $derived.by(() => {
const source = showAverage ? averages : lasts;
return Object.entries(source)
.filter(([key]) => key.startsWith("node/"))
.sort((a, b) => b[1] - a[1]);
}
});
function getViewerPerformanceData(onlyLast: boolean = false) {
let data = onlyLast ? getLasts() : getAverages();
return Object.entries(data)
const viewerPerformanceData = $derived.by(() => {
const source = showAverage ? averages : lasts;
return Object.entries(source)
.filter(
([key]) =>
key !== "total-vertices" &&
@@ -110,14 +112,29 @@
viewerKeys.includes(key),
)
.sort((a, b) => b[1] - a[1]);
}
});
function getTotalPoints() {
const splitValues = $derived.by(() => {
if (showAverage) {
return [
getAverage("worker-transfer"),
getAverage("runtime"),
getAverage("update-geometries"),
];
}
return [
getLast("worker-transfer"),
getLast("runtime"),
getLast("update-geometries"),
];
});
const totalPoints = $derived.by(() => {
if (showAverage) {
return data.map((run) => {
return (
run["runtime"].reduce((acc, v) => acc + v, 0) +
run["update-geometries"].reduce((acc, v) => acc + v, 0) +
(run["runtime"]?.reduce((acc, v) => acc + v, 0) || 0) +
(run["update-geometries"]?.reduce((acc, v) => acc + v, 0) || 0) +
(run["worker-transfer"]?.reduce((acc, v) => acc + v, 0) || 0)
);
});
@@ -125,16 +142,16 @@
return data.map((run) => {
return (
run["runtime"][0] +
run["update-geometries"][0] +
(run["runtime"]?.[0] || 0) +
(run["update-geometries"]?.[0] || 0) +
(run["worker-transfer"]?.[0] || 0)
);
});
}
});
function constructPoints(key: string) {
if (key === "total") {
return getTotalPoints();
return totalPoints;
}
return data.map((run) => {
if (key in run) {
@@ -148,47 +165,33 @@
});
}
function getSplitValues(): number[] {
if (showAverage) {
return [
getAverage("worker-transfer"),
getAverage("runtime"),
getAverage("update-geometries"),
];
}
const computedTotalDisplay = $derived.by(() =>
round(showAverage ? totalPerformance.average : totalPerformance.onlyLast),
);
return [
getLast("worker-transfer"),
getLast("runtime"),
getLast("update-geometries"),
];
}
function getTitle(t: string) {
if (t.includes("/")) {
return `Node ${t.split("/").slice(-1).join("/")}`;
}
return t
.split("-")
.map((v) => v[0].toUpperCase() + v.slice(1))
.join(" ");
}
const computedFps = $derived.by(() =>
Math.floor(
1000 /
(showAverage
? totalPerformance.average || 1
: totalPerformance.onlyLast || 1),
),
);
</script>
{#key $activeType && data}
{#if $activeType === "cache-hit"}
{#if data.length !== 0}
{#if activeType === "cache-hit"}
<Monitor
title="Cache Hits"
points={constructPoints($activeType)}
points={constructPoints(activeType)}
min={0}
max={1}
type="%"
/>
{:else}
<Monitor
title={getTitle($activeType)}
points={constructPoints($activeType)}
title={getTitle(activeType)}
points={constructPoints(activeType)}
/>
{/if}
@@ -198,116 +201,108 @@
<label for="show-total">Show Average</label>
</div>
{#if data.length !== 0}
<BarSplit
labels={["worker-transfer", "runtime", "update-geometries"]}
values={getSplitValues()}
/>
<BarSplit
labels={["worker-transfer", "runtime", "update-geometries"]}
values={splitValues}
/>
<h3>General</h3>
<h3>General</h3>
<table>
<tbody>
<table>
<tbody>
<tr>
<td>
{computedTotalDisplay}<span>ms</span>
</td>
<td
class:active={activeType === "total"}
onclick={() => (activeType = "total")}
>
total<span>({computedFps}fps)</span>
</td>
</tr>
{#each performanceData as [key, value]}
<tr>
<td>
{round(getTotalPerformance(!showAverage))}<span>ms</span>
</td>
<td>{round(value)}<span>ms</span></td>
<td
class:active={$activeType === "total"}
on:click={() => ($activeType = "total")}
class:active={activeType === key}
onclick={() => (activeType = key)}
>
total<span
>({Math.floor(
1000 / getTotalPerformance(showAverage),
)}fps)</span
>
{key}
</td>
</tr>
{#each getPerformanceData(!showAverage) as [key, value]}
<tr>
<td>
{round(value)}<span>ms</span>
</td>
<td
class:active={$activeType === key}
on:click={() => ($activeType = key)}
>
{key}
</td>
</tr>
{/each}
{/each}
<tr>
<td>{data.length}</td>
<td>Samples</td>
</tr>
</tbody>
<tbody>
<tr><td><h3>Nodes</h3></td></tr>
</tbody>
<tbody>
<tr>
<td
>{showAverage ? cacheRatio.average : cacheRatio.onlyLast}<span
>%</span
></td
>
<td
class:active={activeType === "cache-hit"}
onclick={() => (activeType = "cache-hit")}
>
cache hits
</td>
</tr>
{#each nodePerformanceData as [key, value]}
<tr>
<td>{data.length}</td>
<td>Samples</td>
</tr>
</tbody>
<tbody>
<tr>
<td>
<h3>Nodes</h3>
</td>
</tr>
</tbody>
<tbody>
<tr>
<td> {getCacheRatio(!showAverage)}<span>%</span> </td>
<td>{round(value)}<span>ms</span></td>
<td
class:active={$activeType === "cache-hit"}
on:click={() => ($activeType = "cache-hit")}>cache hits</td
class:active={activeType === key}
onclick={() => (activeType = key)}
>
</tr>
{#each getNodePerformanceData(!showAverage) as [key, value]}
<tr>
<td>
{round(value)}<span>ms</span>
</td>
<td
class:active={$activeType === key}
on:click={() => ($activeType = key)}
>
{key.split("/").slice(-1).join("/")}
</td>
</tr>
{/each}
</tbody>
<tbody>
<tr>
<td>
<h3>Viewer</h3>
{key.split("/").slice(-1).join("/")}
</td>
</tr>
</tbody>
<tbody>
{/each}
</tbody>
<tbody>
<tr><td><h3>Viewer</h3></td></tr>
</tbody>
<tbody>
<tr>
<td>{humanizeNumber(getLast("total-vertices"))}</td>
<td>Vertices</td>
</tr>
<tr>
<td>{humanizeNumber(getLast("total-faces"))}</td>
<td>Faces</td>
</tr>
{#each viewerPerformanceData as [key, value]}
<tr>
<td>{humanizeNumber(getLast("total-vertices"))}</td>
<td>Vertices</td>
<td>{round(value)}<span>ms</span></td>
<td
class:active={activeType === key}
onclick={() => (activeType = key)}
>
{key.split("/").slice(-1).join("/")}
</td>
</tr>
<tr>
<td>{humanizeNumber(getLast("total-faces"))}</td>
<td>Faces</td>
</tr>
{#each getViewerPerformanceData(!showAverage) as [key, value]}
<tr>
<td>
{round(value)}<span>ms</span>
</td>
<td
class:active={$activeType === key}
on:click={() => ($activeType = key)}
>
{key.split("/").slice(-1).join("/")}
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
<p>No runs available</p>
{/if}
{/each}
</tbody>
</table>
</div>
{/key}
{:else}
<p>No runs available</p>
{/if}
<style>
h3 {

View File

@@ -1,7 +1,7 @@
<script lang="ts">
export let points: number[];
const { points }: { points: number[] } = $props();
function constructPath() {
const path = $derived.by(() => {
const max = Math.max(...points);
const min = Math.min(...points);
return points
@@ -11,13 +11,11 @@
return `${x},${y}`;
})
.join(" ");
}
});
</script>
<svg preserveAspectRatio="none" viewBox="0 0 100 100">
{#key points}
<polyline vector-effect="non-scaling-stroke" points={constructPath()} />
{/key}
<polyline vector-effect="non-scaling-stroke" points={path} />
</svg>
<style>

View File

@@ -1,25 +1,23 @@
<script lang="ts">
import { humanizeDuration, humanizeNumber } from "$lib/helpers";
import localStore from "$lib/helpers/localStore";
import { localState } from "$lib/helpers/localState.svelte";
import SmallGraph from "./SmallGraph.svelte";
import type { PerformanceData, PerformanceStore } from "@nodarium/utils";
export let store: PerformanceStore;
const { store, fps }: { store: PerformanceStore; fps: number[] } = $props();
const open = localStore("node.performance.small.open", {
const open = localState("node.performance.small.open", {
runtime: false,
fps: false,
});
$: vertices = $store?.at(-1)?.["total-vertices"]?.[0] || 0;
$: faces = $store?.at(-1)?.["total-faces"]?.[0] || 0;
$: runtime = $store?.at(-1)?.["runtime"]?.[0] || 0;
const vertices = $derived($store?.at(-1)?.["total-vertices"]?.[0] || 0);
const faces = $derived($store?.at(-1)?.["total-faces"]?.[0] || 0);
const runtime = $derived($store?.at(-1)?.["runtime"]?.[0] || 0);
function getPoints(data: PerformanceData, key: string) {
return data?.map((run) => run[key]?.[0] || 0) || [];
}
export let fps: number[] = [];
</script>
<div class="wrapper">
@@ -27,12 +25,12 @@
<tbody>
<tr
style="cursor:pointer;"
on:click={() => ($open.runtime = !$open.runtime)}
onclick={() => (open.value.runtime = !open.value.runtime)}
>
<td>{$open.runtime ? "-" : "+"} runtime </td>
<td>{open.value.runtime ? "-" : "+"} runtime </td>
<td>{humanizeDuration(runtime || 1000)}</td>
</tr>
{#if $open.runtime}
{#if open.value.runtime}
<tr>
<td colspan="2">
<SmallGraph points={getPoints($store, "runtime")} />
@@ -40,13 +38,16 @@
</tr>
{/if}
<tr style="cursor:pointer;" on:click={() => ($open.fps = !$open.fps)}>
<td>{$open.fps ? "-" : "+"} fps </td>
<tr
style="cursor:pointer;"
onclick={() => (open.value.fps = !open.value.fps)}
>
<td>{open.value.fps ? "-" : "+"} fps </td>
<td>
{Math.floor(fps[fps.length - 1])}fps
</td>
</tr>
{#if $open.fps}
{#if open.value.fps}
<tr>
<td colspan="2">
<SmallGraph points={fps} />

View File

@@ -35,6 +35,9 @@
scene = $bindable(),
}: Props = $props();
let geometries = $state.raw<BufferGeometry[]>([]);
let center = $state(new Vector3(0, 4, 0));
useTask(
(delta) => {
fps.push(1 / delta);
@@ -45,11 +48,13 @@
export const invalidate = function () {
if (scene) {
geometries = scene.children
.filter((child) => "geometry" in child && child.isObject3D)
.map((child) => {
return (child as Mesh).geometry;
});
const geos: BufferGeometry[] = [];
scene.traverse(function (child) {
if (isMesh(child)) {
geos.push(child.geometry);
}
});
geometries = geos;
}
if (geometries && scene && centerCamera) {
@@ -62,9 +67,6 @@
_invalidate();
};
let geometries = $state<BufferGeometry[]>();
let center = $state(new Vector3(0, 4, 0));
function isMesh(child: Mesh | any): child is Mesh {
return child.isObject3D && "material" in child;
}
@@ -76,7 +78,7 @@
$effect(() => {
const wireframe = appSettings.value.debug.wireframe;
scene.traverse(function (child) {
if (isMesh(child) && isMatCapMaterial(child.material)) {
if (isMesh(child) && isMatCapMaterial(child.material) && child.visible) {
child.material.wireframe = wireframe;
}
});
@@ -90,6 +92,13 @@
geo.attributes.position.array[i + 2],
] as Vector3Tuple;
}
// $effect(() => {
// console.log({
// geometries: $state.snapshot(geometries),
// indices: appSettings.value.debug.showIndices,
// });
// });
</script>
<Camera {center} {centerCamera} />

View File

@@ -11,7 +11,7 @@ import {
} from "three";
function fastArrayHash(arr: Int32Array) {
const sampleDistance = Math.max(Math.floor(arr.length / 100), 1);
const sampleDistance = Math.max(Math.floor(arr.length / 1000), 1);
const sampleCount = Math.floor(arr.length / sampleDistance);
let hash = new Int32Array(sampleCount);
@@ -40,6 +40,9 @@ export function createGeometryPool(parentScene: Group, material: Material) {
let hash = fastArrayHash(data);
let geometry = existingMesh ? existingMesh.geometry : new BufferGeometry();
if (existingMesh) {
existingMesh.visible = true;
}
// Extract data from the encoded array
// const geometryType = encodedData[index++];
@@ -121,7 +124,6 @@ export function createGeometryPool(parentScene: Group, material: Material) {
updateSingleGeometry(input, existingMesh || null);
} else if (existingMesh) {
existingMesh.visible = false;
scene.remove(existingMesh);
}
}
return { totalVertices, totalFaces };
@@ -258,7 +260,6 @@ export function createInstancedGeometryPool(
updateSingleInstance(input, existingMesh || null);
} else if (existingMesh) {
existingMesh.visible = false;
scene.remove(existingMesh);
}
}
return { totalVertices, totalFaces };

View File

@@ -1,19 +1,33 @@
import { type SyncCache } from "@nodarium/types";
export class MemoryRuntimeCache implements SyncCache {
private map = new Map<string, unknown>();
size: number;
private cache: [string, unknown][] = [];
size = 50;
constructor(size = 50) {
this.size = size;
}
get<T>(key: string): T | undefined {
return this.cache.find(([k]) => k === key)?.[1] as T;
}
set<T>(key: string, value: T): void {
this.cache.push([key, value]);
this.cache = this.cache.slice(-this.size);
}
clear(): void {
this.cache = [];
if (!this.map.has(key)) return undefined;
const value = this.map.get(key) as T;
this.map.delete(key);
this.map.set(key, value);
return value;
}
set<T>(key: string, value: T): void {
if (this.map.has(key)) {
this.map.delete(key);
}
this.map.set(key, value);
while (this.map.size > this.size) {
const oldestKey = this.map.keys().next().value as string;
this.map.delete(oldestKey);
}
}
clear(): void {
this.map.clear();
}
}

View File

@@ -1,6 +1,5 @@
import type {
Graph,
Node,
NodeDefinition,
NodeInput,
NodeRegistry,
@@ -14,6 +13,7 @@ import {
fastHashArrayBuffer,
type PerformanceStore,
} from "@nodarium/utils";
import type { RuntimeNode } from "./types";
const log = createLogger("runtime-executor");
log.mute();
@@ -64,7 +64,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
constructor(
private registry: NodeRegistry,
private cache?: SyncCache<Int32Array>,
public cache?: SyncCache<Int32Array>,
) {
this.cache = undefined;
}
@@ -92,18 +92,27 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// First, lets check if all nodes have a definition
this.definitionMap = await this.getNodeDefinitions(graph);
const outputNode = graph.nodes.find((node) =>
const graphNodes = graph.nodes.map(node => {
const n = node as RuntimeNode;
n.state = {
depth: 0,
children: [],
parents: [],
inputNodes: {},
}
return n
})
const outputNode = graphNodes.find((node) =>
node.type.endsWith("/output"),
) as Node;
);
if (!outputNode) {
throw new Error("No output node found");
}
outputNode.tmp = outputNode.tmp || {};
outputNode.tmp.depth = 0;
const nodeMap = new Map<number, Node>(
graph.nodes.map((node) => [node.id, node]),
const nodeMap = new Map(
graphNodes.map((node) => [node.id, node]),
);
// loop through all edges and assign the parent and child nodes to each node
@@ -112,14 +121,9 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
const parent = nodeMap.get(parentId);
const child = nodeMap.get(childId);
if (parent && child) {
parent.tmp = parent.tmp || {};
parent.tmp.children = parent.tmp.children || [];
parent.tmp.children.push(child);
child.tmp = child.tmp || {};
child.tmp.parents = child.tmp.parents || [];
child.tmp.parents.push(parent);
child.tmp.inputNodes = child.tmp.inputNodes || {};
child.tmp.inputNodes[childInput] = parent;
parent.state.children.push(child);
child.state.parents.push(parent);
child.state.inputNodes[childInput] = parent;
}
}
@@ -130,20 +134,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
while (stack.length) {
const node = stack.pop();
if (!node) continue;
node.tmp = node.tmp || {};
if (node?.tmp?.depth === undefined) {
node.tmp.depth = 0;
}
if (node?.tmp?.parents !== undefined) {
for (const parent of node.tmp.parents) {
parent.tmp = parent.tmp || {};
if (parent.tmp?.depth === undefined) {
parent.tmp.depth = node.tmp.depth + 1;
stack.push(parent);
} else {
parent.tmp.depth = Math.max(parent.tmp.depth, node.tmp.depth + 1);
}
}
for (const parent of node.state.parents) {
parent.state = parent.state || {};
parent.state.depth = node.state.depth + 1;
stack.push(parent);
}
nodes.push(node);
}
@@ -175,7 +169,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// we execute the nodes from the bottom up
const sortedNodes = nodes.sort(
(a, b) => (b.tmp?.depth || 0) - (a.tmp?.depth || 0),
(a, b) => (b.state?.depth || 0) - (a.state?.depth || 0),
);
// here we store the intermediate results of the nodes
@@ -188,7 +182,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
for (const node of sortedNodes) {
const node_type = this.definitionMap.get(node.type)!;
if (!node_type || !node.tmp || !node_type.execute) {
if (!node_type || !node.state || !node_type.execute) {
log.warn(`Node ${node.id} has no definition`);
continue;
}
@@ -208,7 +202,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
}
// check if the input is connected to another node
const inputNode = node.tmp?.inputNodes?.[key];
const inputNode = node.state.inputNodes[key];
if (inputNode) {
if (results[inputNode.id] === undefined) {
throw new Error(
@@ -250,13 +244,14 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
}
this.perf?.addPoint("cache-hit", 0);
log.group(`executing ${node_type.id || node.id}`);
log.group(`executing ${node_type.id}-${node.id}`);
log.log(`Inputs:`, inputs);
a = performance.now();
results[node.id] = node_type.execute(encoded_inputs);
log.log("Executed", node.type, node.id)
b = performance.now();
if (this.cache) {
if (this.cache && node.id !== outputNode.id) {
this.cache.set(inputHash, results[node.id]);
}

View File

@@ -0,0 +1,10 @@
import type { SerializedNode } from "@nodarium/types";
type RuntimeState = {
depth: number
parents: RuntimeNode[],
children: RuntimeNode[],
inputNodes: Record<string, RuntimeNode>
}
export type RuntimeNode = SerializedNode & { state: RuntimeState }

View File

@@ -2,15 +2,33 @@ import { MemoryRuntimeExecutor } from "./runtime-executor";
import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry";
import type { Graph } from "@nodarium/types";
import { createPerformanceStore } from "@nodarium/utils";
import { MemoryRuntimeCache } from "./runtime-executor-cache";
const indexDbCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry("", indexDbCache);
const executor = new MemoryRuntimeExecutor(nodeRegistry);
const cache = new MemoryRuntimeCache()
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
const performanceStore = createPerformanceStore();
executor.perf = performanceStore;
export async function setUseRegistryCache(useCache: boolean) {
if (useCache) {
nodeRegistry.cache = indexDbCache;
} else {
nodeRegistry.cache = undefined;
}
}
export async function setUseRuntimeCache(useCache: boolean) {
if (useCache) {
executor.cache = cache;
} else {
executor.cache = undefined;
}
}
export async function executeGraph(
graph: Graph,
settings: Record<string, unknown>,

View File

@@ -11,5 +11,11 @@ export class WorkerRuntimeExecutor implements RuntimeExecutor {
async getPerformanceData() {
return this.worker.getPerformanceData();
}
set useRuntimeCache(useCache: boolean) {
this.worker.setUseRuntimeCache(useCache);
}
set useRegistryCache(useCache: boolean) {
this.worker.setUseRegistryCache(useCache);
}
}

View File

@@ -54,7 +54,7 @@ export const AppSettingTypes = {
},
useWorker: {
type: "boolean",
label: "Execute runtime in worker",
label: "Execute in WebWorker",
value: true,
},
showIndices: {
@@ -87,6 +87,19 @@ export const AppSettingTypes = {
label: "Show Graph Source",
value: false,
},
cache: {
title: "Cache",
useRuntimeCache: {
type: "boolean",
label: "Node Results",
value: true,
},
useRegistryCache: {
type: "boolean",
label: "Node Source",
value: true,
},
},
stressTest: {
title: "Stress Test",
amount: {

View File

@@ -1,43 +1,46 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Readable } from "svelte/store";
import { getContext, type Snippet } from "svelte";
import type { PanelState } from "./PanelState.svelte";
export let id: string;
export let icon: string = "";
export let title = "";
export let classes = "";
export let hidden: boolean | undefined = undefined;
const {
id,
icon = "",
title = "",
classes = "",
hidden,
children,
} = $props<{
id: string;
icon?: string;
title?: string;
classes?: string;
hidden?: boolean;
children?: Snippet;
}>();
const setVisibility =
getContext<(id: string, visible: boolean) => void>("setVisibility");
const panelState = getContext<PanelState>("panel-state");
$: if (typeof hidden === "boolean") {
setVisibility(id, !hidden);
}
const registerPanel =
getContext<
(id: string, icon: string, classes: string) => Readable<boolean>
>("registerPanel");
let visible = registerPanel(id, icon, classes);
const panel = panelState.registerPanel(id, icon, classes, hidden);
$effect(() => {
panel.hidden = hidden;
});
</script>
{#if $visible}
{#if panelState.activePanel.value === id}
<div class="wrapper" class:hidden>
{#if title}
<header>
<h3>{title}</h3>
</header>
{/if}
<slot />
{@render children?.()}
</div>
{/if}
<style>
header {
border-bottom: solid thin var(--outline);
height: 69px;
height: 70px;
display: flex;
align-items: center;
padding-left: 1em;

View File

@@ -0,0 +1,35 @@
import { localState } from "$lib/helpers/localState.svelte";
type Panel = {
icon: string;
classes: string;
hidden?: boolean;
}
export class PanelState {
panels = $state<Record<string, Panel>>({});
activePanel = localState<string | boolean>("node.activePanel", "")
get keys() {
return Object.keys(this.panels);
}
public registerPanel(id: string, icon: string, classes: string, hidden: boolean): Panel {
const state = $state({
icon: icon,
classes: classes,
hidden: hidden,
});
this.panels[id] = state;
return state;
}
public toggleOpen() {
if (this.activePanel.value) {
this.activePanel.value = false;
} else {
this.activePanel.value = this.keys[0]
}
}
}

View File

@@ -1,77 +1,35 @@
<script lang="ts">
import localStore from "$lib/helpers/localStore";
import { setContext } from "svelte";
import { derived } from "svelte/store";
import { setContext, type Snippet } from "svelte";
import { PanelState } from "./PanelState.svelte";
let panels: Record<
string,
{
icon: string;
id: string;
classes: string;
visible?: boolean;
}
> = {};
const state = new PanelState();
setContext("panel-state", state);
const activePanel = localStore<keyof typeof panels | false>(
"nodes.settings.activePanel",
false,
);
$: keys = panels
? (Object.keys(panels) as unknown as (keyof typeof panels)[]).filter(
(key) => !!panels[key]?.id,
)
: [];
setContext("setVisibility", (id: string, visible: boolean) => {
panels[id].visible = visible;
panels = { ...panels };
});
setContext("registerPanel", (id: string, icon: string, classes: string) => {
panels[id] = { id, icon, classes };
return derived(activePanel, ($activePanel) => {
return $activePanel === id;
});
});
function setActivePanel(panel: keyof typeof panels | false) {
if (panel === $activePanel) {
$activePanel = false;
} else if (panel) {
$activePanel = panel;
} else {
$activePanel = false;
}
}
const { children } = $props<{ children?: Snippet }>();
</script>
<div class="wrapper" class:visible={$activePanel}>
<div class="wrapper" class:visible={state.activePanel.value}>
<div class="tabs">
<button
aria-label="Close"
on:click={() => {
setActivePanel($activePanel ? false : keys[0]);
}}
>
<span class="absolute i-tabler-chevron-left w-6 h-6 block"></span>
<button aria-label="Close" onclick={() => state.toggleOpen()}>
<span class="icon-[tabler--settings]"></span>
<span class="absolute i-[tabler--chevron-left] w-6 h-6 block"></span>
</button>
{#each keys as panel (panels[panel].id)}
{#if panels[panel].visible !== false}
{#each state.keys as panelId (panelId)}
{#if !state.panels[panelId].hidden}
<button
aria-label={panel}
class="tab {panels[panel].classes}"
class:active={panel === $activePanel}
on:click={() => setActivePanel(panel)}
aria-label={panelId}
class="tab {state.panels[panelId].classes}"
class:active={panelId === state.activePanel.value}
onclick={() => (state.activePanel.value = panelId)}
>
<span class={`block w-6 h-6 ${panels[panel].icon}`}></span>
<span class={`block w-6 h-6 iconify ${state.panels[panelId].icon}`}
></span>
</button>
{/if}
{/each}
</div>
<div class="content">
<slot />
{@render children?.()}
</div>
</div>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import type { Node, NodeInput } from "@nodarium/types";
import type { NodeInstance, NodeInput } from "@nodarium/types";
import NestedSettings from "$lib/settings/NestedSettings.svelte";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
type Props = {
manager: GraphManager;
node: Node;
node: NodeInstance;
};
const { manager, node = $bindable() }: Props = $props();
@@ -19,19 +19,19 @@
})
.map(([key, value]) => {
//@ts-ignore
value.__node_type = node?.tmp?.type.id;
value.__node_type = node.state?.type.id;
//@ts-ignore
value.__node_input = key;
return [key, value];
}),
);
}
const nodeDefinition = filterInputs(node.tmp?.type?.inputs);
const nodeDefinition = filterInputs(node.state?.type?.inputs);
type Store = Record<string, number | number[]>;
let store = $state<Store>(createStore(node?.props, nodeDefinition));
function createStore(
props: Node["props"],
props: NodeInstance["props"],
inputs: Record<string, NodeInput>,
): Store {
const store: Store = {};
@@ -64,6 +64,7 @@
lastPropsHash = propsHash;
if (needsUpdate) {
manager.save();
manager.execute();
}
}

View File

@@ -1,24 +1,24 @@
<script lang="ts">
import type { Node } from "@nodarium/types";
import type { NodeInstance } from "@nodarium/types";
import type { GraphManager } from "$lib/graph-interface/graph-manager.svelte";
import ActiveNodeSelected from "./ActiveNodeSelected.svelte";
type Props = {
manager: GraphManager;
node: Node | undefined;
node: NodeInstance | undefined;
};
const { manager, node }: Props = $props();
let { manager, node = $bindable() }: Props = $props();
</script>
{#if node}
{#key node.id}
{#if node}
<ActiveNodeSelected {manager} {node} />
<ActiveNodeSelected {manager} bind:node />
{:else}
<p class="mx-4">Active Node has no Settings</p>
{/if}
{/key}
{:else}
<p class="mx-4">No active node</p>
<p class="mx-4">No node selected</p>
{/if}

View File

@@ -1,9 +1,15 @@
<script lang="ts" module>
let result:
| { stdev: number; avg: number; duration: number; samples: number[] }
| undefined = $state();
</script>
<script lang="ts">
import localStore from "$lib/helpers/localStore";
import { Integer } from "@nodarium/ui";
import { writable } from "svelte/store";
import { humanizeDuration } from "$lib/helpers";
import Monitor from "$lib/performance/Monitor.svelte";
import { localState } from "$lib/helpers/localState.svelte";
function calculateStandardDeviation(array: number[]) {
const n = array.length;
@@ -12,18 +18,18 @@
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n,
);
}
type Props = {
run: () => Promise<any>;
};
export let run: () => Promise<any>;
const { run }: Props = $props();
let isRunning = false;
let amount = localStore<number>("nodes.benchmark.samples", 500);
let samples = 0;
let isRunning = $state(false);
let amount = localState<number>("nodes.benchmark.samples", 500);
let samples = $state(0);
let warmUp = writable(0);
let warmUpAmount = 10;
let state = "";
let result:
| { stdev: number; avg: number; duration: number; samples: number[] }
| undefined;
let status = "";
const copyContent = async (text?: string | number) => {
if (!text) return;
@@ -56,7 +62,7 @@
let results = [];
// perform run
for (let i = 0; i < $amount; i++) {
for (let i = 0; i < amount.value; i++) {
const a = performance.now();
await run();
samples = i;
@@ -73,55 +79,53 @@
}
</script>
{state}
{status}
<div class="wrapper" class:running={isRunning}>
{#if isRunning}
{#if result}
<h3>Finished ({humanizeDuration(result.duration)})</h3>
<div class="monitor-wrapper">
<Monitor points={result.samples} />
</div>
<label for="bench-avg">Average </label>
<button
id="bench-avg"
on:keydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)}
on:click={() => copyContent(result?.avg)}
>{Math.floor(result.avg * 100) / 100}</button
>
<i
role="button"
tabindex="0"
on:keydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)}
on:click={() => copyContent(result?.avg)}>(click to copy)</i
>
<label for="bench-stdev">Standard Deviation σ</label>
<button id="bench-stdev" on:click={() => copyContent(result?.stdev)}
>{Math.floor(result.stdev * 100) / 100}</button
>
<i
role="button"
tabindex="0"
on:keydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)}
on:click={() => copyContent(result?.stdev + "")}>(click to copy)</i
>
<div>
<button on:click={() => (isRunning = false)}>reset</button>
</div>
{:else}
<p>WarmUp ({$warmUp}/{warmUpAmount})</p>
<progress value={$warmUp} max={warmUpAmount}
>{Math.floor(($warmUp / warmUpAmount) * 100)}%</progress
>
<p>Progress ({samples}/{$amount})</p>
<progress value={samples} max={$amount}
>{Math.floor((samples / $amount) * 100)}%</progress
>
{/if}
{#if result}
<h3>Finished ({humanizeDuration(result.duration)})</h3>
<div class="monitor-wrapper">
<Monitor points={result.samples} />
</div>
<label for="bench-avg">Average </label>
<button
id="bench-avg"
onkeydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)}
onclick={() => copyContent(result?.avg)}
>{Math.floor(result.avg * 100) / 100}</button
>
<i
role="button"
tabindex="0"
onkeydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)}
onclick={() => copyContent(result?.avg)}>(click to copy)</i
>
<label for="bench-stdev">Standard Deviation σ</label>
<button id="bench-stdev" onclick={() => copyContent(result?.stdev)}
>{Math.floor(result.stdev * 100) / 100}</button
>
<i
role="button"
tabindex="0"
onkeydown={(ev) => ev.key === "Enter" && copyContent(result?.avg)}
onclick={() => copyContent(result?.stdev + "")}>(click to copy)</i
>
<div>
<button onclick={() => (isRunning = false)}>reset</button>
</div>
{:else if isRunning}
<p>WarmUp ({$warmUp}/{warmUpAmount})</p>
<progress value={$warmUp} max={warmUpAmount}
>{Math.floor(($warmUp / warmUpAmount) * 100)}%</progress
>
<p>Progress ({samples}/{amount.value})</p>
<progress value={samples} max={amount.value}
>{Math.floor((samples / amount.value) * 100)}%</progress
>
{:else}
<label for="bench-samples">Samples</label>
<Integer id="bench-sample" bind:value={$amount} max={1000} />
<button on:click={benchmark} disabled={isRunning}> start </button>
<Integer id="bench-sample" bind:value={amount.value} max={1000} />
<button onclick={benchmark} disabled={isRunning}> start </button>
{/if}
</div>

View File

@@ -15,7 +15,7 @@
FileSaver.saveAs(blob, name + "." + extension);
};
export let scene: Group;
const { scene } = $props<{ scene: Group }>();
let gltfExporter: GLTFExporter;
async function exportGltf() {
@@ -53,7 +53,7 @@
}
</script>
<div class="p-2">
<button on:click={exportObj}> export obj </button>
<button on:click={exportGltf}> export gltf </button>
<div class="p-4">
<button onclick={exportObj}> export obj </button>
<button onclick={exportGltf}> export gltf </button>
</div>

View File

@@ -13,32 +13,34 @@
let { keymaps }: Props = $props();
</script>
<table class="wrapper">
<tbody>
{#each keymaps as keymap}
<tr>
<td colspan="2">
<h3>{keymap.title}</h3>
</td>
</tr>
{#each get(keymap.keymap?.keys) as key}
<div class="p-4">
<table class="wrapper">
<tbody>
{#each keymaps as keymap}
<tr>
{#if key.description}
<td class="command-wrapper">
<ShortCut
alt={key.alt}
ctrl={key.ctrl}
shift={key.shift}
key={key.key}
/>
</td>
<td>{key.description}</td>
{/if}
<td colspan="2">
<h3>{keymap.title}</h3>
</td>
</tr>
{#each get(keymap.keymap?.keys) as key}
<tr>
{#if key.description}
<td class="command-wrapper">
<ShortCut
alt={key.alt}
ctrl={key.ctrl}
shift={key.shift}
key={key.key}
/>
</td>
<td>{key.description}</td>
{/if}
</tr>
{/each}
{/each}
{/each}
</tbody>
</table>
</tbody>
</table>
</div>
<style>
.wrapper {

View File

@@ -1,7 +1,15 @@
<script lang="ts">
import "@nodarium/ui/app.css";
import "virtual:uno.css";
import "@unocss/reset/normalize.css";
import "../app.css";
import type { Snippet } from "svelte";
import * as config from "$lib/config";
const { children } = $props<{ children?: Snippet }>();
</script>
<slot />
{@render children?.()}
<svelte:head>
{#if config.ANALYTIC_SCRIPT}
{@html config.ANALYTIC_SCRIPT}
{/if}
</svelte:head>

View File

@@ -2,7 +2,7 @@
import Grid from "$lib/grid";
import GraphInterface from "$lib/graph-interface";
import * as templates from "$lib/graph-templates";
import type { Graph, Node } from "@nodarium/types";
import type { Graph, NodeInstance } from "@nodarium/types";
import Viewer from "$lib/result-viewer/Viewer.svelte";
import {
appSettings,
@@ -42,7 +42,26 @@
appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime,
);
let activeNode = $state<Node | undefined>(undefined);
$effect(() => {
workerRuntime.useRegistryCache =
appSettings.value.debug.cache.useRuntimeCache;
workerRuntime.useRuntimeCache =
appSettings.value.debug.cache.useRegistryCache;
if (appSettings.value.debug.cache.useRegistryCache) {
nodeRegistry.cache = registryCache;
} else {
nodeRegistry.cache = undefined;
}
if (appSettings.value.debug.cache.useRuntimeCache) {
memoryRuntime.cache = runtimeCache;
} else {
memoryRuntime.cache = undefined;
}
});
let activeNode = $state<NodeInstance | undefined>(undefined);
let scene = $state<Group>(null!);
let graph = $state(
@@ -88,13 +107,10 @@
randomSeed: { type: "boolean", value: false },
});
let runIndex = 0;
async function update(
g: Graph,
s: Record<string, any> = $state.snapshot(graphSettings),
) {
runIndex++;
performanceStore.startRun();
try {
let a = performance.now();
@@ -181,7 +197,7 @@
onsave={(graph) => handleSave(graph)}
/>
<Sidebar>
<Panel id="general" title="General" icon="i-tabler-settings">
<Panel id="general" title="General" icon="i-[tabler--settings]">
<NestedSettings
id="general"
bind:value={appSettings.value}
@@ -191,7 +207,7 @@
<Panel
id="shortcuts"
title="Keyboard Shortcuts"
icon="i-tabler-keyboard"
icon="i-[tabler--keyboard]"
>
<Keymap
keymaps={[
@@ -200,23 +216,21 @@
]}
/>
</Panel>
<Panel id="exports" title="Exporter" icon="i-tabler-package-export">
<Panel id="exports" title="Exporter" icon="i-[tabler--package-export]">
<ExportSettings {scene} />
</Panel>
<Panel
id="node-store"
classes="text-green-400"
title="Node Store"
icon="i-tabler-database"
icon="i-[tabler--database] bg-green-400"
>
<NodeStore registry={nodeRegistry} />
</Panel>
<Panel
id="performance"
title="Performance"
classes="text-red-400"
hidden={!appSettings.value.debug.showPerformancePanel}
icon="i-tabler-brand-speedtest"
icon="i-[tabler--brand-speedtest] bg-red-400"
>
{#if $performanceStore}
<PerformanceViewer data={$performanceStore} />
@@ -226,24 +240,22 @@
id="graph-source"
title="Graph Source"
hidden={!appSettings.value.debug.showGraphJson}
icon="i-tabler-code"
icon="i-[tabler--code]"
>
<GraphSource {graph} />
<GraphSource graph={graph && manager.serialize()} />
</Panel>
<Panel
id="benchmark"
title="Benchmark"
classes="text-red-400"
hidden={!appSettings.value.debug.showBenchmarkPanel}
icon="i-tabler-graph"
icon="i-[tabler--graph] bg-red-400"
>
<BenchmarkPanel run={randomGenerate} />
</Panel>
<Panel
id="graph-settings"
title="Graph Settings"
classes="text-blue-400"
icon="i-custom-graph"
icon="i-[custom--graph] bg-blue-400"
>
<NestedSettings
id="graph-settings"
@@ -254,10 +266,9 @@
<Panel
id="active-node"
title="Node Settings"
classes="text-blue-400"
icon="i-tabler-adjustments"
icon="i-[tabler--adjustments] bg-blue-400"
>
<ActiveNodeSettings {manager} node={activeNode} />
<ActiveNodeSettings {manager} bind:node={activeNode} />
</Panel>
</Sidebar>
</Grid.Cell>

View File

@@ -0,0 +1,8 @@
<script lang="ts">
import type { Snippet } from "svelte";
const { children } = $props<{ children?: Snippet }>();
</script>
<main class="w-screen overflow-x-hidden">
{@render children()}
</main>

View File

@@ -0,0 +1,120 @@
<script lang="ts">
import NodeHTML from "$lib/graph-interface/node/NodeHTML.svelte";
import { localState } from "$lib/helpers/localState.svelte";
import Panel from "$lib/sidebar/Panel.svelte";
import Sidebar from "$lib/sidebar/Sidebar.svelte";
import { IndexDBCache, RemoteNodeRegistry } from "@nodarium/registry";
import { type NodeId, type NodeInstance } from "@nodarium/types";
import Code from "./Code.svelte";
import Grid from "$lib/grid";
import {
concatEncodedArrays,
createWasmWrapper,
encodeNestedArray,
} from "@nodarium/utils";
const registryCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry("", registryCache);
let activeNode = localState<NodeId | undefined>(
"node.dev.activeNode",
undefined,
);
let nodeWasm = $state<ArrayBuffer>();
let nodeInstance = $state<NodeInstance>();
let nodeWasmWrapper = $state<ReturnType<typeof createWasmWrapper>>();
async function fetchNodeData(nodeId?: NodeId) {
console.log("FETCHING", { nodeId });
nodeWasm = undefined;
nodeInstance = undefined;
if (!nodeId) return;
const data = await nodeRegistry.fetchNodeDefinition(nodeId);
nodeWasm = await nodeRegistry.fetchArrayBuffer("nodes/" + nodeId + ".wasm");
nodeInstance = {
id: 0,
type: nodeId,
position: [0, 0] as [number, number],
props: {},
state: {
type: data,
},
};
nodeWasmWrapper = createWasmWrapper(nodeWasm);
}
$effect(() => {
fetchNodeData(activeNode.value);
});
$effect(() => {
if (nodeInstance?.props && nodeWasmWrapper) {
const keys = Object.keys(nodeInstance.state.type?.inputs || {});
let ins = Object.values(nodeInstance.props) as number[];
if (keys[0] === "plant") {
ins = [[0, 0, 0, 0, 0, 0, 0, 0], ...ins];
}
const inputs = concatEncodedArrays(encodeNestedArray(ins));
nodeWasmWrapper?.execute(inputs);
}
});
</script>
<div class="node-wrapper absolute bottom-8 left-8">
{#if nodeInstance}
<NodeHTML inView position="relative" z={5} bind:node={nodeInstance} />
{/if}
</div>
<Grid.Row>
<Grid.Cell>
<pre>
<code>
{JSON.stringify(nodeInstance?.props)}
</code>
</pre>
</Grid.Cell>
<Grid.Cell>
<div class="h-screen w-[80vw] overflow-y-auto">
{#if nodeWasm}
<Code wasm={nodeWasm} />
{/if}
</div>
</Grid.Cell>
</Grid.Row>
<Sidebar>
<Panel
id="node-store"
classes="text-green-400"
title="Node Store"
icon="i-[tabler--database]"
>
<div class="p-4 flex flex-col gap-2">
{#await nodeRegistry.fetchCollection("max/plantarium")}
<p>Loading Nodes...</p>
{:then result}
{#each result.nodes as n}
<button
class="cursor-pointer p-2 bg-layer-1 {activeNode.value === n.id
? 'outline outline-offset-1'
: ''}"
onclick={() => (activeNode.value = n.id)}>{n.id}</button
>
{/each}
{/await}
</div>
</Panel>
</Sidebar>
<style>
:global body {
height: 100vh;
width: 100vw;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import wabtInit from "wabt";
const { wasm } = $props<{ wasm: ArrayBuffer }>();
async function toWat(arrayBuffer: ArrayBuffer) {
const wabt = await wabtInit();
const module = wabt.readWasm(new Uint8Array(arrayBuffer), {
readDebugNames: true,
});
module.generateNames();
module.applyNames();
return module.toText({ foldExprs: false, inlineExport: false });
}
</script>
{#await toWat(wasm)}
<p>Converting to WAT</p>
{:then c}
<pre>
<code class="text-gray-50">{c}</code>
</pre>
{/await}

View File

@@ -1,20 +0,0 @@
// uno.config.ts
import { defineConfig } from 'unocss'
import presetIcons from '@unocss/preset-icons'
import { presetUno } from 'unocss'
import fs from 'fs'
const icons = Object.fromEntries(fs.readdirSync('./src/lib/icons')
.map(name => [name.replace(".svg", ""), fs.readFileSync(`./src/lib/icons/${name}`, 'utf-8')]))
export default defineConfig({
presets: [
presetUno(),
presetIcons({
collections: {
custom: icons
}
}),
]
})

View File

@@ -1,14 +1,14 @@
import { sveltekit } from '@sveltejs/kit/vite';
import UnoCSS from 'unocss/vite';
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite'
import comlink from 'vite-plugin-comlink';
import glsl from "vite-plugin-glsl";
import wasm from "vite-plugin-wasm";
export default defineConfig({
plugins: [
tailwindcss(),
comlink(),
UnoCSS(),
sveltekit(),
glsl(),
wasm()

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1768564909,
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

40
flake.nix Normal file
View File

@@ -0,0 +1,40 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = {nixpkgs, ...}: let
systems = ["aarch64-darwin" "x86_64-linux"];
eachSystem = function:
nixpkgs.lib.genAttrs systems (system:
function {
inherit system;
pkgs = nixpkgs.legacyPackages.${system};
});
in {
devShells = eachSystem ({pkgs, ...}: {
default = pkgs.mkShellNoCC {
packages = [
# general deps
pkgs.nodejs_24
pkgs.pnpm_10
# wasm/rust stuff
pkgs.rustc
pkgs.cargo
pkgs.rust-analyzer
pkgs.rustfmt
pkgs.wasm-bindgen-cli
pkgs.wasm-pack
pkgs.lld
# frontend
pkgs.vscode-langservers-extracted
pkgs.typescript-language-server
pkgs.prettier
pkgs.tailwindcss-language-server
];
};
});
};
}

View File

@@ -11,18 +11,8 @@ crate-type = ["cdylib", "rlib"]
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.
utils = { version = "0.1.0", path = "../../../../packages/utils" }
macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/out.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,22 +7,6 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/box.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,18 +1,15 @@
use nodarium_macros::include_definition_file;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{
concat_args, encode_float, evaluate_float, geometry::calculate_normals, log, set_panic_hook,
encode_float, evaluate_float, geometry::calculate_normals,log,
split_args, wrap_arg,
};
use wasm_bindgen::prelude::*;
include_definition_file!("src/input.json");
nodarium_definition_file!("src/input.json");
#[rustfmt::skip]
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(input);
log!("WASM(cube): input: {:?} -> {:?}", input, args);
@@ -22,7 +19,6 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
let p = encode_float(size);
let n = encode_float(-size);
// [[1,3, x, y, z, x, y,z,x,y,z]];
let mut cube_geometry = [

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,23 +7,6 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
glam = "0.27.0"
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/branch.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,20 +1,19 @@
use nodarium_macros::include_definition_file;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{
concat_arg_vecs, evaluate_float, evaluate_int,
geometry::{
create_path, interpolate_along_path, rotate_vector_by_angle, wrap_path, wrap_path_mut,
},
log, set_panic_hook, split_args,
log, split_args,
};
use std::f32::consts::PI;
use wasm_bindgen::prelude::*;
include_definition_file!("src/input.json");
nodarium_definition_file!("src/input.json");
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(input);
let paths = split_args(args[0]);

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,21 +7,6 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
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"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/float.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,9 +1,9 @@
use nodarium_macros::include_definition_file;
use wasm_bindgen::prelude::*;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
include_definition_file!("src/input.json");
nodarium_definition_file!("src/input.json");
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(args: &[i32]) -> Vec<i32> {
args.into()
}

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,24 +7,7 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
noise = "0.9.0"
glam = "0.27.0"
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
glam = "0.30.10"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/gravity.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,22 +1,20 @@
use glam::Vec3;
use nodarium_macros::include_definition_file;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{
concat_args, evaluate_float, evaluate_int,
geometry::{wrap_path, wrap_path_mut},
log, reset_call_count, set_panic_hook, split_args,
log, reset_call_count, split_args,
};
use wasm_bindgen::prelude::*;
include_definition_file!("src/input.json");
nodarium_definition_file!("src/input.json");
fn lerp_vec3(a: Vec3, b: Vec3, t: f32) -> Vec3 {
a + (b - a) * t
}
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
reset_call_count();
let args = split_args(input);

View File

@@ -1,13 +0,0 @@
//! 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

@@ -1,5 +1,5 @@
[package]
name = "nodarium_instance"
name = "instance"
version = "0.1.0"
authors = ["Max Richter <jim-x@web.de>"]
edition = "2018"
@@ -7,23 +7,7 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
glam = "0.27.0"
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
glam = "0.30.10"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/instance.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,20 +1,18 @@
use glam::{Mat4, Quat, Vec3};
use nodarium_macros::include_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_macros::nodarium_definition_file;
use nodarium_utils::{
concat_args, encode_float, evaluate_float, evaluate_int,
concat_args, evaluate_float, evaluate_int,
geometry::{
calculate_normals, create_instance_data, wrap_geometry_data, wrap_instance_data, wrap_path,
create_instance_data, wrap_geometry_data, wrap_instance_data, wrap_path,
},
log, set_panic_hook, split_args, wrap_arg,
log, split_args,
};
use wasm_bindgen::prelude::*;
include_definition_file!("src/input.json");
nodarium_definition_file!("src/input.json");
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(input);
let mut inputs = split_args(args[0]);
log!("WASM(instance): inputs: {:?}", inputs);

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,17 +7,6 @@ edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.84"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
web-sys = { version = "0.3.69", features = ["console"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/math.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,12 +1,13 @@
use nodarium_macros::include_definition_file;
use nodarium_utils::{concat_args, set_panic_hook, split_args};
use wasm_bindgen::prelude::*;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{
concat_args, split_args
};
include_definition_file!("src/input.json");
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(args: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(args);
concat_args(vec![&[0], args[0], args[1], args[2]])
}
nodarium_definition_file!("src/input.json");

View File

@@ -1,13 +0,0 @@
//! 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

@@ -1,5 +1,5 @@
[package]
name = "nodes-noise"
name = "noise"
version = "0.1.0"
authors = ["Max Richter <jim-x@web.de>"]
edition = "2018"
@@ -7,24 +7,8 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
noise = "0.9.0"
glam = "0.27.0"
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/noise.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,21 +1,19 @@
use nodarium_macros::include_definition_file;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{
concat_args, evaluate_float, evaluate_int, evaluate_vec3, geometry::wrap_path_mut, log,
reset_call_count, set_panic_hook, split_args,
concat_args, evaluate_float, evaluate_int, evaluate_vec3, geometry::wrap_path_mut,
reset_call_count, split_args,
};
use noise::{HybridMulti, MultiFractal, NoiseFn, OpenSimplex};
use wasm_bindgen::prelude::*;
include_definition_file!("src/input.json");
nodarium_definition_file!("src/input.json");
fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + t * (b - a)
}
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
reset_call_count();
let args = split_args(input);

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,24 +7,6 @@ edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
console_error_panic_hook = ["dep: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.
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] }
glam = "0.27.0"
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/output.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,17 +1,15 @@
use nodarium_macros::include_definition_file;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{
concat_args, evaluate_int,
geometry::{extrude_path, wrap_path},
log, set_panic_hook, split_args,
log, split_args,
};
use wasm_bindgen::prelude::*;
include_definition_file!("src/inputs.json");
nodarium_definition_file!("src/inputs.json");
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
log!("WASM(output): input: {:?}", input);
let args = split_args(input);

View File

@@ -1,13 +0,0 @@
//! 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

@@ -7,21 +7,7 @@ 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.
nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
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"

View File

@@ -1,6 +1,6 @@
{
"scripts": {
"build": "wasm-pack build --release --out-name index --no-default-features",
"build": "cargo build --target wasm32-unknown-unknown --release && mkdir -p pkg && cp ../../../../target/wasm32-unknown-unknown/release/random.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
}
}

View File

@@ -1,12 +1,11 @@
use nodarium_macros::include_definition_file;
use nodarium_utils::{concat_args, set_panic_hook, split_args};
use wasm_bindgen::prelude::*;
use nodarium_macros::nodarium_definition_file;
use nodarium_macros::nodarium_execute;
use nodarium_utils::{concat_args, split_args};
include_definition_file!("src/definition.json");
nodarium_definition_file!("src/definition.json");
#[wasm_bindgen]
#[nodarium_execute]
pub fn execute(args: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(args);
concat_args(vec![&[1], args[0], args[1], args[2]])
}

Some files were not shown because too many files have changed in this diff Show More