20 Commits

Author SHA1 Message Date
Niklas Koll
e84c715f4c chore: Add flake and direnv stuff 2026-01-19 12:51:33 +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
Max Richter
ef479d0557 chore: update
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 3m50s
2025-12-02 17:31:58 +01:00
Max Richter
a1c926c3cf fix: better handle randomGeneration 2025-12-02 17:27:34 +01:00
202 changed files with 5405 additions and 7749 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 # Added by cargo
/target /target
.direnv/

367
Cargo.lock generated
View File

@@ -1,176 +1,80 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.2.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "box" name = "box"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
] ]
[[package]] [[package]]
name = "branch" name = "branch"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "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]] [[package]]
name = "float" name = "float"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
] ]
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.27.0" version = "0.30.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9"
[[package]] [[package]]
name = "gravity" name = "gravity"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"glam", "glam",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"noise", ]
"serde",
"serde-wasm-bindgen", [[package]]
"wasm-bindgen", name = "instance"
"wasm-bindgen-test", version = "0.1.0"
"web-sys", dependencies = [
"glam",
"nodarium_macros",
"nodarium_utils",
] ]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[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"
[[package]] [[package]]
name = "math" name = "math"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
] ]
[[package]] [[package]]
name = "max-plantarium-triangle" name = "memchr"
version = "0.1.0" version = "2.7.6"
dependencies = [ source = "registry+https://github.com/rust-lang/crates.io-index"
"console_error_panic_hook", checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
"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",
]
[[package]] [[package]]
name = "nodarium_macros" name = "nodarium_macros"
@@ -180,7 +84,7 @@ dependencies = [
"quote", "quote",
"serde", "serde",
"serde_json", "serde_json",
"syn 1.0.109", "syn",
] ]
[[package]] [[package]]
@@ -195,29 +99,19 @@ dependencies = [
name = "nodarium_utils" name = "nodarium_utils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"glam", "glam",
"noise", "noise 0.9.0",
"serde", "serde",
"serde_json", "serde_json",
"wasm-bindgen",
"web-sys",
] ]
[[package]] [[package]]
name = "nodes-noise" name = "noise"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"noise", "noise 0.9.0",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
] ]
[[package]] [[package]]
@@ -233,48 +127,35 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.18" version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "output" name = "output"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"glam",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde",
"serde_json",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.81" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -307,103 +188,76 @@ dependencies = [
name = "random" name = "random"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
] ]
[[package]] [[package]]
name = "rotate" name = "rotate"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"glam", "glam",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde", "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]] [[package]]
name = "serde" name = "serde"
version = "1.0.198" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" 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 = [ dependencies = [
"serde_derive", "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]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.198" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.60", "syn",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.116" version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "memchr",
"serde", "serde",
"serde_core",
"zmij",
] ]
[[package]] [[package]]
name = "stem" name = "stem"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"console_error_panic_hook",
"nodarium_macros", "nodarium_macros",
"nodarium_utils", "nodarium_utils",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-test",
"web-sys",
] ]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -411,119 +265,30 @@ dependencies = [
] ]
[[package]] [[package]]
name = "syn" name = "triangle"
version = "2.0.60" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [ dependencies = [
"proc-macro2", "nodarium_macros",
"quote", "nodarium_utils",
"unicode-ident",
] ]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]] [[package]]
name = "wasm-bindgen" name = "vec3"
version = "0.2.92" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [ dependencies = [
"cfg-if", "nodarium_macros",
"wasm-bindgen-macro", "nodarium_utils",
"serde",
] ]
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "zmij"
version = "0.2.92" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"
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",
]

View File

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

