feat: add flat_tree allgorithm
This commit is contained in:
parent
2ed1501747
commit
e2940183f1
@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"test": "vitest",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri:dev": "tauri dev",
|
"tauri:dev": "tauri dev",
|
||||||
"story:dev": "histoire dev",
|
"story:dev": "histoire dev",
|
||||||
@ -40,6 +41,7 @@
|
|||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.4",
|
||||||
"vite-plugin-glsl": "^1.2.1",
|
"vite-plugin-glsl": "^1.2.1",
|
||||||
"vite-plugin-wasm": "^3.3.0"
|
"vite-plugin-wasm": "^3.3.0",
|
||||||
|
"vitest": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
61
app/src/lib/helpers/flat_tree.test.ts
Normal file
61
app/src/lib/helpers/flat_tree.test.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { expect, test } from 'vitest'
|
||||||
|
import { decode, encode } from './flat_tree'
|
||||||
|
|
||||||
|
// Original test case
|
||||||
|
test('it correctly decodes/encodes complex nested arrays', () => {
|
||||||
|
const input = [5, [6, 1], 1, 5, [5], [7, 2, [5, 1]]];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with empty array
|
||||||
|
test('it correctly handles an empty array', () => {
|
||||||
|
const input: number[] = [];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with nested empty arrays
|
||||||
|
test('it correctly handles nested empty arrays', () => {
|
||||||
|
const input = [5, [], [6, []], []];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with single-element array
|
||||||
|
test('it correctly handles a single-element array', () => {
|
||||||
|
const input = [42];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with deeply nested array
|
||||||
|
test('it correctly handles deeply nested arrays', () => {
|
||||||
|
const input = [[[[[1]]]]];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with large numbers
|
||||||
|
test('it correctly handles large numbers', () => {
|
||||||
|
const input = [2147483647, [-2147483648, 1234567890]];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with sequential nesting
|
||||||
|
test('it correctly handles sequential nesting', () => {
|
||||||
|
const input = [1, [2, [3, [4, [5]]]]];
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test with mixed data types (if supported)
|
||||||
|
// Note: This test assumes your implementation supports mixed types.
|
||||||
|
// If not, you can ignore or remove this test.
|
||||||
|
test('it correctly handles arrays with mixed data types', () => {
|
||||||
|
const input = [1, 'text', [true, [null, ['another text']]]];
|
||||||
|
// @ts-ignore
|
||||||
|
const decoded = decode(encode(input));
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
60
app/src/lib/helpers/flat_tree.ts
Normal file
60
app/src/lib/helpers/flat_tree.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
|
||||||
|
|
||||||
|
// Encodes a nested array into a flat array with bracket and distance notation
|
||||||
|
export function encode(array: SparseArray): number[] {
|
||||||
|
const encoded = [0, 0]; // Initialize encoded array with root bracket notation
|
||||||
|
let missingBracketIndex = 1; // Track where to insert the distance to the next bracket
|
||||||
|
|
||||||
|
for (let index = 0; index < array.length; index++) {
|
||||||
|
const item = array[index];
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
// Update the distance to the next bracket for the last opened bracket
|
||||||
|
encoded[missingBracketIndex] = encoded.length - missingBracketIndex;
|
||||||
|
if (item.length === 0) {
|
||||||
|
// Handle empty arrays by directly adding bracket notation
|
||||||
|
encoded.push(0, 1, 1, 1);
|
||||||
|
} else {
|
||||||
|
// Recursively encode non-empty arrays
|
||||||
|
const child = encode(item);
|
||||||
|
encoded.push(...child, 1, 0); // Note: The trailing comma after 0 can be removed
|
||||||
|
}
|
||||||
|
// Update missingBracketIndex to the position of the newly added bracket
|
||||||
|
missingBracketIndex = encoded.length - 1;
|
||||||
|
} else {
|
||||||
|
// Handle non-array items
|
||||||
|
encoded.push(item);
|
||||||
|
// Update the distance for the last opened bracket
|
||||||
|
if (missingBracketIndex) encoded[missingBracketIndex] = index + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
};
|
||||||
|
|
||||||
|
function decode_recursive(dense: number[], index = 0) {
|
||||||
|
const decoded: (number | number[])[] = [];
|
||||||
|
let nextBracketIndex = dense[index + 1] + index + 1; // Calculate the index of the next bracket
|
||||||
|
|
||||||
|
index += 2; // Skip the initial bracket notation
|
||||||
|
while (index < dense.length) {
|
||||||
|
if (index === nextBracketIndex) {
|
||||||
|
if (dense[index] === 0) { // Opening bracket detected
|
||||||
|
const [p, nextIndex, _nextBracketIndex] = decode_recursive(dense, index);
|
||||||
|
decoded.push(p);
|
||||||
|
index = nextIndex + 1;
|
||||||
|
nextBracketIndex = _nextBracketIndex;
|
||||||
|
} else { // Closing bracket detected
|
||||||
|
nextBracketIndex = dense[index + 1] + index + 1;
|
||||||
|
return [decoded, index, nextBracketIndex] as const;
|
||||||
|
}
|
||||||
|
} else if (index < nextBracketIndex) {
|
||||||
|
decoded.push(dense[index]); // Add regular number to decoded array
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return [decoded, index, nextBracketIndex] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(dense: number[]) {
|
||||||
|
return decode_recursive(dense, 0)[0];
|
||||||
|
}
|
@ -41,6 +41,8 @@ const nodeTypes: NodeType[] = [
|
|||||||
const log = createLogger("node-registry");
|
const log = createLogger("node-registry");
|
||||||
export class RemoteNodeRegistry implements NodeRegistry {
|
export class RemoteNodeRegistry implements NodeRegistry {
|
||||||
|
|
||||||
|
|
||||||
|
status: "loading" | "ready" | "error" = "loading";
|
||||||
private nodes: Map<string, NodeType> = new Map();
|
private nodes: Map<string, NodeType> = new Map();
|
||||||
|
|
||||||
constructor(private url: string) { }
|
constructor(private url: string) { }
|
||||||
@ -55,6 +57,7 @@ export class RemoteNodeRegistry implements NodeRegistry {
|
|||||||
const wasmResponse = await fetch(`${nodeUrl}/wasm`);
|
const wasmResponse = await fetch(`${nodeUrl}/wasm`);
|
||||||
const wrapperReponse = await fetch(`${nodeUrl}/wrapper`);
|
const wrapperReponse = await fetch(`${nodeUrl}/wrapper`);
|
||||||
if (!wrapperReponse.ok) {
|
if (!wrapperReponse.ok) {
|
||||||
|
this.status = "error";
|
||||||
throw new Error(`Failed to load node ${id}`);
|
throw new Error(`Failed to load node ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +71,7 @@ wasm = val;`);
|
|||||||
wasmWrapper.__wbg_set_wasm(instance.exports);
|
wasmWrapper.__wbg_set_wasm(instance.exports);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
this.status = "error";
|
||||||
throw new Error(`Failed to load node ${id}`);
|
throw new Error(`Failed to load node ${id}`);
|
||||||
}
|
}
|
||||||
const node = await response.json();
|
const node = await response.json();
|
||||||
@ -78,6 +82,7 @@ wasm = val;`);
|
|||||||
const duration = performance.now() - a;
|
const duration = performance.now() - a;
|
||||||
|
|
||||||
log.log("loaded nodes in", duration, "ms");
|
log.log("loaded nodes in", duration, "ms");
|
||||||
|
this.status = "ready";
|
||||||
}
|
}
|
||||||
|
|
||||||
getNode(id: string) {
|
getNode(id: string) {
|
||||||
|
@ -6,6 +6,11 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
|||||||
constructor(private registry: NodeRegistry) { }
|
constructor(private registry: NodeRegistry) { }
|
||||||
|
|
||||||
private getNodeTypes(graph: Graph) {
|
private getNodeTypes(graph: Graph) {
|
||||||
|
|
||||||
|
if (this.registry.status !== "ready") {
|
||||||
|
throw new Error("Node registry is not ready");
|
||||||
|
}
|
||||||
|
|
||||||
const typeMap = new Map<string, NodeType>();
|
const typeMap = new Map<string, NodeType>();
|
||||||
for (const node of graph.nodes) {
|
for (const node of graph.nodes) {
|
||||||
if (!typeMap.has(node.type)) {
|
if (!typeMap.has(node.type)) {
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Grid from "$lib/grid";
|
import Grid from "$lib/grid";
|
||||||
import Graph from "@nodes/graph-interface";
|
import GraphInterface from "@nodes/graph-interface";
|
||||||
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
import { MemoryRuntimeExecutor } from "$lib/runtime-executor";
|
||||||
import { MemoryNodeRegistry, RemoteNodeRegistry } from "$lib/node-registry";
|
import { MemoryNodeRegistry, RemoteNodeRegistry } from "$lib/node-registry";
|
||||||
import * as templates from "$lib/graph-templates";
|
import * as templates from "$lib/graph-templates";
|
||||||
|
import type { Graph } from "@nodes/types";
|
||||||
|
|
||||||
const memNodeRegistry = new MemoryNodeRegistry();
|
|
||||||
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
const nodeRegistry = new RemoteNodeRegistry("http://localhost:3001");
|
||||||
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
const runtimeExecutor = new MemoryRuntimeExecutor(nodeRegistry);
|
||||||
|
|
||||||
let res = 0;
|
let res = "2";
|
||||||
|
let time = 0;
|
||||||
|
|
||||||
let graph = localStorage.getItem("graph")
|
let graph = localStorage.getItem("graph")
|
||||||
? JSON.parse(localStorage.getItem("graph")!)
|
? JSON.parse(localStorage.getItem("graph")!)
|
||||||
: templates.grid(3, 3);
|
: templates.grid(3, 3);
|
||||||
|
|
||||||
function handleResult(event) {
|
function handleResult(event: CustomEvent<Graph>) {
|
||||||
console.log("Res", event);
|
let a = performance.now();
|
||||||
res = runtimeExecutor.execute(event.detail);
|
res = runtimeExecutor.execute(event.detail);
|
||||||
|
time = performance.now() - a;
|
||||||
console.log(res);
|
console.log(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave(event) {
|
function handleSave(event: CustomEvent<Graph>) {
|
||||||
localStorage.setItem("graph", JSON.stringify(event.detail));
|
localStorage.setItem("graph", JSON.stringify(event.detail));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -30,11 +32,13 @@
|
|||||||
<header>header</header>
|
<header>header</header>
|
||||||
<Grid.Row>
|
<Grid.Row>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
{res}
|
result: {res}
|
||||||
|
<br />
|
||||||
|
time: {time}ms
|
||||||
</Grid.Cell>
|
</Grid.Cell>
|
||||||
<Grid.Cell>
|
<Grid.Cell>
|
||||||
{#key graph}
|
{#key graph}
|
||||||
<Graph
|
<GraphInterface
|
||||||
registry={nodeRegistry}
|
registry={nodeRegistry}
|
||||||
{graph}
|
{graph}
|
||||||
on:result={handleResult}
|
on:result={handleResult}
|
||||||
|
20
app/src/routes/test/+page.svelte
Normal file
20
app/src/routes/test/+page.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { decode, encode } from "$lib/helpers/flat_tree";
|
||||||
|
|
||||||
|
const input = [5, [6, 1], [7, 2, [5, 1]]];
|
||||||
|
// const input = [5, [], [6, []], []];
|
||||||
|
// const input = [52];
|
||||||
|
|
||||||
|
console.log("INPUT");
|
||||||
|
console.log(input);
|
||||||
|
|
||||||
|
const encoded = encode(input);
|
||||||
|
console.log("ENCODED");
|
||||||
|
console.log(encoded);
|
||||||
|
|
||||||
|
const decoded = decode(encoded);
|
||||||
|
console.log("DECODED");
|
||||||
|
console.log(decoded);
|
||||||
|
|
||||||
|
console.log("EQUALS", JSON.stringify(input) === JSON.stringify(decoded));
|
||||||
|
</script>
|
@ -45,6 +45,11 @@ export type Socket = {
|
|||||||
|
|
||||||
|
|
||||||
export interface NodeRegistry {
|
export interface NodeRegistry {
|
||||||
|
/**
|
||||||
|
* The status of the node registry
|
||||||
|
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
|
||||||
|
*/
|
||||||
|
status: "loading" | "ready" | "error";
|
||||||
/**
|
/**
|
||||||
* Load the nodes with the given ids
|
* Load the nodes with the given ids
|
||||||
* @param nodeIds - The ids of the nodes to load
|
* @param nodeIds - The ids of the nodes to load
|
||||||
@ -67,6 +72,11 @@ export interface NodeRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeExecutor {
|
export interface RuntimeExecutor {
|
||||||
|
/**
|
||||||
|
* Execute the given graph
|
||||||
|
* @param graph - The graph to execute
|
||||||
|
* @returns The result of the execution
|
||||||
|
*/
|
||||||
execute: (graph: Graph) => void;
|
execute: (graph: Graph) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,9 @@ importers:
|
|||||||
vite-plugin-wasm:
|
vite-plugin-wasm:
|
||||||
specifier: ^3.3.0
|
specifier: ^3.3.0
|
||||||
version: 3.3.0(vite@5.1.4)
|
version: 3.3.0(vite@5.1.4)
|
||||||
|
vitest:
|
||||||
|
specifier: ^1.2.0
|
||||||
|
version: 1.4.0
|
||||||
|
|
||||||
nodes/max/plantarium/float: {}
|
nodes/max/plantarium/float: {}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user