feat: improve performance by cachine edges
This commit is contained in:
parent
2df3035855
commit
5f2b2f59be
@ -21,7 +21,9 @@
|
||||
"@threlte/flex": "^1.0.1",
|
||||
"@types/three": "^0.159.0",
|
||||
"jsondiffpatch": "^0.6.0",
|
||||
"three": "^0.159.0"
|
||||
"meshline": "^3.2.0",
|
||||
"three": "^0.159.0",
|
||||
"three.meshline": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@histoire/plugin-svelte": "^0.17.9",
|
||||
|
@ -1,4 +1,4 @@
|
||||
<script context="module">
|
||||
<script context="module" lang="ts">
|
||||
const color = new Color(0x202020);
|
||||
color.convertLinearToSRGB();
|
||||
|
||||
@ -9,63 +9,72 @@
|
||||
color,
|
||||
toneMapped: false,
|
||||
});
|
||||
|
||||
const lineCache = new Map<number, BufferGeometry>();
|
||||
|
||||
const curve = new CubicBezierCurve(
|
||||
new Vector2(0, 0),
|
||||
new Vector2(0, 0),
|
||||
new Vector2(0, 0),
|
||||
new Vector2(0, 0),
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { T } from "@threlte/core";
|
||||
import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras";
|
||||
import { MeshBasicMaterial, type Mesh, LineBasicMaterial } from "three";
|
||||
import { MeshLineMaterial } from "@threlte/extras";
|
||||
import { BufferGeometry, MeshBasicMaterial, Vector3 } from "three";
|
||||
import { Color } from "three/src/math/Color.js";
|
||||
import { CubicBezierCurve } from "three/src/extras/curves/CubicBezierCurve.js";
|
||||
import { Vector3 } from "three/src/math/Vector3.js";
|
||||
import { Vector2 } from "three/src/math/Vector2.js";
|
||||
import { createEdgeGeometry } from "./createEdgeGeometry";
|
||||
|
||||
export let from: { x: number; y: number };
|
||||
export let to: { x: number; y: number };
|
||||
|
||||
let samples = 5;
|
||||
|
||||
const curve = new CubicBezierCurve(
|
||||
new Vector2(from.x, from.y),
|
||||
new Vector2(from.x + 2, from.y),
|
||||
new Vector2(to.x - 2, to.y),
|
||||
new Vector2(to.x, to.y),
|
||||
);
|
||||
let geometry: BufferGeometry;
|
||||
|
||||
let points: Vector3[] = [];
|
||||
let lastId: number | null = null;
|
||||
|
||||
let last_from_x = 0;
|
||||
let last_from_y = 0;
|
||||
const primeA = 31;
|
||||
const primeB = 37;
|
||||
|
||||
let mesh: Mesh;
|
||||
|
||||
export const update = function (force = false) {
|
||||
if (!force) {
|
||||
const new_x = from.x + to.x;
|
||||
const new_y = from.y + to.y;
|
||||
if (last_from_x === new_x && last_from_y === new_y) {
|
||||
return;
|
||||
}
|
||||
last_from_x = new_x;
|
||||
last_from_y = new_y;
|
||||
export const update = function () {
|
||||
const new_x = to.x - from.x;
|
||||
const new_y = to.y - from.y;
|
||||
const curveId = new_x * primeA + new_y * primeB;
|
||||
if (lastId === curveId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mid = new Vector2((from.x + to.x) / 2, (from.y + to.y) / 2);
|
||||
const mid = new Vector2(new_x / 2, new_y / 2);
|
||||
|
||||
if (lineCache.has(curveId)) {
|
||||
geometry = lineCache.get(curveId)!;
|
||||
return;
|
||||
}
|
||||
|
||||
const length = Math.floor(
|
||||
Math.sqrt(Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2)) / 4,
|
||||
Math.sqrt(Math.pow(new_x, 2) + Math.pow(new_y, 2)) / 4,
|
||||
);
|
||||
samples = Math.min(Math.max(10, length), 100);
|
||||
samples = Math.min(Math.max(10, length), 60);
|
||||
|
||||
curve.v0.set(from.x, from.y);
|
||||
curve.v1.set(mid.x, from.y);
|
||||
curve.v2.set(mid.x, to.y);
|
||||
curve.v3.set(to.x, to.y);
|
||||
curve.v0.set(0, 0);
|
||||
curve.v1.set(mid.x, 0);
|
||||
curve.v2.set(mid.x, new_y);
|
||||
curve.v3.set(new_x, new_y);
|
||||
|
||||
points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y));
|
||||
const points = curve
|
||||
.getPoints(samples)
|
||||
.map((p) => new Vector3(p.x, 0, p.y))
|
||||
.flat();
|
||||
|
||||
geometry = createEdgeGeometry(points);
|
||||
lineCache.set(curveId, geometry);
|
||||
};
|
||||
|
||||
update();
|
||||
$: if (from || to) {
|
||||
update();
|
||||
}
|
||||
@ -91,14 +100,13 @@
|
||||
<T.CircleGeometry args={[0.3, 16]} />
|
||||
</T.Mesh>
|
||||
|
||||
<T.Mesh position.y={0.5} bind:ref={mesh}>
|
||||
{#key samples}
|
||||
<MeshLineGeometry {points} />
|
||||
{/key}
|
||||
<MeshLineMaterial
|
||||
width={4}
|
||||
attenuate={false}
|
||||
color={color2}
|
||||
toneMapped={false}
|
||||
/>
|
||||
</T.Mesh>
|
||||
{#if geometry}
|
||||
<T.Mesh position.x={from.x} position.z={from.y} position.y={0.1} {geometry}>
|
||||
<MeshLineMaterial
|
||||
width={4}
|
||||
attenuate={false}
|
||||
color={color2}
|
||||
toneMapped={false}
|
||||
/>
|
||||
</T.Mesh>
|
||||
{/if}
|
||||
|
100
frontend/src/lib/components/edges/createEdgeGeometry.ts
Normal file
100
frontend/src/lib/components/edges/createEdgeGeometry.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { BufferGeometry, Vector3, BufferAttribute } from 'three'
|
||||
import { setXY, setXYZ, setXYZW, setXYZXYZ } from './utils'
|
||||
|
||||
|
||||
export function createEdgeGeometry(points: Vector3[]) {
|
||||
|
||||
let shape = 'none'
|
||||
let shapeFunction = (p: number) => 1
|
||||
|
||||
// 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
|
||||
|
||||
if (shape === 'taper') {
|
||||
shapeFunction = (p: number) => 1 * Math.pow(4 * p * (1 - p), 1)
|
||||
}
|
||||
|
||||
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 = shape === 'none' ? 1 : 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;
|
||||
|
||||
}
|
34
frontend/src/lib/components/edges/utils.ts
Normal file
34
frontend/src/lib/components/edges/utils.ts
Normal file
@ -0,0 +1,34 @@
|
||||
export const setXYZXYZ = (array: number[], location: number, x: number, y: number, z: number) => {
|
||||
array[location + 0] = x
|
||||
array[location + 1] = y
|
||||
array[location + 2] = z
|
||||
|
||||
array[location + 3] = x
|
||||
array[location + 4] = y
|
||||
array[location + 5] = z
|
||||
}
|
||||
|
||||
export const setXY = (array: number[], location: number, x: number, y: number) => {
|
||||
array[location + 0] = x
|
||||
array[location + 1] = y
|
||||
}
|
||||
|
||||
export const setXYZ = (array: number[], location: number, x: number, y: number, z: number) => {
|
||||
array[location + 0] = x
|
||||
array[location + 1] = y
|
||||
array[location + 2] = z
|
||||
}
|
||||
|
||||
export const setXYZW = (
|
||||
array: number[],
|
||||
location: number,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
w: number
|
||||
) => {
|
||||
array[location + 0] = x
|
||||
array[location + 1] = y
|
||||
array[location + 2] = z
|
||||
array[location + 3] = w
|
||||
}
|
@ -749,9 +749,7 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#key $graphId}
|
||||
<GraphView {nodes} {edges} {cameraPosition} />
|
||||
{/key}
|
||||
<GraphView {nodes} {edges} {cameraPosition} />
|
||||
{:else if $status === "loading"}
|
||||
<span>Loading</span>
|
||||
{:else if $status === "error"}
|
||||
|
@ -15,6 +15,7 @@
|
||||
const socketId = `${node.id}-${id}`;
|
||||
|
||||
const graph = getGraphManager();
|
||||
const graphId = graph.id;
|
||||
const inputSockets = graph.inputSockets;
|
||||
|
||||
const setDownSocket = getContext<(socket: Socket) => void>("setDownSocket");
|
||||
@ -67,24 +68,26 @@
|
||||
class="wrapper"
|
||||
class:disabled={$possibleSocketIds && !$possibleSocketIds.has(socketId)}
|
||||
>
|
||||
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
||||
<NodeInput {node} {input} {id} />
|
||||
</div>
|
||||
{#key id && graphId}
|
||||
<div class="content" class:disabled={$inputSockets.has(socketId)}>
|
||||
<NodeInput {node} {input} {id} />
|
||||
</div>
|
||||
|
||||
{#if node.tmp?.type?.inputs?.[id].internal !== true}
|
||||
<div
|
||||
class="large target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="small target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
{/if}
|
||||
{#if node?.tmp?.type?.inputs?.[id]?.internal !== true}
|
||||
<div
|
||||
class="large target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="small target"
|
||||
on:mousedown={handleMouseDown}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -137,6 +137,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
||||
}
|
||||
|
||||
async load(graph: Graph) {
|
||||
|
||||
const a = performance.now();
|
||||
|
||||
this.loaded = false;
|
||||
this.graph = graph;
|
||||
this.status.set("loading");
|
||||
@ -161,6 +164,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
||||
this.status.set("idle");
|
||||
|
||||
this.loaded = true;
|
||||
logger.log(`Graph loaded in ${performance.now() - a}ms`);
|
||||
}
|
||||
|
||||
|
||||
@ -296,7 +300,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
||||
|
||||
const nodeType = this.nodeRegistry.getNode(type);
|
||||
if (!nodeType) {
|
||||
console.error(`Node type not found: ${type}`);
|
||||
logger.error(`Node type not found: ${type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -317,8 +321,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
||||
// check if this exact edge already exists
|
||||
const existingEdge = existingEdges.find(e => e[0].id === from.id && e[1] === fromSocket && e[3] === toSocket);
|
||||
if (existingEdge) {
|
||||
console.log("Edge already exists");
|
||||
console.log(existingEdge)
|
||||
logger.error("Edge already exists", existingEdge);
|
||||
return;
|
||||
};
|
||||
|
||||
@ -327,7 +330,7 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
||||
const toSocketType = to.tmp?.type?.inputs?.[toSocket]?.type;
|
||||
|
||||
if (fromSocketType !== toSocketType) {
|
||||
console.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
||||
logger.error(`Socket types do not match: ${fromSocketType} !== ${toSocketType}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -396,11 +399,9 @@ export class GraphManager extends EventEmitter<{ "save": Graph }> {
|
||||
getParentsOfNode(node: Node) {
|
||||
const parents = [];
|
||||
const stack = node.tmp?.parents?.slice(0);
|
||||
|
||||
|
||||
while (stack?.length) {
|
||||
if (parents.length > 1000000) {
|
||||
console.log("Infinite loop detected")
|
||||
logger.warn("Infinite loop detected")
|
||||
break;
|
||||
}
|
||||
const parent = stack.pop();
|
||||
|
@ -117,7 +117,6 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
|
||||
const inputNode = node.tmp.inputNodes?.[key];
|
||||
if (inputNode) {
|
||||
if (results[inputNode.id] === undefined) {
|
||||
console.log(inputNode, node)
|
||||
throw new Error("Input node has no result");
|
||||
}
|
||||
inputs[key] = results[inputNode.id];
|
||||
|
@ -37,7 +37,7 @@
|
||||
<br />
|
||||
<button
|
||||
on:click={() =>
|
||||
graphManager.load(graphManager.createTemplate("grid", 5, 5))}
|
||||
graphManager.load(graphManager.createTemplate("grid", 10, 10))}
|
||||
>load grid</button
|
||||
>
|
||||
<br />
|
||||
|
@ -38,9 +38,15 @@ importers:
|
||||
jsondiffpatch:
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
meshline:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0(three@0.159.0)
|
||||
three:
|
||||
specifier: ^0.159.0
|
||||
version: 0.159.0
|
||||
three.meshline:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
devDependencies:
|
||||
'@histoire/plugin-svelte':
|
||||
specifier: ^0.17.9
|
||||
@ -2832,6 +2838,14 @@ packages:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
/meshline@3.2.0(three@0.159.0):
|
||||
resolution: {integrity: sha512-ZaJkC967GTuef7UBdO0rGPX544oIWaNo7tYedVHSoR2lje6RR16fX8IsgMxgxoYYERtjqsRWIYBSPBxG4QR84Q==}
|
||||
peerDependencies:
|
||||
three: '>=0.137'
|
||||
dependencies:
|
||||
three: 0.159.0
|
||||
dev: false
|
||||
|
||||
/meshoptimizer@0.18.1:
|
||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||
dev: false
|
||||
@ -4002,6 +4016,10 @@ packages:
|
||||
tweakpane: 3.1.10
|
||||
dev: false
|
||||
|
||||
/three.meshline@1.4.0:
|
||||
resolution: {integrity: sha512-A8IsiMrWP8zmHisGDAJ76ZD7t/dOF/oCe/FUKNE6Bu01ZYEx8N6IlU/1Plb2aOZtAuWM2A8s8qS3hvY0OFuvOw==}
|
||||
dev: false
|
||||
|
||||
/three@0.159.0:
|
||||
resolution: {integrity: sha512-eCmhlLGbBgucuo4VEA9IO3Qpc7dh8Bd4VKzr7WfW4+8hMcIfoAVi1ev0pJYN9PTTsCslbcKgBwr2wNZ1EvLInA==}
|
||||
dev: false
|
||||
|
Loading…
Reference in New Issue
Block a user