@@ -1,15 +1,21 @@
<script lang="ts"> <script lang="ts">
import { HTML } from "@threlte/extras"; import { HTML } from "@threlte/extras";
import { onMount } from "svelte"; import { onMount } from "svelte";
import type { Node, NodeType } from "@nodarium/types"; import type { NodeInstance, NodeId } from "@nodarium/types";
import { getGraphManager, getGraphState } from "../graph/state.svelte"; import { getGraphManager, getGraphState } from "../graph-state.svelte";
type Props = {
onnode: (n: NodeInstance) => void;
};
const { onnode }: Props = $props();
const graph = getGraphManager(); const graph = getGraphManager();
const graphState = getGraphState(); const graphState = getGraphState();
let input: HTMLInputElement; let input: HTMLInputElement;
let value = $state<string>(); let value = $state<string>();
let activeNodeId = $state<NodeType>(); let activeNodeId = $state<NodeId>();
const allNodes = graphState.activeSocket const allNodes = graphState.activeSocket
? graph.getPossibleNodes(graphState.activeSocket) ? graph.getPossibleNodes(graphState.activeSocket)
@@ -33,26 +39,15 @@
} }
}); });
function handleNodeCreation(nodeType: Node["type"]) { function handleNodeCreation(nodeType: NodeInstance["type"]) {
if (!graphState.addMenuPosition) return; if (!graphState.addMenuPosition) return;
onnode?.({
const newNode = graph.createNode({ id: -1,
type: nodeType, type: nodeType,
position: graphState.addMenuPosition, position: [...graphState.addMenuPosition],
props: {}, props: {},
state: {},
}); });
const edgeInputSocket = graphState.activeSocket;
if (edgeInputSocket && newNode) {
if (typeof edgeInputSocket.index === "number") {
graph.smartConnect(edgeInputSocket.node, newNode);
} else {
graph.smartConnect(newNode, edgeInputSocket.node);
}
}
graphState.activeSocket = null;
graphState.addMenuPosition = null;
} }
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {

View File

@@ -26,32 +26,32 @@
<script lang="ts"> <script lang="ts">
import { T } from "@threlte/core"; import { T } from "@threlte/core";
import { MeshLineMaterial } from "@threlte/extras"; import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
import { Mesh, MeshBasicMaterial, Vector3 } from "three"; import { MeshBasicMaterial, Vector3 } from "three";
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js"; import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
import { Vector2 } from "three/src/math/Vector2.js"; import { Vector2 } from "three/src/math/Vector2.js";
import { createEdgeGeometry } from "./createEdgeGeometry.js";
import { appSettings } from "$lib/settings/app-settings.svelte"; import { appSettings } from "$lib/settings/app-settings.svelte";
type Props = { type Props = {
from: { x: number; y: number }; x1: number;
to: { x: number; y: number }; y1: number;
x2: number;
y2: number;
z: number; z: number;
}; };
const { from, to, z }: Props = $props(); const { x1, y1, x2, y2, z }: Props = $props();
const thickness = $derived(Math.max(0.001, 0.00082 * Math.exp(0.055 * z))); 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; let lastId: string | null = null;
function update() { function update() {
const new_x = to.x - from.x; const new_x = x2 - x1;
const new_y = to.y - from.y; const new_y = y2 - y1;
const curveId = `${from.x}-${from.y}-${to.x}-${to.y}`; const curveId = `${x1}-${y1}-${x2}-${y2}`;
if (lastId === curveId) { if (lastId === curveId) {
return; return;
} }
@@ -68,26 +68,22 @@
curve.v2.set(new_x / 2, new_y); curve.v2.set(new_x / 2, new_y);
curve.v3.set(new_x, new_y); curve.v3.set(new_x, new_y);
const points = curve points = curve
.getPoints(samples) .getPoints(samples)
.map((p) => new Vector3(p.x, 0, p.y)) .map((p) => new Vector3(p.x, 0, p.y))
.flat(); .flat();
if (mesh) {
mesh.geometry = createEdgeGeometry(points);
}
} }
$effect(() => { $effect(() => {
if (from || to) { if (x1 || x2 || y1 || y2) {
update(); update();
} }
}); });
</script> </script>
<T.Mesh <T.Mesh
position.x={from.x} position.x={x1}
position.z={from.y} position.z={y1}
position.y={0.8} position.y={0.8}
rotation.x={-Math.PI / 2} rotation.x={-Math.PI / 2}
material={circleMaterial} material={circleMaterial}
@@ -96,8 +92,8 @@
</T.Mesh> </T.Mesh>
<T.Mesh <T.Mesh
position.x={to.x} position.x={x2}
position.z={to.y} position.z={y2}
position.y={0.8} position.y={0.8}
rotation.x={-Math.PI / 2} rotation.x={-Math.PI / 2}
material={circleMaterial} material={circleMaterial}
@@ -105,11 +101,7 @@
<T.CircleGeometry args={[0.5, 16]} /> <T.CircleGeometry args={[0.5, 16]} />
</T.Mesh> </T.Mesh>
<T.Mesh <T.Mesh position.x={x1} position.z={y1} position.y={0.1}>
bind:ref={mesh} <MeshLineGeometry {points} />
position.x={from.x}
position.z={from.y}
position.y={0.1}
>
<MeshLineMaterial width={thickness} color={lineColor} /> <MeshLineMaterial width={thickness} color={lineColor} />
</T.Mesh> </T.Mesh>

View File

@@ -1,12 +0,0 @@
<script lang="ts">
import Edge from "./Edge.svelte";
type Props = {
from: { x: number; y: number };
to: { x: number; y: number };
z: number;
};
const { from, to, z }: Props = $props();
</script>
<Edge {from} {to} {z} />

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 { import type {
Edge, Edge,
Graph, Graph,
Node, NodeInstance,
NodeDefinition, NodeDefinition,
NodeInput, NodeInput,
NodeRegistry, NodeRegistry,
NodeType, NodeId,
Socket, Socket,
} from "@nodarium/types"; } from "@nodarium/types";
import { fastHashString } from "@nodarium/utils"; import { fastHashString } from "@nodarium/utils";
@@ -68,7 +68,7 @@ export class GraphManager extends EventEmitter<{
graph: Graph = { id: 0, nodes: [], edges: [] }; graph: Graph = { id: 0, nodes: [], edges: [] };
id = $state(0); id = $state(0);
nodes = new SvelteMap<number, Node>(); nodes = new SvelteMap<number, NodeInstance>();
edges = $state<Edge[]>([]); edges = $state<Edge[]>([]);
@@ -101,7 +101,7 @@ export class GraphManager extends EventEmitter<{
position: [...node.position], position: [...node.position],
type: node.type, type: node.type,
props: node.props, props: node.props,
})) as Node[]; })) as NodeInstance[];
const edges = this.edges.map((edge) => [ const edges = this.edges.map((edge) => [
edge[0].id, edge[0].id,
edge[1], edge[1],
@@ -133,8 +133,8 @@ export class GraphManager extends EventEmitter<{
return this.registry.getAllNodes(); return this.registry.getAllNodes();
} }
getLinkedNodes(node: Node) { getLinkedNodes(node: NodeInstance) {
const nodes = new Set<Node>(); const nodes = new Set<NodeInstance>();
const stack = [node]; const stack = [node];
while (stack.length) { while (stack.length) {
const n = stack.pop(); const n = stack.pop();
@@ -148,10 +148,10 @@ export class GraphManager extends EventEmitter<{
return [...nodes.values()]; return [...nodes.values()];
} }
getEdgesBetweenNodes(nodes: Node[]): [number, number, number, string][] { getEdgesBetweenNodes(nodes: NodeInstance[]): [number, number, number, string][] {
const edges = []; const edges = [];
for (const node of nodes) { for (const node of nodes) {
const children = node.tmp?.children || []; const children = node.state?.children || [];
for (const child of children) { for (const child of children) {
if (nodes.includes(child)) { if (nodes.includes(child)) {
const edge = this.edges.find( const edge = this.edges.find(
@@ -174,14 +174,15 @@ export class GraphManager extends EventEmitter<{
private _init(graph: Graph) { private _init(graph: Graph) {
const nodes = new Map( const nodes = new Map(
graph.nodes.map((node: Node) => { graph.nodes.map((node) => {
const nodeType = this.registry.getNode(node.type); const nodeType = this.registry.getNode(node.type);
const n = node as NodeInstance;
if (nodeType) { if (nodeType) {
node.tmp = { n.state = {
type: nodeType, type: nodeType,
}; };
} }
return [node.id, node]; return [node.id, n];
}), }),
); );
@@ -191,12 +192,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) { if (!from || !to) {
throw new Error("Edge references non-existing node"); throw new Error("Edge references non-existing node");
} }
from.tmp = from.tmp || {}; from.state.children = from.state.children || [];
from.tmp.children = from.tmp.children || []; from.state.children.push(to);
from.tmp.children.push(to); to.state.parents = to.state.parents || [];
to.tmp = to.tmp || {}; to.state.parents.push(from);
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
return [from, edge[1], to, edge[3]] as Edge; return [from, edge[1], to, edge[3]] as Edge;
}); });
@@ -232,8 +231,10 @@ export class GraphManager extends EventEmitter<{
this.status = "error"; this.status = "error";
return; return;
} }
node.tmp = node.tmp || {}; // Turn into runtime node
node.tmp.type = nodeType; const n = node as NodeInstance;
n.state = {};
n.state.type = nodeType;
} }
// load settings // load settings
@@ -292,7 +293,7 @@ export class GraphManager extends EventEmitter<{
return this.registry.getNode(id); return this.registry.getNode(id);
} }
async loadNodeType(id: NodeType) { async loadNodeType(id: NodeId) {
await this.registry.load([id]); await this.registry.load([id]);
const nodeType = this.registry.getNode(id); const nodeType = this.registry.getNode(id);
@@ -321,19 +322,19 @@ export class GraphManager extends EventEmitter<{
this.emit("settings", { types: settingTypes, values: settingValues }); this.emit("settings", { types: settingTypes, values: settingValues });
} }
getChildren(node: Node) { getChildren(node: NodeInstance) {
const children = []; const children = [];
const stack = node.tmp?.children?.slice(0); const stack = node.state?.children?.slice(0);
while (stack?.length) { while (stack?.length) {
const child = stack.pop(); const child = stack.pop();
if (!child) continue; if (!child) continue;
children.push(child); children.push(child);
stack.push(...(child.tmp?.children || [])); stack.push(...(child.state?.children || []));
} }
return children; return children;
} }
getNodesBetween(from: Node, to: Node): Node[] | undefined { getNodesBetween(from: NodeInstance, to: NodeInstance): NodeInstance[] | undefined {
// < - - - - from // < - - - - from
const toParents = this.getParentsOfNode(to); const toParents = this.getParentsOfNode(to);
// < - - - - from - - - - 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 edgesToNode = this.edges.filter((edge) => edge[2].id === node.id);
const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id); const edgesFromNode = this.edges.filter((edge) => edge[0].id === node.id);
for (const edge of [...edgesToNode, ...edgesFromNode]) { for (const edge of [...edgesToNode, ...edgesFromNode]) {
@@ -363,8 +364,8 @@ export class GraphManager extends EventEmitter<{
for (const [to, toSocket] of inputSockets) { for (const [to, toSocket] of inputSockets) {
for (const [from, fromSocket] of outputSockets) { for (const [from, fromSocket] of outputSockets) {
const outputType = from.tmp?.type?.outputs?.[fromSocket]; const outputType = from.state?.type?.outputs?.[fromSocket];
const inputType = to?.tmp?.type?.inputs?.[toSocket]?.type; const inputType = to?.state?.type?.inputs?.[toSocket]?.type;
if (outputType === inputType) { if (outputType === inputType) {
this.createEdge(from, fromSocket, to, toSocket, { this.createEdge(from, fromSocket, to, toSocket, {
applyUpdate: false, applyUpdate: false,
@@ -380,9 +381,9 @@ export class GraphManager extends EventEmitter<{
this.save(); this.save();
} }
smartConnect(from: Node, to: Node): Edge | undefined { smartConnect(from: NodeInstance, to: NodeInstance): Edge | undefined {
const inputs = Object.entries(to.tmp?.type?.inputs ?? {}); const inputs = Object.entries(to.state?.type?.inputs ?? {});
const outputs = from.tmp?.type?.outputs ?? []; const outputs = from.state?.type?.outputs ?? [];
for (let i = 0; i < inputs.length; i++) { for (let i = 0; i < inputs.length; i++) {
const [inputName, input] = inputs[0]; const [inputName, input] = inputs[0];
for (let o = 0; o < outputs.length; o++) { 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; 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 // map old ids to new ids
const idMap = new Map<number, number>(); const idMap = new Map<number, number>();
@@ -422,13 +423,11 @@ export class GraphManager extends EventEmitter<{
throw new Error("Edge references non-existing node"); throw new Error("Edge references non-existing node");
} }
to.tmp = to.tmp || {}; to.state.parents = to.state.parents || [];
to.tmp.parents = to.tmp.parents || []; to.state.parents.push(from);
to.tmp.parents.push(from);
from.tmp = from.tmp || {}; from.state.children = from.state.children || [];
from.tmp.children = from.tmp.children || []; from.state.children.push(to);
from.tmp.children.push(to);
return [from, edge[1], to, edge[3]] as Edge; return [from, edge[1], to, edge[3]] as Edge;
}); });
@@ -448,9 +447,9 @@ export class GraphManager extends EventEmitter<{
position, position,
props = {}, props = {},
}: { }: {
type: Node["type"]; type: NodeInstance["type"];
position: Node["position"]; position: NodeInstance["position"];
props: Node["props"]; props: NodeInstance["props"];
}) { }) {
const nodeType = this.registry.getNode(type); const nodeType = this.registry.getNode(type);
if (!nodeType) { if (!nodeType) {
@@ -458,11 +457,11 @@ export class GraphManager extends EventEmitter<{
return; return;
} }
const node: Node = $state({ const node: NodeInstance = $state({
id: this.createNodeId(), id: this.createNodeId(),
type, type,
position, position,
tmp: { type: nodeType }, state: { type: nodeType },
props, props,
}); });
@@ -474,9 +473,9 @@ export class GraphManager extends EventEmitter<{
} }
createEdge( createEdge(
from: Node, from: NodeInstance,
fromSocket: number, fromSocket: number,
to: Node, to: NodeInstance,
toSocket: string, toSocket: string,
{ applyUpdate = true } = {}, { applyUpdate = true } = {},
): Edge | undefined { ): Edge | undefined {
@@ -493,10 +492,10 @@ export class GraphManager extends EventEmitter<{
} }
// check if socket types match // check if socket types match
const fromSocketType = from.tmp?.type?.outputs?.[fromSocket]; const fromSocketType = from.state?.type?.outputs?.[fromSocket];
const toSocketType = [to.tmp?.type?.inputs?.[toSocket]?.type]; const toSocketType = [to.state?.type?.inputs?.[toSocket]?.type];
if (to.tmp?.type?.inputs?.[toSocket]?.accepts) { if (to.state?.type?.inputs?.[toSocket]?.accepts) {
toSocketType.push(...(to?.tmp?.type?.inputs?.[toSocket]?.accepts || [])); toSocketType.push(...(to?.state?.type?.inputs?.[toSocket]?.accepts || []));
} }
if (!areSocketsCompatible(fromSocketType, toSocketType)) { if (!areSocketsCompatible(fromSocketType, toSocketType)) {
@@ -517,13 +516,10 @@ export class GraphManager extends EventEmitter<{
this.edges.push(edge); this.edges.push(edge);
from.tmp = from.tmp || {}; from.state.children = from.state.children || [];
from.tmp.children = from.tmp.children || []; from.state.children.push(to);
from.tmp.children.push(to); to.state.parents = to.state.parents || [];
to.state.parents.push(from);
to.tmp = to.tmp || {};
to.tmp.parents = to.tmp.parents || [];
to.tmp.parents.push(from);
if (applyUpdate) { if (applyUpdate) {
this.save(); this.save();
@@ -566,9 +562,9 @@ export class GraphManager extends EventEmitter<{
logger.log("saving graphs", state); logger.log("saving graphs", state);
} }
getParentsOfNode(node: Node) { getParentsOfNode(node: NodeInstance) {
const parents = []; const parents = [];
const stack = node.tmp?.parents?.slice(0); const stack = node.state?.parents?.slice(0);
while (stack?.length) { while (stack?.length) {
if (parents.length > 1000000) { if (parents.length > 1000000) {
logger.warn("Infinite loop detected"); logger.warn("Infinite loop detected");
@@ -577,7 +573,7 @@ export class GraphManager extends EventEmitter<{
const parent = stack.pop(); const parent = stack.pop();
if (!parent) continue; if (!parent) continue;
parents.push(parent); parents.push(parent);
stack.push(...(parent.tmp?.parents || [])); stack.push(...(parent.state?.parents || []));
} }
return parents.reverse(); return parents.reverse();
} }
@@ -585,7 +581,7 @@ export class GraphManager extends EventEmitter<{
getPossibleNodes(socket: Socket): NodeDefinition[] { getPossibleNodes(socket: Socket): NodeDefinition[] {
const allDefinitions = this.getNodeDefinitions(); const allDefinitions = this.getNodeDefinitions();
const nodeType = socket.node.tmp?.type; const nodeType = socket.node.state?.type;
if (!nodeType) { if (!nodeType) {
return []; return [];
} }
@@ -612,11 +608,11 @@ export class GraphManager extends EventEmitter<{
} }
getPossibleSockets({ node, index }: Socket): [Node, string | number][] { getPossibleSockets({ node, index }: Socket): [NodeInstance, string | number][] {
const nodeType = node?.tmp?.type; const nodeType = node?.state?.type;
if (!nodeType) return []; 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 index is a string, we are an input looking for outputs
if (typeof index === "string") { if (typeof index === "string") {
@@ -629,7 +625,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType?.inputs?.[index].type; const ownType = nodeType?.inputs?.[index].type;
for (const node of nodes) { for (const node of nodes) {
const nodeType = node?.tmp?.type; const nodeType = node?.state?.type;
const inputs = nodeType?.outputs; const inputs = nodeType?.outputs;
if (!inputs) continue; if (!inputs) continue;
for (let index = 0; index < inputs.length; index++) { for (let index = 0; index < inputs.length; index++) {
@@ -657,7 +653,7 @@ export class GraphManager extends EventEmitter<{
const ownType = nodeType.outputs?.[index]; const ownType = nodeType.outputs?.[index];
for (const node of nodes) { for (const node of nodes) {
const inputs = node?.tmp?.type?.inputs; const inputs = node?.state?.type?.inputs;
if (!inputs) continue; if (!inputs) continue;
for (const key in inputs) { for (const key in inputs) {
const otherType = [inputs[key].type]; const otherType = [inputs[key].type];
@@ -692,17 +688,15 @@ export class GraphManager extends EventEmitter<{
if (!_edge) return; if (!_edge) return;
edge[0].tmp = edge[0].tmp || {}; if (edge[0].state.children) {
if (edge[0].tmp.children) { edge[0].state.children = edge[0].state.children.filter(
edge[0].tmp.children = edge[0].tmp.children.filter( (n: NodeInstance) => n.id !== id2,
(n: Node) => n.id !== id2,
); );
} }
edge[2].tmp = edge[2].tmp || {}; if (edge[2].state.parents) {
if (edge[2].tmp.parents) { edge[2].state.parents = edge[2].state.parents.filter(
edge[2].tmp.parents = edge[2].tmp.parents.filter( (n: NodeInstance) => n.id !== id0,
(n: Node) => n.id !== id0,
); );
} }
@@ -714,7 +708,7 @@ export class GraphManager extends EventEmitter<{
} }
getEdgesToNode(node: Node) { getEdgesToNode(node: NodeInstance) {
return this.edges return this.edges
.filter((edge) => edge[2].id === node.id) .filter((edge) => edge[2].id === node.id)
.map((edge) => { .map((edge) => {
@@ -723,10 +717,10 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return; if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const; 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 return this.edges
.filter((edge) => edge[0].id === node.id) .filter((edge) => edge[0].id === node.id)
.map((edge) => { .map((edge) => {
@@ -735,6 +729,6 @@ export class GraphManager extends EventEmitter<{
if (!from || !to) return; if (!from || !to) return;
return [from, edge[1], to, edge[3]] as const; 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 { getContext, setContext } from "svelte";
import { SvelteSet } from "svelte/reactivity"; import { SvelteSet } from "svelte/reactivity";
import type { GraphManager } from "../graph-manager.svelte"; import type { GraphManager } from "./graph-manager.svelte";
import type { OrthographicCamera } from "three"; import type { OrthographicCamera } from "three";
@@ -24,7 +24,24 @@ export function setGraphManager(manager: GraphManager) {
export class GraphState { 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); width = $state(100);
height = $state(100); height = $state(100);
@@ -38,7 +55,7 @@ export class GraphState {
cameraPosition: [number, number, number] = $state([0, 0, 4]); cameraPosition: [number, number, number] = $state([0, 0, 4]);
clipboard: null | { clipboard: null | {
nodes: Node[]; nodes: NodeInstance[];
edges: [number, number, number, string][]; edges: [number, number, number, string][];
} = null; } = null;
@@ -80,42 +97,24 @@ export class GraphState {
isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT"; isBodyFocused = () => document?.activeElement?.nodeName !== "INPUT";
setCameraTransform( updateNodePosition(node: NodeInstance) {
x = this.cameraPosition[0], if (
y = this.cameraPosition[1], node.state.x === node.position[0] &&
z = this.cameraPosition[2], node.state.y === node.position[1]
) { ) {
if (this.camera) { delete node.state.x;
this.camera.position.x = x; delete node.state.y;
this.camera.position.z = y;
this.camera.zoom = z;
} }
this.cameraPosition = [x, y, z];
localStorage.setItem("cameraPosition", JSON.stringify(this.cameraPosition));
}
if (node.state["x"] !== undefined && node.state["y"] !== undefined) {
updateNodePosition(node: Node) { if (node.state.ref) {
if (node?.tmp?.ref && node?.tmp?.mesh) { node.state.ref.style.setProperty("--nx", `${node.state.x * 10}px`);
if (node.tmp["x"] !== undefined && node.tmp["y"] !== undefined) { node.state.ref.style.setProperty("--ny", `${node.state.y * 10}px`);
node.tmp.ref.style.setProperty("--nx", `${node.tmp.x * 10}px`); }
node.tmp.ref.style.setProperty("--ny", `${node.tmp.y * 10}px`); } else {
node.tmp.mesh.position.x = node.tmp.x + 10; if (node.state.ref) {
node.tmp.mesh.position.z = node.tmp.y + this.getNodeHeight(node.type) / 2; node.state.ref.style.setProperty("--nx", `${node.position[0] * 10}px`);
if ( node.state.ref.style.setProperty("--ny", `${node.position[1] * 10}px`);
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;
} }
} }
} }
@@ -134,19 +133,19 @@ export class GraphState {
} }
getSocketPosition( getSocketPosition(
node: Node, node: NodeInstance,
index: string | number, index: string | number,
): [number, number] { ): [number, number] {
if (typeof index === "number") { if (typeof index === "number") {
return [ return [
(node?.tmp?.x ?? node.position[0]) + 20, (node?.state?.x ?? node.position[0]) + 20,
(node?.tmp?.y ?? node.position[1]) + 2.5 + 10 * index, (node?.state?.y ?? node.position[1]) + 2.5 + 10 * index,
]; ];
} else { } else {
const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index); const _index = Object.keys(node.state?.type?.inputs || {}).indexOf(index);
return [ return [
node?.tmp?.x ?? node.position[0], node?.state?.x ?? node.position[0],
(node?.tmp?.y ?? node.position[1]) + 10 + 10 * _index, (node?.state?.y ?? node.position[1]) + 10 + 10 * _index,
]; ];
} }
} }
@@ -174,32 +173,6 @@ export class GraphState {
return height; 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() { copyNodes() {
if (this.activeNodeId === -1 && !this.selectedNodes?.size) if (this.activeNodeId === -1 && !this.selectedNodes?.size)
return; return;
@@ -231,12 +204,11 @@ export class GraphState {
const nodes = this.clipboard.nodes const nodes = this.clipboard.nodes
.map((node) => { .map((node) => {
node.tmp = node.tmp || {};
node.position[0] = this.mousePosition[0] - node.position[0]; node.position[0] = this.mousePosition[0] - node.position[0];
node.position[1] = this.mousePosition[1] - node.position[1]; node.position[1] = this.mousePosition[1] - node.position[1];
return node; return node;
}) })
.filter(Boolean) as Node[]; .filter(Boolean) as NodeInstance[];
const newNodes = this.graph.createGraph(nodes, this.clipboard.edges); const newNodes = this.graph.createGraph(nodes, this.clipboard.edges);
this.selectedNodes.clear(); this.selectedNodes.clear();
@@ -327,7 +299,7 @@ export class GraphState {
return clickedNodeId; return clickedNodeId;
} }
isNodeInView(node: Node) { isNodeInView(node: NodeInstance) {
const height = this.getNodeHeight(node.type); const height = this.getNodeHeight(node.type);
const width = 20; const width = 20;
return ( return (

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Edge } from "@nodarium/types"; import type { Edge, NodeInstance } from "@nodarium/types";
import { onMount } from "svelte";
import { createKeyMap } from "../../helpers/createKeyMap"; import { createKeyMap } from "../../helpers/createKeyMap";
import AddMenu from "../components/AddMenu.svelte"; import AddMenu from "../components/AddMenu.svelte";
import Background from "../background/Background.svelte"; import Background from "../background/Background.svelte";
@@ -8,10 +7,9 @@
import EdgeEl from "../edges/Edge.svelte"; import EdgeEl from "../edges/Edge.svelte";
import NodeEl from "../node/Node.svelte"; import NodeEl from "../node/Node.svelte";
import Camera from "../components/Camera.svelte"; import Camera from "../components/Camera.svelte";
import FloatingEdge from "../edges/FloatingEdge.svelte";
import { Canvas } from "@threlte/core"; import { Canvas } from "@threlte/core";
import HelpView from "../components/HelpView.svelte"; 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 { HTML } from "@threlte/extras";
import { FileDropEventManager, MouseEventManager } from "./events"; import { FileDropEventManager, MouseEventManager } from "./events";
import { maxZoom, minZoom } from "./constants"; import { maxZoom, minZoom } from "./constants";
@@ -27,10 +25,6 @@
const fileDropEvents = new FileDropEventManager(graph, graphState); const fileDropEvents = new FileDropEventManager(graph, graphState);
const mouseEvents = new MouseEventManager(graph, graphState); const mouseEvents = new MouseEventManager(graph, graphState);
function getEdgeId(edge: Edge) {
return `${edge[0].id}-${edge[1]}-${edge[2].id}-${edge[3]}`;
}
function getEdgePosition(edge: Edge) { function getEdgePosition(edge: Edge) {
const fromNode = graph.nodes.get(edge[0].id); const fromNode = graph.nodes.get(edge[0].id);
const toNode = graph.nodes.get(edge[2].id); const toNode = graph.nodes.get(edge[2].id);
@@ -45,14 +39,59 @@
return [pos1[0], pos1[1], pos2[0], pos2[1]]; return [pos1[0], pos1[1], pos2[0], pos2[1]];
} }
onMount(() => { function handleNodeCreation(node: NodeInstance) {
if (localStorage.getItem("cameraPosition")) { const newNode = graph.createNode({
const cPosition = JSON.parse(localStorage.getItem("cameraPosition")!); type: node.type,
if (Array.isArray(cPosition)) { position: node.position,
graphState.setCameraTransform(cPosition[0], cPosition[1], cPosition[2]); props: node.props,
});
if (!newNode) return;
if (graphState.activeSocket) {
if (typeof graphState.activeSocket.index === "number") {
const socketType =
graphState.activeSocket.node.state?.type?.outputs?.[
graphState.activeSocket.index
];
const input = Object.entries(newNode?.state?.type?.inputs || {}).find(
(inp) => inp[1].type === socketType,
);
if (input) {
graph.createEdge(
graphState.activeSocket.node,
graphState.activeSocket.index,
newNode,
input[0],
);
}
} else {
const socketType =
graphState.activeSocket.node.state?.type?.inputs?.[
graphState.activeSocket.index
];
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;
});
if (output) {
graph.createEdge(
newNode,
output.indexOf(output),
graphState.activeSocket.node,
graphState.activeSocket.index,
);
}
} }
} }
});
graphState.activeSocket = null;
graphState.addMenuPosition = null;
}
</script> </script>
<svelte:window <svelte:window
@@ -120,36 +159,22 @@
{#if graph.status === "idle"} {#if graph.status === "idle"}
{#if graphState.addMenuPosition} {#if graphState.addMenuPosition}
<AddMenu /> <AddMenu onnode={handleNodeCreation} />
{/if} {/if}
{#if graphState.activeSocket} {#if graphState.activeSocket}
<FloatingEdge <EdgeEl
z={graphState.cameraPosition[2]} z={graphState.cameraPosition[2]}
from={{ x1={graphState.activeSocket.position[0]}
x: graphState.activeSocket.position[0], y1={graphState.activeSocket.position[1]}
y: graphState.activeSocket.position[1], x2={graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0]}
}} y2={graphState.edgeEndPosition?.[1] ?? graphState.mousePosition[1]}
to={{
x: graphState.edgeEndPosition?.[0] ?? graphState.mousePosition[0],
y: graphState.edgeEndPosition?.[1] ?? graphState.mousePosition[1],
}}
/> />
{/if} {/if}
{#each graph.edges as edge (getEdgeId(edge))} {#each graph.edges as edge}
{@const [x1, y1, x2, y2] = getEdgePosition(edge)} {@const [x1, y1, x2, y2] = getEdgePosition(edge)}
<EdgeEl <EdgeEl z={graphState.cameraPosition[2]} {x1} {y1} {x2} {y2} />
z={graphState.cameraPosition[2]}
from={{
x: x1,
y: y1,
}}
to={{
x: x2,
y: y2,
}}
/>
{/each} {/each}
<HTML transform={false}> <HTML transform={false}>

View File

@@ -1,9 +1,13 @@
<script lang="ts"> <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 GraphEl from "./Graph.svelte";
import { GraphManager } from "../graph-manager.svelte"; import { GraphManager } from "../graph-manager.svelte";
import { createKeyMap } from "$lib/helpers/createKeyMap"; 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"; import { setupKeymaps } from "../keymaps";
type Props = { type Props = {
@@ -12,7 +16,7 @@
settings?: Record<string, any>; settings?: Record<string, any>;
activeNode?: Node; activeNode?: NodeInstance;
showGrid?: boolean; showGrid?: boolean;
snapToGrid?: boolean; snapToGrid?: boolean;
showHelp?: 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 { 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 { animate, lerp } from "$lib/helpers";
import { snapToGrid as snapPointToGrid } from "../helpers"; import { snapToGrid as snapPointToGrid } from "../helpers";
import { maxZoom, minZoom, zoomSpeed } from "./constants"; import { maxZoom, minZoom, zoomSpeed } from "./constants";
@@ -17,7 +17,7 @@ export class FileDropEventManager {
event.preventDefault(); event.preventDefault();
this.state.isDragging = false; this.state.isDragging = false;
if (!event.dataTransfer) return; 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 mx = event.clientX - this.state.rect.x;
let my = event.clientY - this.state.rect.y; let my = event.clientY - this.state.rect.y;
@@ -131,45 +131,45 @@ export class MouseEventManager {
if (clickedNodeId !== -1) { if (clickedNodeId !== -1) {
if (activeNode) { if (activeNode) {
if (!activeNode?.tmp?.isMoving && !event.ctrlKey && !event.shiftKey) { if (!activeNode?.state?.isMoving && !event.ctrlKey && !event.shiftKey) {
this.state.activeNodeId = clickedNodeId; this.state.activeNodeId = clickedNodeId;
this.state.clearSelection(); this.state.clearSelection();
} }
} }
} }
if (activeNode?.tmp?.isMoving) { if (activeNode?.state?.isMoving) {
activeNode.tmp = activeNode.tmp || {}; activeNode.state = activeNode.state || {};
activeNode.tmp.isMoving = false; activeNode.state.isMoving = false;
if (this.state.snapToGrid) { if (this.state.snapToGrid) {
const snapLevel = this.state.getSnapLevel(); const snapLevel = this.state.getSnapLevel();
activeNode.position[0] = snapPointToGrid( activeNode.position[0] = snapPointToGrid(
activeNode?.tmp?.x ?? activeNode.position[0], activeNode?.state?.x ?? activeNode.position[0],
5 / snapLevel, 5 / snapLevel,
); );
activeNode.position[1] = snapPointToGrid( activeNode.position[1] = snapPointToGrid(
activeNode?.tmp?.y ?? activeNode.position[1], activeNode?.state?.y ?? activeNode.position[1],
5 / snapLevel, 5 / snapLevel,
); );
} else { } else {
activeNode.position[0] = activeNode?.tmp?.x ?? activeNode.position[0]; activeNode.position[0] = activeNode?.state?.x ?? activeNode.position[0];
activeNode.position[1] = activeNode?.tmp?.y ?? activeNode.position[1]; activeNode.position[1] = activeNode?.state?.y ?? activeNode.position[1];
} }
const nodes = [ const nodes = [
...[...(this.state.selectedNodes?.values() || [])].map((id) => ...[...(this.state.selectedNodes?.values() || [])].map((id) =>
this.graph.getNode(id), this.graph.getNode(id),
), ),
] as Node[]; ] as NodeInstance[];
const vec = [ const vec = [
activeNode.position[0] - (activeNode?.tmp.x || 0), activeNode.position[0] - (activeNode?.state.x || 0),
activeNode.position[1] - (activeNode?.tmp.y || 0), activeNode.position[1] - (activeNode?.state.y || 0),
]; ];
for (const node of nodes) { for (const node of nodes) {
if (!node) continue; if (!node) continue;
node.tmp = node.tmp || {}; node.state = node.state || {};
const { x, y } = node.tmp; const { x, y } = node.state;
if (x !== undefined && y !== undefined) { if (x !== undefined && y !== undefined) {
node.position[0] = x + vec[0]; node.position[0] = x + vec[0];
node.position[1] = y + vec[1]; node.position[1] = y + vec[1];
@@ -179,14 +179,14 @@ export class MouseEventManager {
animate(500, (a: number) => { animate(500, (a: number) => {
for (const node of nodes) { for (const node of nodes) {
if ( if (
node?.tmp && node?.state &&
node.tmp["x"] !== undefined && node.state["x"] !== undefined &&
node.tmp["y"] !== undefined node.state["y"] !== undefined
) { ) {
node.tmp.x = lerp(node.tmp.x, node.position[0], a); node.state.x = lerp(node.state.x, node.position[0], a);
node.tmp.y = lerp(node.tmp.y, node.position[1], a); node.state.y = lerp(node.state.y, node.position[1], a);
this.state.updateNodePosition(node); this.state.updateNodePosition(node);
if (node?.tmp?.isMoving) { if (node?.state?.isMoving) {
return false; return false;
} }
} }
@@ -318,17 +318,17 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId); const node = this.graph.getNode(this.state.activeNodeId);
if (!node) return; if (!node) return;
node.tmp = node.tmp || {}; node.state = node.state || {};
node.tmp.downX = node.position[0]; node.state.downX = node.position[0];
node.tmp.downY = node.position[1]; node.state.downY = node.position[1];
if (this.state.selectedNodes) { if (this.state.selectedNodes) {
for (const nodeId of this.state.selectedNodes) { for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId); const n = this.graph.getNode(nodeId);
if (!n) continue; if (!n) continue;
n.tmp = n.tmp || {}; n.state = n.state || {};
n.tmp.downX = n.position[0]; n.state.downX = n.position[0];
n.tmp.downY = n.position[1]; n.state.downY = n.position[1];
} }
} }
@@ -382,7 +382,7 @@ export class MouseEventManager {
const y1 = Math.min(mouseD[1], this.state.mousePosition[1]); const y1 = Math.min(mouseD[1], this.state.mousePosition[1]);
const y2 = Math.max(mouseD[1], this.state.mousePosition[1]); const y2 = Math.max(mouseD[1], this.state.mousePosition[1]);
for (const node of this.graph.nodes.values()) { for (const node of this.graph.nodes.values()) {
if (!node?.tmp) continue; if (!node?.state) continue;
const x = node.position[0]; const x = node.position[0];
const y = node.position[1]; const y = node.position[1];
const height = this.state.getNodeHeight(node.type); const height = this.state.getNodeHeight(node.type);
@@ -400,10 +400,10 @@ export class MouseEventManager {
const node = this.graph.getNode(this.state.activeNodeId); const node = this.graph.getNode(this.state.activeNodeId);
if (!node || event.buttons !== 1) return; if (!node || event.buttons !== 1) return;
node.tmp = node.tmp || {}; node.state = node.state || {};
const oldX = node.tmp.downX || 0; const oldX = node.state.downX || 0;
const oldY = node.tmp.downY || 0; const oldY = node.state.downY || 0;
let newX = let newX =
oldX + (mx - this.state.mouseDown[0]) / this.state.cameraPosition[2]; 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); const dist = Math.sqrt((oldX - newX) ** 2 + (oldY - newY) ** 2);
if (dist > 0.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) { if (this.state.selectedNodes?.size) {
for (const nodeId of this.state.selectedNodes) { for (const nodeId of this.state.selectedNodes) {
const n = this.graph.getNode(nodeId); const n = this.graph.getNode(nodeId);
if (!n?.tmp) continue; if (!n?.state) continue;
n.tmp.x = (n?.tmp?.downX || 0) - vecX; n.state.x = (n?.state?.downX || 0) - vecX;
n.tmp.y = (n?.tmp?.downY || 0) - vecY; n.state.y = (n?.state?.downY || 0) - vecY;
this.state.updateNodePosition(n); this.state.updateNodePosition(n);
} }
} }
node.tmp.x = newX; node.state.x = newX;
node.tmp.y = newY; node.state.y = newY;
this.state.updateNodePosition(node); this.state.updateNodePosition(node);
@@ -455,7 +455,8 @@ export class MouseEventManager {
this.state.cameraDown[1] - this.state.cameraDown[1] -
(my - this.state.mouseDown[1]) / this.state.cameraPosition[2]; (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]; const zoomRatio = newZoom / this.state.cameraPosition[2];
// Update camera position and zoom level // Update camera position and zoom level
this.state.setCameraTransform( this.state.cameraPosition[0] = this.state.mousePosition[0] -
this.state.mousePosition[0] -
(this.state.mousePosition[0] - this.state.cameraPosition[0]) / (this.state.mousePosition[0] - this.state.cameraPosition[0]) /
zoomRatio, zoomRatio;
this.state.mousePosition[1] - this.state.cameraPosition[1] = this.state.mousePosition[1] -
(this.state.mousePosition[1] - this.state.cameraPosition[1]) / (this.state.mousePosition[1] - this.state.cameraPosition[1]) /
zoomRatio, 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 type { createKeyMap } from "$lib/helpers/createKeyMap";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
import type { GraphManager } from "./graph-manager.svelte"; 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>; type Keymap = ReturnType<typeof createKeyMap>;
export function setupKeymaps(keymap: Keymap, graph: GraphManager, graphState: GraphState) { 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); const ease = (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
animate(500, (a: number) => { animate(500, (a: number) => {
graphState.setCameraTransform( graphState.cameraPosition[0] = lerp(camX, average[0], ease(a));
lerp(camX, average[0], ease(a)), graphState.cameraPosition[1] = lerp(camY, average[1], ease(a));
lerp(camY, average[1], ease(a)), graphState.cameraPosition[2] = lerp(camZ, 2, ease(a))
lerp(camZ, 2, ease(a)),
);
if (graphState.mouseDown) return false; if (graphState.mouseDown) return false;
}); });
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
<script lang="ts"> <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); setContext("grid-id", id);
</script> </script>
<slot {id} /> {@render children({ id })}

View File

@@ -126,7 +126,7 @@ export function humanizeDuration(durationInMilliseconds: number) {
} }
if (millis > 0 || durationString === '') { if (millis > 0 || durationString === '') {
durationString += millis + 'ms'; durationString += Math.floor(millis) + 'ms';
} }
return durationString.trim(); 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="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="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"/> <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"; import path from "path";
export async function getWasm(id: `${string}/${string}/${string}`) { 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 { try {
await fs.access(filePath); await fs.access(filePath);

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,12 @@
<script lang="ts"> <script lang="ts">
export let labels: string[] = []; type Props = {
export let values: number[] = []; 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"]; let colors = ["red", "green", "blue"];
</script> </script>
@@ -10,7 +14,10 @@
<div class="wrapper"> <div class="wrapper">
<div class="bars"> <div class="bars">
{#each values as value, i} {#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 {Math.round(value)}ms
</div> </div>
{/each} {/each}
@@ -18,12 +25,11 @@
<div class="labels mt-2"> <div class="labels mt-2">
{#each values as _label, i} {#each values as _label, i}
<div class="text-{colors[i]}">{labels[i]}</div> <div class="text-{colors[i]}-400">{labels[i]}</div>
{/each} {/each}
</div> </div>
<span <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> ></span>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,33 @@
import { type SyncCache } from "@nodarium/types"; import { type SyncCache } from "@nodarium/types";
export class MemoryRuntimeCache implements SyncCache { export class MemoryRuntimeCache implements SyncCache {
private map = new Map<string, unknown>();
size: number;
private cache: [string, unknown][] = []; constructor(size = 50) {
size = 50; this.size = size;
}
get<T>(key: string): T | undefined { get<T>(key: string): T | undefined {
return this.cache.find(([k]) => k === key)?.[1] as T; if (!this.map.has(key)) return undefined;
} const value = this.map.get(key) as T;
set<T>(key: string, value: T): void { this.map.delete(key);
this.cache.push([key, value]); this.map.set(key, value);
this.cache = this.cache.slice(-this.size); return value;
}
clear(): void {
this.cache = [];
} }
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 { import type {
Graph, Graph,
Node,
NodeDefinition, NodeDefinition,
NodeInput, NodeInput,
NodeRegistry, NodeRegistry,
@@ -14,6 +13,7 @@ import {
fastHashArrayBuffer, fastHashArrayBuffer,
type PerformanceStore, type PerformanceStore,
} from "@nodarium/utils"; } from "@nodarium/utils";
import type { RuntimeNode } from "./types";
const log = createLogger("runtime-executor"); const log = createLogger("runtime-executor");
log.mute(); log.mute();
@@ -58,7 +58,7 @@ function getValue(input: NodeInput, value?: unknown) {
export class MemoryRuntimeExecutor implements RuntimeExecutor { export class MemoryRuntimeExecutor implements RuntimeExecutor {
private definitionMap: Map<string, NodeDefinition> = new Map(); private definitionMap: Map<string, NodeDefinition> = new Map();
private randomSeed = Math.floor(Math.random() * 100000000); private seed = Math.floor(Math.random() * 100000000);
perf?: PerformanceStore; perf?: PerformanceStore;
@@ -92,18 +92,27 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// First, lets check if all nodes have a definition // First, lets check if all nodes have a definition
this.definitionMap = await this.getNodeDefinitions(graph); 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"), node.type.endsWith("/output"),
) as Node; );
if (!outputNode) { if (!outputNode) {
throw new Error("No output node found"); throw new Error("No output node found");
} }
outputNode.tmp = outputNode.tmp || {}; const nodeMap = new Map(
outputNode.tmp.depth = 0; graphNodes.map((node) => [node.id, node]),
const nodeMap = new Map<number, Node>(
graph.nodes.map((node) => [node.id, node]),
); );
// loop through all edges and assign the parent and child nodes to each 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 parent = nodeMap.get(parentId);
const child = nodeMap.get(childId); const child = nodeMap.get(childId);
if (parent && child) { if (parent && child) {
parent.tmp = parent.tmp || {}; parent.state.children.push(child);
parent.tmp.children = parent.tmp.children || []; child.state.parents.push(parent);
parent.tmp.children.push(child); child.state.inputNodes[childInput] = parent;
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;
} }
} }
@@ -130,20 +134,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
while (stack.length) { while (stack.length) {
const node = stack.pop(); const node = stack.pop();
if (!node) continue; if (!node) continue;
node.tmp = node.tmp || {}; for (const parent of node.state.parents) {
if (node?.tmp?.depth === undefined) { parent.state = parent.state || {};
node.tmp.depth = 0; parent.state.depth = node.state.depth + 1;
} stack.push(parent);
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);
}
}
} }
nodes.push(node); nodes.push(node);
} }
@@ -175,16 +169,20 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// we execute the nodes from the bottom up // we execute the nodes from the bottom up
const sortedNodes = nodes.sort( 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 // here we store the intermediate results of the nodes
const results: Record<string, Int32Array> = {}; const results: Record<string, Int32Array> = {};
if (settings["randomSeed"]) {
this.seed = Math.floor(Math.random() * 100000000);
}
for (const node of sortedNodes) { for (const node of sortedNodes) {
const node_type = this.definitionMap.get(node.type)!; 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`); log.warn(`Node ${node.id} has no definition`);
continue; continue;
} }
@@ -195,10 +193,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
const inputs = Object.entries(node_type.inputs || {}).map( const inputs = Object.entries(node_type.inputs || {}).map(
([key, input]) => { ([key, input]) => {
if (input.type === "seed") { if (input.type === "seed") {
if (settings["randomSeed"] === true) { return this.seed;
this.randomSeed = Math.floor(Math.random() * 100000000);
}
return this.randomSeed;
} }
// If the input is linked to a setting, we use that value // If the input is linked to a setting, we use that value
@@ -207,7 +202,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
} }
// check if the input is connected to another node // check if the input is connected to another node
const inputNode = node.tmp?.inputNodes?.[key]; const inputNode = node.state.inputNodes[key];
if (inputNode) { if (inputNode) {
if (results[inputNode.id] === undefined) { if (results[inputNode.id] === undefined) {
throw new Error( throw new Error(
@@ -249,13 +244,14 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
} }
this.perf?.addPoint("cache-hit", 0); 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); log.log(`Inputs:`, inputs);
a = performance.now(); a = performance.now();
results[node.id] = node_type.execute(encoded_inputs); results[node.id] = node_type.execute(encoded_inputs);
log.log("Executed", node.type, node.id)
b = performance.now(); b = performance.now();
if (this.cache) { if (this.cache && node.id !== outputNode.id) {
this.cache.set(inputHash, results[node.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,11 +2,13 @@ import { MemoryRuntimeExecutor } from "./runtime-executor";
import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry"; import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry";
import type { Graph } from "@nodarium/types"; import type { Graph } from "@nodarium/types";
import { createPerformanceStore } from "@nodarium/utils"; import { createPerformanceStore } from "@nodarium/utils";
import { MemoryRuntimeCache } from "./runtime-executor-cache";
const indexDbCache = new IndexDBCache("node-registry"); const indexDbCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry("", indexDbCache); const nodeRegistry = new RemoteNodeRegistry("", indexDbCache);
const executor = new MemoryRuntimeExecutor(nodeRegistry); const cache = new MemoryRuntimeCache()
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
const performanceStore = createPerformanceStore(); const performanceStore = createPerformanceStore();
executor.perf = performanceStore; executor.perf = performanceStore;

View File

@@ -54,7 +54,7 @@ export const AppSettingTypes = {
}, },
useWorker: { useWorker: {
type: "boolean", type: "boolean",
label: "Execute runtime in worker", label: "Execute in WebWorker",
value: true, value: true,
}, },
showIndices: { showIndices: {

View File

@@ -1,43 +1,46 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte"; import { getContext, type Snippet } from "svelte";
import type { Readable } from "svelte/store"; import type { PanelState } from "./PanelState.svelte";
export let id: string; const {
export let icon: string = ""; id,
export let title = ""; icon = "",
export let classes = ""; title = "",
export let hidden: boolean | undefined = undefined; classes = "",
hidden,
children,
} = $props<{
id: string;
icon?: string;
title?: string;
classes?: string;
hidden?: boolean;
children?: Snippet;
}>();
const setVisibility = const panelState = getContext<PanelState>("panel-state");
getContext<(id: string, visible: boolean) => void>("setVisibility");
$: if (typeof hidden === "boolean") { const panel = panelState.registerPanel(id, icon, classes, hidden);
setVisibility(id, !hidden); $effect(() => {
} panel.hidden = hidden;
});
const registerPanel =
getContext<
(id: string, icon: string, classes: string) => Readable<boolean>
>("registerPanel");
let visible = registerPanel(id, icon, classes);
</script> </script>
{#if $visible} {#if panelState.activePanel.value === id}
<div class="wrapper" class:hidden> <div class="wrapper" class:hidden>
{#if title} {#if title}
<header> <header>
<h3>{title}</h3> <h3>{title}</h3>
</header> </header>
{/if} {/if}
<slot /> {@render children?.()}
</div> </div>
{/if} {/if}
<style> <style>
header { header {
border-bottom: solid thin var(--outline); border-bottom: solid thin var(--outline);
height: 69px; height: 70px;
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: 1em; 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"> <script lang="ts">
import localStore from "$lib/helpers/localStore"; import { setContext, type Snippet } from "svelte";
import { setContext } from "svelte"; import { PanelState } from "./PanelState.svelte";
import { derived } from "svelte/store";
let panels: Record< const state = new PanelState();
string, setContext("panel-state", state);
{
icon: string;
id: string;
classes: string;
visible?: boolean;
}
> = {};
const activePanel = localStore<keyof typeof panels | false>( const { children } = $props<{ children?: Snippet }>();
"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;
}
}
</script> </script>
<div class="wrapper" class:visible={$activePanel}> <div class="wrapper" class:visible={state.activePanel.value}>
<div class="tabs"> <div class="tabs">
<button <button aria-label="Close" onclick={() => state.toggleOpen()}>
aria-label="Close" <span class="icon-[tabler--settings]"></span>
on:click={() => { <span class="absolute i-[tabler--chevron-left] w-6 h-6 block"></span>
setActivePanel($activePanel ? false : keys[0]);
}}
>
<span class="absolute i-tabler-chevron-left w-6 h-6 block"></span>
</button> </button>
{#each keys as panel (panels[panel].id)} {#each state.keys as panelId (panelId)}
{#if panels[panel].visible !== false} {#if !state.panels[panelId].hidden}
<button <button
aria-label={panel} aria-label={panelId}
class="tab {panels[panel].classes}" class="tab {state.panels[panelId].classes}"
class:active={panel === $activePanel} class:active={panelId === state.activePanel.value}
on:click={() => setActivePanel(panel)} 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> </button>
{/if} {/if}
{/each} {/each}
</div> </div>
<div class="content"> <div class="content">
<slot /> {@render children?.()}
</div> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import "@nodarium/ui/app.css"; import "@nodarium/ui/app.css";
import "virtual:uno.css"; import "../app.css";
import "@unocss/reset/normalize.css"; import type { Snippet } from "svelte";
const { children } = $props<{ children?: Snippet }>();
</script> </script>
<slot /> {@render children?.()}

View File

@@ -2,7 +2,7 @@
import Grid from "$lib/grid"; import Grid from "$lib/grid";
import GraphInterface from "$lib/graph-interface"; import GraphInterface from "$lib/graph-interface";
import * as templates from "$lib/graph-templates"; 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 Viewer from "$lib/result-viewer/Viewer.svelte";
import { import {
appSettings, appSettings,
@@ -42,7 +42,7 @@
appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime, appSettings.value.debug.useWorker ? workerRuntime : memoryRuntime,
); );
let activeNode = $state<Node | undefined>(undefined); let activeNode = $state<NodeInstance | undefined>(undefined);
let scene = $state<Group>(null!); let scene = $state<Group>(null!);
let graph = $state( let graph = $state(
@@ -69,7 +69,7 @@
{ {
key: "r", key: "r",
description: "Regenerate the plant model", description: "Regenerate the plant model",
callback: randomGenerate, callback: () => randomGenerate(),
}, },
]); ]);
let graphSettings = $state<Record<string, any>>({}); let graphSettings = $state<Record<string, any>>({});
@@ -88,13 +88,10 @@
randomSeed: { type: "boolean", value: false }, randomSeed: { type: "boolean", value: false },
}); });
let runIndex = 0;
async function update( async function update(
g: Graph, g: Graph,
s: Record<string, any> = $state.snapshot(graphSettings), s: Record<string, any> = $state.snapshot(graphSettings),
) { ) {
runIndex++;
performanceStore.startRun(); performanceStore.startRun();
try { try {
let a = performance.now(); let a = performance.now();
@@ -181,7 +178,7 @@
onsave={(graph) => handleSave(graph)} onsave={(graph) => handleSave(graph)}
/> />
<Sidebar> <Sidebar>
<Panel id="general" title="General" icon="i-tabler-settings"> <Panel id="general" title="General" icon="i-[tabler--settings]">
<NestedSettings <NestedSettings
id="general" id="general"
bind:value={appSettings.value} bind:value={appSettings.value}
@@ -191,7 +188,7 @@
<Panel <Panel
id="shortcuts" id="shortcuts"
title="Keyboard Shortcuts" title="Keyboard Shortcuts"
icon="i-tabler-keyboard" icon="i-[tabler--keyboard]"
> >
<Keymap <Keymap
keymaps={[ keymaps={[
@@ -200,23 +197,21 @@
]} ]}
/> />
</Panel> </Panel>
<Panel id="exports" title="Exporter" icon="i-tabler-package-export"> <Panel id="exports" title="Exporter" icon="i-[tabler--package-export]">
<ExportSettings {scene} /> <ExportSettings {scene} />
</Panel> </Panel>
<Panel <Panel
id="node-store" id="node-store"
classes="text-green-400"
title="Node Store" title="Node Store"
icon="i-tabler-database" icon="i-[tabler--database] bg-green-400"
> >
<NodeStore registry={nodeRegistry} /> <NodeStore registry={nodeRegistry} />
</Panel> </Panel>
<Panel <Panel
id="performance" id="performance"
title="Performance" title="Performance"
classes="text-red-400"
hidden={!appSettings.value.debug.showPerformancePanel} hidden={!appSettings.value.debug.showPerformancePanel}
icon="i-tabler-brand-speedtest" icon="i-[tabler--brand-speedtest] bg-red-400"
> >
{#if $performanceStore} {#if $performanceStore}
<PerformanceViewer data={$performanceStore} /> <PerformanceViewer data={$performanceStore} />
@@ -226,24 +221,22 @@
id="graph-source" id="graph-source"
title="Graph Source" title="Graph Source"
hidden={!appSettings.value.debug.showGraphJson} hidden={!appSettings.value.debug.showGraphJson}
icon="i-tabler-code" icon="i-[tabler--code]"
> >
<GraphSource {graph} /> <GraphSource graph={graph && manager.serialize()} />
</Panel> </Panel>
<Panel <Panel
id="benchmark" id="benchmark"
title="Benchmark" title="Benchmark"
classes="text-red-400"
hidden={!appSettings.value.debug.showBenchmarkPanel} hidden={!appSettings.value.debug.showBenchmarkPanel}
icon="i-tabler-graph" icon="i-[tabler--graph] bg-red-400"
> >
<BenchmarkPanel run={randomGenerate} /> <BenchmarkPanel run={randomGenerate} />
</Panel> </Panel>
<Panel <Panel
id="graph-settings" id="graph-settings"
title="Graph Settings" title="Graph Settings"
classes="text-blue-400" icon="i-[custom--graph] bg-blue-400"
icon="i-custom-graph"
> >
<NestedSettings <NestedSettings
id="graph-settings" id="graph-settings"
@@ -254,10 +247,9 @@
<Panel <Panel
id="active-node" id="active-node"
title="Node Settings" title="Node Settings"
classes="text-blue-400" icon="i-[tabler--adjustments] bg-blue-400"
icon="i-tabler-adjustments"
> >
<ActiveNodeSettings {manager} node={activeNode} /> <ActiveNodeSettings {manager} bind:node={activeNode} />
</Panel> </Panel>
</Sidebar> </Sidebar>
</Grid.Cell> </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 { sveltekit } from '@sveltejs/kit/vite';
import UnoCSS from 'unocss/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite'
import comlink from 'vite-plugin-comlink'; import comlink from 'vite-plugin-comlink';
import glsl from "vite-plugin-glsl"; import glsl from "vite-plugin-glsl";
import wasm from "vite-plugin-wasm"; import wasm from "vite-plugin-wasm";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
tailwindcss(),
comlink(), comlink(),
UnoCSS(),
sveltekit(), sveltekit(),
glsl(), glsl(),
wasm() 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"] default = ["console_error_panic_hook"]
[dependencies] [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" } utils = { version = "0.1.0", path = "../../../../packages/utils" }
macros = { version = "0.1.0", path = "../../../../packages/macros" } macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = { version = "0.1.7", optional = true } console_error_panic_hook = { version = "0.1.7", optional = true }
web-sys = { version = "0.3.69", features = ["console"] } web-sys = { version = "0.3.69", features = ["console"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{ {
"scripts": { "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'" "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] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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": { "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'" "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::{ 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, split_args, wrap_arg,
}; };
use wasm_bindgen::prelude::*;
include_definition_file!("src/input.json"); nodarium_definition_file!("src/input.json");
#[rustfmt::skip] #[nodarium_execute]
#[wasm_bindgen]
pub fn execute(input: &[i32]) -> Vec<i32> { pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(input); let args = split_args(input);
log!("WASM(cube): input: {:?} -> {:?}", input, args); log!("WASM(cube): input: {:?} -> {:?}", input, args);
@@ -22,7 +19,6 @@ pub fn execute(input: &[i32]) -> Vec<i32> {
let p = encode_float(size); let p = encode_float(size);
let n = encode_float(-size); let n = encode_float(-size);
// [[1,3, x, y, z, x, y,z,x,y,z]]; // [[1,3, x, y, z, x, y,z,x,y,z]];
let mut cube_geometry = [ 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] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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": { "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'" "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::{ use nodarium_utils::{
concat_arg_vecs, evaluate_float, evaluate_int, concat_arg_vecs, evaluate_float, evaluate_int,
geometry::{ geometry::{
create_path, interpolate_along_path, rotate_vector_by_angle, wrap_path, wrap_path_mut, 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 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> { pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(input); let args = split_args(input);
let paths = split_args(args[0]); 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] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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": { "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'" "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 nodarium_macros::nodarium_definition_file;
use wasm_bindgen::prelude::*; 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> { pub fn execute(args: &[i32]) -> Vec<i32> {
args.into() 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] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] } glam = "0.30.10"
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": { "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'" "dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
} }
} }

View File

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

@@ -1,5 +1,5 @@
[package] [package]
name = "nodarium_instance" name = "instance"
version = "0.1.0" version = "0.1.0"
authors = ["Max Richter <jim-x@web.de>"] authors = ["Max Richter <jim-x@web.de>"]
edition = "2018" edition = "2018"
@@ -7,23 +7,7 @@ edition = "2018"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] } glam = "0.30.10"
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": { "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'" "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 glam::{Mat4, Quat, Vec3};
use nodarium_macros::include_definition_file; use nodarium_macros::nodarium_execute;
use nodarium_macros::nodarium_definition_file;
use nodarium_utils::{ use nodarium_utils::{
concat_args, encode_float, evaluate_float, evaluate_int, concat_args, evaluate_float, evaluate_int,
geometry::{ 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> { pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(input); let args = split_args(input);
let mut inputs = split_args(args[0]); let mut inputs = split_args(args[0]);
log!("WASM(instance): inputs: {:?}", inputs); 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] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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": { "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'" "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_macros::nodarium_definition_file;
use nodarium_utils::{concat_args, set_panic_hook, split_args}; use nodarium_macros::nodarium_execute;
use wasm_bindgen::prelude::*; use nodarium_utils::{
concat_args, split_args
};
include_definition_file!("src/input.json"); #[nodarium_execute]
#[wasm_bindgen]
pub fn execute(args: &[i32]) -> Vec<i32> { pub fn execute(args: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(args); let args = split_args(args);
concat_args(vec![&[0], args[0], args[1], args[2]]) 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] [package]
name = "nodes-noise" name = "noise"
version = "0.1.0" version = "0.1.0"
authors = ["Max Richter <jim-x@web.de>"] authors = ["Max Richter <jim-x@web.de>"]
edition = "2018" edition = "2018"
@@ -7,24 +7,8 @@ edition = "2018"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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" noise = "0.9.0"
glam = "0.27.0"
[dev-dependencies]
wasm-bindgen-test = "0.3.34"

View File

@@ -1,6 +1,6 @@
{ {
"scripts": { "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'" "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::{ use nodarium_utils::{
concat_args, evaluate_float, evaluate_int, evaluate_vec3, geometry::wrap_path_mut, log, concat_args, evaluate_float, evaluate_int, evaluate_vec3, geometry::wrap_path_mut,
reset_call_count, set_panic_hook, split_args, reset_call_count, split_args,
}; };
use noise::{HybridMulti, MultiFractal, NoiseFn, OpenSimplex}; 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 { fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + t * (b - a) a + t * (b - a)
} }
#[wasm_bindgen] #[nodarium_execute]
pub fn execute(input: &[i32]) -> Vec<i32> { pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
reset_call_count(); reset_call_count();
let args = split_args(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,24 +7,6 @@ edition = "2018"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
console_error_panic_hook = ["dep:console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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": { "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'" "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::{ use nodarium_utils::{
concat_args, evaluate_int, concat_args, evaluate_int,
geometry::{extrude_path, wrap_path}, 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> { pub fn execute(input: &[i32]) -> Vec<i32> {
set_panic_hook();
log!("WASM(output): input: {:?}", input); log!("WASM(output): input: {:?}", input);
let args = split_args(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] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } 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": { "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'" "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_macros::nodarium_definition_file;
use nodarium_utils::{concat_args, set_panic_hook, split_args}; use nodarium_macros::nodarium_execute;
use wasm_bindgen::prelude::*; 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> { pub fn execute(args: &[i32]) -> Vec<i32> {
set_panic_hook();
let args = split_args(args); let args = split_args(args);
concat_args(vec![&[1], args[0], args[1], args[2]]) concat_args(vec![&[1], args[0], args[1], args[2]])
} }

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,8 @@ edition = "2018"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies] [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_utils = { version = "0.1.0", path = "../../../../packages/utils" }
nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4" glam = "0.30.10"
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": { "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/rotate.wasm ./pkg/node.wasm",
"dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'" "dev": "cargo watch -s 'wasm-pack build --dev --out-name index --no-default-features'"
} }
} }

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