polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
259 lines (238 loc) • 7.48 kB
text/typescript
import {PolyScene} from '../../engine/scene/PolyScene';
import {CoreGraphNode} from './CoreGraphNode';
export type CoreGraphNodeId = number;
export class CoreGraph {
private _next_id: CoreGraphNodeId = 0;
private _scene: PolyScene | undefined;
private _successors: Map<CoreGraphNodeId, Set<CoreGraphNodeId>> = new Map();
private _predecessors: Map<CoreGraphNodeId, Set<CoreGraphNodeId>> = new Map();
private _nodes_by_id: Map<number, CoreGraphNode> = new Map();
private _nodesCount = 0;
private _debugging = false;
private _addedNodesDuringDebugging: Map<CoreGraphNodeId, CoreGraphNode> = new Map();
startDebugging() {
this._debugging = true;
console.log('CoreGraph.startDebugging', this._next_id);
}
stopDebugging() {
this._debugging = false;
console.log('CoreGraph.stopDebugging', this._next_id);
}
printDebug() {
this._addedNodesDuringDebugging.forEach((node, nodeId) => {
console.log(nodeId, node, node.graphPredecessors(), node.graphSuccessors());
});
}
set_scene(scene: PolyScene) {
this._scene = scene;
}
scene() {
return this._scene;
}
next_id(): CoreGraphNodeId {
this._next_id += 1;
return this._next_id;
}
nodes_from_ids(ids: number[]) {
const nodes: CoreGraphNode[] = [];
for (let id of ids) {
const node = this.node_from_id(id);
if (node) {
nodes.push(node);
}
}
return nodes;
}
node_from_id(id: number): CoreGraphNode | undefined {
return this._nodes_by_id.get(id);
}
has_node(node: CoreGraphNode): boolean {
return this._nodes_by_id.get(node.graphNodeId()) != null;
}
add_node(node: CoreGraphNode) {
this._nodes_by_id.set(node.graphNodeId(), node);
this._nodesCount += 1;
if (this._debugging) {
this._addedNodesDuringDebugging.set(node.graphNodeId(), node);
}
}
remove_node(node: CoreGraphNode) {
this._nodes_by_id.delete(node.graphNodeId());
this._successors.delete(node.graphNodeId());
this._predecessors.delete(node.graphNodeId());
this._nodesCount -= 1;
if (this._debugging) {
this._addedNodesDuringDebugging.delete(node.graphNodeId());
}
}
nodesCount() {
return this._nodesCount;
}
connect(src: CoreGraphNode, dest: CoreGraphNode, check_if_graph_may_have_cycle = true): boolean {
const src_id = src.graphNodeId();
const dest_id = dest.graphNodeId();
if (this.has_node(src) && this.has_node(dest)) {
// this._graph.setEdge(src_id, dest_id);
// if check_if_graph_may_have_cycle is passed as false, that means we never check.
// this can be useful when we know that the connection will not create a cycle,
// such as when connecting params or inputs to a node
if (check_if_graph_may_have_cycle) {
const scene_loading = this._scene ? this._scene.loadingController.isLoading() : true;
check_if_graph_may_have_cycle = !scene_loading;
}
let graph_would_have_cycle = false;
if (check_if_graph_may_have_cycle) {
// graph_has_cycle = !alg.isAcyclic(this._graph);
graph_would_have_cycle = this._has_predecessor(src_id, dest_id);
}
if (graph_would_have_cycle) {
// this._graph.removeEdge(src_id, dest_id);
return false;
} else {
this._create_connection(src_id, dest_id);
src.dirtyController.clear_successors_cache_with_predecessors();
return true;
}
} else {
console.warn(`attempt to connect non existing node ${src_id} or ${dest_id}`);
return false;
}
}
disconnect(src: CoreGraphNode, dest: CoreGraphNode) {
// const src_id_s = src.graphNodeId();
// const dest_id_s = dest.graphNodeId();
// this._graph.removeEdge(src_id_s, dest_id_s);
this._remove_connection(src.graphNodeId(), dest.graphNodeId());
src.dirtyController.clear_successors_cache_with_predecessors();
}
disconnect_predecessors(node: CoreGraphNode) {
const predecessors = this.predecessors(node);
for (let predecessor of predecessors) {
this.disconnect(predecessor, node);
}
}
disconnect_successors(node: CoreGraphNode) {
const successors = this.successors(node);
for (let successor of successors) {
this.disconnect(node, successor);
}
}
predecessor_ids(id: CoreGraphNodeId): CoreGraphNodeId[] {
const map = this._predecessors.get(id);
if (map) {
const ids: CoreGraphNodeId[] = [];
map.forEach((bool, id) => {
ids.push(id);
});
return ids;
}
return [];
}
predecessors(node: CoreGraphNode) {
const ids = this.predecessor_ids(node.graphNodeId());
return this.nodes_from_ids(ids);
}
successor_ids(id: CoreGraphNodeId): CoreGraphNodeId[] {
const map = this._successors.get(id);
if (map) {
const ids: CoreGraphNodeId[] = [];
map.forEach((bool, id) => {
ids.push(id);
});
return ids;
}
return [];
}
successors(node: CoreGraphNode): CoreGraphNode[] {
const ids = this.successor_ids(node.graphNodeId()) || [];
return this.nodes_from_ids(ids);
}
all_predecessor_ids(node: CoreGraphNode): CoreGraphNodeId[] {
return this.all_next_ids(node, 'predecessor_ids');
}
all_successor_ids(node: CoreGraphNode): CoreGraphNodeId[] {
return this.all_next_ids(node, 'successor_ids');
}
all_predecessors(node: CoreGraphNode): CoreGraphNode[] {
const ids = this.all_predecessor_ids(node);
return this.nodes_from_ids(ids);
}
all_successors(node: CoreGraphNode): CoreGraphNode[] {
const ids = this.all_successor_ids(node);
return this.nodes_from_ids(ids);
}
private _create_connection(src_id: CoreGraphNodeId, dest_id: CoreGraphNodeId) {
// set successors
let node_successors = this._successors.get(src_id);
if (!node_successors) {
node_successors = new Set();
this._successors.set(src_id, node_successors);
}
if (node_successors.has(dest_id)) {
return;
}
node_successors.add(dest_id);
// set predecessors
let node_predecessors = this._predecessors.get(dest_id);
if (!node_predecessors) {
node_predecessors = new Set();
this._predecessors.set(dest_id, node_predecessors);
}
node_predecessors.add(src_id);
}
private _remove_connection(src_id: CoreGraphNodeId, dest_id: CoreGraphNodeId) {
// remove successors
let node_successors = this._successors.get(src_id);
if (node_successors) {
node_successors.delete(dest_id);
if (node_successors.size == 0) {
this._successors.delete(src_id);
}
}
// remove predecessors
let node_predecessors = this._predecessors.get(dest_id);
if (node_predecessors) {
node_predecessors.delete(src_id);
if (node_predecessors.size == 0) {
this._predecessors.delete(dest_id);
}
}
}
private all_next_ids(node: CoreGraphNode, method: 'successor_ids' | 'predecessor_ids'): CoreGraphNodeId[] {
const ids_by_id: Map<CoreGraphNodeId, boolean> = new Map();
const ids: CoreGraphNodeId[] = [];
let next_ids = this[method](node.graphNodeId());
while (next_ids.length > 0) {
const next_next_ids = [];
for (let next_id of next_ids) {
for (let next_next_id of this[method](next_id)) {
next_next_ids.push(next_next_id);
}
}
for (let id of next_ids) {
ids_by_id.set(id, true);
}
for (let id of next_next_ids) {
next_ids.push(id);
}
next_ids = next_next_ids;
}
ids_by_id.forEach((bool, id) => {
ids.push(id);
});
return ids;
}
private _has_predecessor(src_id: CoreGraphNodeId, dest_id: CoreGraphNodeId): boolean {
const ids = this.predecessor_ids(src_id);
if (ids) {
if (ids.includes(dest_id)) {
return true;
} else {
for (let id of ids) {
return this._has_predecessor(id, dest_id);
}
}
}
return false;
}
}