2024-03-11 22:00:16 +01:00
|
|
|
import { get, writable, type Writable } from "svelte/store";
|
2024-03-11 19:37:58 +01:00
|
|
|
import type { Graph, NodeRegistry as INodeRegistry, NodeType, Node, Edge } from "./types";
|
2024-03-06 18:31:06 +01:00
|
|
|
|
|
|
|
const nodeTypes: NodeType[] = [
|
|
|
|
{
|
|
|
|
id: "input/float",
|
|
|
|
inputs: {
|
|
|
|
"value": { type: "float" },
|
|
|
|
},
|
|
|
|
outputs: ["float"],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "math",
|
|
|
|
inputs: {
|
2024-03-11 22:00:16 +01:00
|
|
|
"type": { type: "select", options: ["add", "subtract", "multiply", "divide"], internal: true },
|
2024-03-06 18:31:06 +01:00
|
|
|
"a": { type: "float" },
|
|
|
|
"b": { type: "float" },
|
|
|
|
},
|
|
|
|
outputs: ["float"],
|
|
|
|
},
|
2024-03-11 22:00:16 +01:00
|
|
|
{
|
|
|
|
id: "output",
|
|
|
|
inputs: {
|
|
|
|
"input": { type: "float" },
|
|
|
|
},
|
|
|
|
outputs: [],
|
|
|
|
}
|
2024-03-06 18:31:06 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
export class NodeRegistry implements INodeRegistry {
|
|
|
|
getNode(id: string): NodeType | undefined {
|
|
|
|
return nodeTypes.find((nodeType) => nodeType.id === id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export class GraphManager {
|
|
|
|
|
|
|
|
status: Writable<"loading" | "idle" | "error"> = writable("loading");
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
private _nodes: Node[] = [];
|
|
|
|
nodes: Writable<Node[]> = writable([]);
|
|
|
|
private _edges: Edge[] = [];
|
|
|
|
edges: Writable<Edge[]> = writable([]);
|
2024-03-06 18:31:06 +01:00
|
|
|
|
|
|
|
private constructor(private graph: Graph, private nodeRegistry: NodeRegistry = new NodeRegistry()) {
|
2024-03-11 22:00:16 +01:00
|
|
|
this.nodes.subscribe((nodes) => {
|
|
|
|
this._nodes = nodes;
|
|
|
|
});
|
|
|
|
this.edges.subscribe((edges) => {
|
|
|
|
this._edges = edges;
|
|
|
|
});
|
2024-03-06 18:31:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async load() {
|
|
|
|
|
|
|
|
const nodes = this.graph.nodes;
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
const nodeType = this.getNodeType(node.type);
|
|
|
|
if (!nodeType) {
|
|
|
|
console.error(`Node type not found: ${node.type}`);
|
|
|
|
this.status.set("error");
|
|
|
|
return;
|
|
|
|
}
|
2024-03-11 22:00:16 +01:00
|
|
|
node.tmp = node.tmp || {};
|
|
|
|
node.tmp.type = nodeType;
|
2024-03-06 18:31:06 +01:00
|
|
|
}
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
this.nodes.set(nodes);
|
|
|
|
this.edges.set(this.graph.edges.map((edge) => {
|
|
|
|
const from = this._nodes.find((node) => node.id === edge[0]);
|
|
|
|
const to = this._nodes.find((node) => node.id === edge[2]);
|
|
|
|
if (!from || !to) return;
|
|
|
|
return [from, edge[1], to, edge[3]] as const;
|
|
|
|
})
|
|
|
|
.filter(Boolean) as unknown as [Node, number, Node, string][]
|
|
|
|
);
|
|
|
|
|
|
|
|
|
2024-03-06 18:31:06 +01:00
|
|
|
this.status.set("idle");
|
|
|
|
}
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
|
2024-03-11 19:37:58 +01:00
|
|
|
getNode(id: number) {
|
2024-03-11 22:00:16 +01:00
|
|
|
return this._nodes.find((node) => node.id === id);
|
2024-03-06 18:31:06 +01:00
|
|
|
}
|
|
|
|
|
2024-03-11 19:37:58 +01:00
|
|
|
getPossibleSockets(node: Node, socketIndex: number, isInput: boolean): [Node, number][] {
|
|
|
|
|
|
|
|
const nodeType = this.getNodeType(node.type);
|
|
|
|
if (!nodeType) return [];
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
const nodes = this._nodes.filter(n => n.id !== node.id);
|
2024-03-11 19:37:58 +01:00
|
|
|
|
|
|
|
const sockets: [Node, number][] = []
|
|
|
|
if (isInput) {
|
|
|
|
|
|
|
|
const ownType = Object.values(nodeType?.inputs || {})[socketIndex].type;
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
const nodeType = this.getNodeType(node.type);
|
|
|
|
const inputs = nodeType?.outputs;
|
|
|
|
if (!inputs) continue;
|
|
|
|
for (let index = 0; index < inputs.length; index++) {
|
|
|
|
if (inputs[index] === ownType) {
|
|
|
|
sockets.push([node, index]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
const ownType = nodeType.outputs?.[socketIndex];
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
const nodeType = this.getNodeType(node.type);
|
|
|
|
const inputs = nodeType?.inputs;
|
|
|
|
const entries = Object.values(inputs || {});
|
|
|
|
entries.map((input, index) => {
|
|
|
|
if (input.type === ownType) {
|
|
|
|
sockets.push([node, index]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return sockets;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
getNodeType(id: string): NodeType {
|
2024-03-06 18:31:06 +01:00
|
|
|
return this.nodeRegistry.getNode(id)!;
|
|
|
|
}
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
removeEdge(edge: Edge) {
|
|
|
|
const id0 = edge[0].id;
|
|
|
|
const sid0 = edge[1];
|
|
|
|
const id2 = edge[2].id;
|
|
|
|
const sid2 = edge[3];
|
|
|
|
this.edges.update((edges) => {
|
|
|
|
return edges.filter((e) => e[0].id !== id0 || e[1] !== sid0 || e[2].id !== id2 || e[3] !== sid2);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getEdgesToNode(node: Node) {
|
|
|
|
return this._edges
|
|
|
|
.filter((edge) => edge[2].id === node.id)
|
2024-03-06 18:31:06 +01:00
|
|
|
.map((edge) => {
|
2024-03-11 22:00:16 +01:00
|
|
|
const from = this._nodes.find((node) => node.id === edge[0].id);
|
|
|
|
const to = this._nodes.find((node) => node.id === edge[2].id);
|
2024-03-06 18:31:06 +01:00
|
|
|
if (!from || !to) return;
|
2024-03-11 22:00:16 +01:00
|
|
|
return [from, edge[1], to, edge[3]] as const;
|
2024-03-06 18:31:06 +01:00
|
|
|
})
|
2024-03-11 22:00:16 +01:00
|
|
|
.filter(Boolean) as unknown as [Node, number, Node, string][];
|
2024-03-06 18:31:06 +01:00
|
|
|
}
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
getEdgesFromNode(node: Node) {
|
|
|
|
return this._edges
|
|
|
|
.filter((edge) => edge[0] === node.id)
|
|
|
|
.map((edge) => {
|
|
|
|
const from = this._nodes.find((node) => node.id === edge[0]);
|
|
|
|
const to = this._nodes.find((node) => node.id === edge[2]);
|
|
|
|
if (!from || !to) return;
|
|
|
|
return [from, edge[1], to, edge[3]] as const;
|
|
|
|
})
|
|
|
|
.filter(Boolean) as unknown as [Node, number, Node, string][];
|
|
|
|
}
|
2024-03-06 18:31:06 +01:00
|
|
|
|
2024-03-11 02:02:04 +01:00
|
|
|
static createEmptyGraph({ width = 20, height = 20 } = {}): GraphManager {
|
2024-03-06 18:31:06 +01:00
|
|
|
|
|
|
|
const graph: Graph = {
|
|
|
|
edges: [],
|
|
|
|
nodes: [],
|
|
|
|
};
|
|
|
|
|
2024-03-11 02:02:04 +01:00
|
|
|
const amount = width * height;
|
|
|
|
|
|
|
|
for (let i = 0; i < amount; i++) {
|
|
|
|
const x = i % width;
|
|
|
|
const y = Math.floor(i / height);
|
2024-03-06 18:31:06 +01:00
|
|
|
|
|
|
|
graph.nodes.push({
|
2024-03-11 19:37:58 +01:00
|
|
|
id: i,
|
2024-03-06 18:31:06 +01:00
|
|
|
tmp: {
|
|
|
|
visible: false,
|
|
|
|
},
|
|
|
|
position: {
|
|
|
|
x: x * 7.5,
|
2024-03-11 02:02:04 +01:00
|
|
|
y: y * 10,
|
2024-03-06 18:31:06 +01:00
|
|
|
},
|
|
|
|
props: i == 0 ? { value: 0 } : {},
|
|
|
|
type: i == 0 ? "input/float" : "math",
|
|
|
|
});
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
graph.edges.push([i, 0, i + 1, i === amount - 1 ? "input" : "a",]);
|
2024-03-06 18:31:06 +01:00
|
|
|
}
|
|
|
|
|
2024-03-11 22:00:16 +01:00
|
|
|
graph.nodes.push({
|
|
|
|
id: amount,
|
|
|
|
tmp: {
|
|
|
|
visible: false,
|
|
|
|
},
|
|
|
|
position: {
|
|
|
|
x: width * 7.5,
|
|
|
|
y: (height - 1) * 10,
|
|
|
|
},
|
|
|
|
type: "output",
|
|
|
|
props: {},
|
|
|
|
});
|
|
|
|
|
2024-03-06 18:31:06 +01:00
|
|
|
return new GraphManager(graph);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|