1 Commits

Author SHA1 Message Date
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
13 changed files with 113 additions and 236 deletions

View File

@@ -16,7 +16,7 @@
inView: boolean;
z: number;
};
let { node, inView, z }: Props = $props();
let { node = $bindable(), inView, z }: Props = $props();
const isActive = $derived(graphState.activeNodeId === node.id);
const isSelected = $derived(graphState.selectedNodes.has(node.id));
@@ -67,4 +67,4 @@
/>
</T.Mesh>
<NodeHtml {node} {inView} {isActive} {isSelected} {z} />
<NodeHtml bind:node {inView} {isActive} {isSelected} {z} />

View File

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

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { getGraphState } from "../graph/state.svelte.js";
import { createNodePath } from "../helpers/index.js";
import type { NodeInstance, SerializedNode } from "@nodarium/types";
import type { NodeInstance } from "@nodarium/types";
const graphState = getGraphState();
const { node }: { node: NodeInstance | SerializedNode } = $props();
const { node }: { node: NodeInstance } = $props();
function handleMouseDown(event: MouseEvent) {
event.stopPropagation();
@@ -20,8 +20,7 @@
}
const cornerTop = 10;
const rightBump =
"state" in node ? !!node?.state?.type?.outputs?.length : false;
const rightBump = !!node?.state?.type?.outputs?.length;
const aspectRatio = 0.25;
const path = createNodePath({

View File

@@ -1,16 +1,12 @@
<script lang="ts">
import type {
NodeInput as NodeInputType,
NodeInstance,
SerializedNode,
} from "@nodarium/types";
import type { NodeInput, NodeInstance } from "@nodarium/types";
import { createNodePath } from "../helpers/index.js";
import NodeInput from "./NodeInput.svelte";
import NodeInputEl from "./NodeInput.svelte";
import { getGraphManager, getGraphState } from "../graph/state.svelte.js";
type Props = {
node: NodeInstance | SerializedNode;
input: NodeInputType;
node: NodeInstance;
input: NodeInput;
id: string;
isLast?: boolean;
};
@@ -80,7 +76,7 @@
<label for={elementId}>{input.label || id}</label>
{/if}
{#if inputType.external !== true}
<NodeInput {graph} {elementId} bind:node {input} {id} />
<NodeInputEl {graph} {elementId} bind:node {input} {id} />
{/if}
</div>

View File

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

View File

@@ -1,17 +1,17 @@
<script lang="ts">
import NodeHtml from "$lib/graph-interface/node/NodeHTML.svelte";
import type { SerializedNode } from "@nodarium/types";
import type { NodeDefinition, NodeId, NodeInstance } from "@nodarium/types";
const { node }: { node: SerializedNode } = $props();
const { node }: { node: NodeDefinition } = $props();
let dragging = $state(false);
let nodeData = $state({
let nodeData = $state<NodeInstance>({
id: 0,
type: node?.id,
type: node.id as unknown as NodeId,
position: [0, 0] as [number, number],
props: {},
tmp: {
state: {
type: node,
},
});
@@ -46,7 +46,7 @@
tabindex="0"
ondragstart={handleDragStart}
>
<NodeHtml inView={true} position={"relative"} z={5} bind:node={nodeData} />
<NodeHtml bind:node={nodeData} inView={true} position={"relative"} z={5} />
</div>
</div>

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,13 @@ import { MemoryRuntimeExecutor } from "./runtime-executor";
import { RemoteNodeRegistry, IndexDBCache } from "@nodarium/registry";
import type { Graph } from "@nodarium/types";
import { createPerformanceStore } from "@nodarium/utils";
import { MemoryRuntimeCache } from "./runtime-executor-cache";
const indexDbCache = new IndexDBCache("node-registry");
const nodeRegistry = new RemoteNodeRegistry("", indexDbCache);
const executor = new MemoryRuntimeExecutor(nodeRegistry);
const cache = new MemoryRuntimeCache()
const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
const performanceStore = createPerformanceStore();
executor.perf = performanceStore;

View File

@@ -88,13 +88,10 @@
randomSeed: { type: "boolean", value: false },
});
let runIndex = 0;
async function update(
g: Graph,
s: Record<string, any> = $state.snapshot(graphSettings),
) {
runIndex++;
performanceStore.startRun();
try {
let a = performance.now();

View File

@@ -1,5 +1,5 @@
import { test, expect } from 'vitest';
import { fastHashArray, fastHashString } from './fastHash';
import { fastHashArrayBuffer, fastHashString } from './fastHash';
test('fastHashString doesnt produce clashes', () => {
const hashA = fastHashString('abcdef');
@@ -14,10 +14,10 @@ test("fastHashArray doesnt product collisions", () => {
const a = new Int32Array(1000);
const hash_a = fastHashArray(a.buffer);
const hash_a = fastHashArrayBuffer(a);
a[0] = 1;
const hash_b = fastHashArray(a.buffer);
const hash_b = fastHashArrayBuffer(a);
expect(hash_a).not.toEqual(hash_b);
@@ -28,13 +28,13 @@ test('fastHashArray is fast(ish) < 20ms', () => {
const a = new Int32Array(10_000);
const t0 = performance.now();
fastHashArray(a.buffer);
fastHashArrayBuffer(a);
const t1 = performance.now();
a[0] = 1;
fastHashArray(a.buffer);
fastHashArrayBuffer(a);
const t2 = performance.now();
@@ -48,7 +48,7 @@ test('fastHashArray is deterministic', () => {
a[42] = 69;
const b = new Int32Array(1000);
b[42] = 69;
const hashA = fastHashArray(a.buffer);
const hashB = fastHashArray(b.buffer);
const hashA = fastHashArrayBuffer(a);
const hashB = fastHashArrayBuffer(b);
expect(hashA).toEqual(hashB);
});

View File

@@ -1,156 +1,45 @@
// https://github.com/6502/sha256/blob/main/sha256.js
function sha256(data?: string | Int32Array) {
let h0 = 0x6a09e667,
h1 = 0xbb67ae85,
h2 = 0x3c6ef372,
h3 = 0xa54ff53a,
h4 = 0x510e527f,
h5 = 0x9b05688c,
h6 = 0x1f83d9ab,
h7 = 0x5be0cd19,
tsz = 0,
bp = 0;
const k = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
],
rrot = (x: number, n: number) => (x >>> n) | (x << (32 - n)),
w = new Uint32Array(64),
buf = new Uint8Array(64),
process = () => {
for (let j = 0, r = 0; j < 16; j++, r += 4) {
w[j] =
(buf[r] << 24) | (buf[r + 1] << 16) | (buf[r + 2] << 8) | buf[r + 3];
}
for (let j = 16; j < 64; j++) {
let s0 = rrot(w[j - 15], 7) ^ rrot(w[j - 15], 18) ^ (w[j - 15] >>> 3);
let s1 = rrot(w[j - 2], 17) ^ rrot(w[j - 2], 19) ^ (w[j - 2] >>> 10);
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) | 0;
}
let a = h0,
b = h1,
c = h2,
d = h3,
e = h4,
f = h5,
g = h6,
h = h7;
for (let j = 0; j < 64; j++) {
let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25),
ch = (e & f) ^ (~e & g),
t1 = (h + S1 + ch + k[j] + w[j]) | 0,
S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22),
maj = (a & b) ^ (a & c) ^ (b & c),
t2 = (S0 + maj) | 0;
h = g;
g = f;
f = e;
e = (d + t1) | 0;
d = c;
c = b;
b = a;
a = (t1 + t2) | 0;
}
h0 = (h0 + a) | 0;
h1 = (h1 + b) | 0;
h2 = (h2 + c) | 0;
h3 = (h3 + d) | 0;
h4 = (h4 + e) | 0;
h5 = (h5 + f) | 0;
h6 = (h6 + g) | 0;
h7 = (h7 + h) | 0;
bp = 0;
},
add = (input: string | Int32Array) => {
const data =
typeof input === "string"
? typeof TextEncoder === "undefined"
? //@ts-ignore
Buffer.from(input)
: new TextEncoder().encode(input)
: input;
export function fastHashArrayBuffer(input: string | Int32Array): string {
const mask = (1n << 64n) - 1n
for (let i = 0; i < data.length; i++) {
buf[bp++] = data[i];
if (bp === 64) process();
}
tsz += data.length;
},
digest = () => {
buf[bp++] = 0x80;
if (bp == 64) process();
if (bp + 8 > 64) {
while (bp < 64) buf[bp++] = 0x00;
process();
}
while (bp < 58) buf[bp++] = 0x00;
// Max number of bytes is 35,184,372,088,831
let L = tsz * 8;
buf[bp++] = (L / 1099511627776) & 255;
buf[bp++] = (L / 4294967296) & 255;
buf[bp++] = L >>> 24;
buf[bp++] = (L >>> 16) & 255;
buf[bp++] = (L >>> 8) & 255;
buf[bp++] = L & 255;
process();
let reply = new Uint8Array(32);
reply[0] = h0 >>> 24;
reply[1] = (h0 >>> 16) & 255;
reply[2] = (h0 >>> 8) & 255;
reply[3] = h0 & 255;
reply[4] = h1 >>> 24;
reply[5] = (h1 >>> 16) & 255;
reply[6] = (h1 >>> 8) & 255;
reply[7] = h1 & 255;
reply[8] = h2 >>> 24;
reply[9] = (h2 >>> 16) & 255;
reply[10] = (h2 >>> 8) & 255;
reply[11] = h2 & 255;
reply[12] = h3 >>> 24;
reply[13] = (h3 >>> 16) & 255;
reply[14] = (h3 >>> 8) & 255;
reply[15] = h3 & 255;
reply[16] = h4 >>> 24;
reply[17] = (h4 >>> 16) & 255;
reply[18] = (h4 >>> 8) & 255;
reply[19] = h4 & 255;
reply[20] = h5 >>> 24;
reply[21] = (h5 >>> 16) & 255;
reply[22] = (h5 >>> 8) & 255;
reply[23] = h5 & 255;
reply[24] = h6 >>> 24;
reply[25] = (h6 >>> 16) & 255;
reply[26] = (h6 >>> 8) & 255;
reply[27] = h6 & 255;
reply[28] = h7 >>> 24;
reply[29] = (h7 >>> 16) & 255;
reply[30] = (h7 >>> 8) & 255;
reply[31] = h7 & 255;
let res = "";
reply.forEach((x) => (res += ("0" + x.toString(16)).slice(-2)));
return res;
};
// FNV-1a 64-bit constants
let h = 0xcbf29ce484222325n // offset basis
const FNV_PRIME = 0x100000001b3n
if (data) add(data);
return { add, digest };
// get bytes for string or Int32Array
let bytes: Uint8Array
if (typeof input === "string") {
// utf-8 encoding
bytes = new TextEncoder().encode(input)
} else {
// Int32Array -> bytes (little-endian)
bytes = new Uint8Array(input.length * 4)
for (let i = 0; i < input.length; i++) {
const v = input[i] >>> 0 // ensure unsigned 32-bit
const base = i * 4
bytes[base] = v & 0xff
bytes[base + 1] = (v >>> 8) & 0xff
bytes[base + 2] = (v >>> 16) & 0xff
bytes[base + 3] = (v >>> 24) & 0xff
}
}
export function fastHashArrayBuffer(buffer: string | Int32Array): string {
return sha256(buffer).digest();
// FNV-1a byte-wise
for (let i = 0; i < bytes.length; i++) {
h = (h ^ BigInt(bytes[i])) & mask
h = (h * FNV_PRIME) & mask
}
// MurmurHash3's fmix64 finalizer (good avalanche)
h ^= h >> 33n
h = (h * 0xff51afd7ed558ccdn) & mask
h ^= h >> 33n
h = (h * 0xc4ceb9fe1a85ec53n) & mask
h ^= h >> 33n
// to 16-char hex
return h.toString(16).padStart(16, "0").slice(-16)
}
// Shamelessly copied from
// https://stackoverflow.com/a/8831937
export function fastHashString(input: string) {
if (input.length === 0) return 0;
@@ -162,20 +51,3 @@ export function fastHashString(input: string) {
return hash;
}
export function fastHash(input: (string | Int32Array | number)[]) {
const s = sha256();
for (let i = 0; i < input.length; i++) {
const v = input[i];
if (typeof v === "string") {
s.add(v);
} else if (v instanceof Int32Array) {
s.add(v);
} else {
s.add(v.toString());
}
}
return s.digest();
}