@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
323 lines (249 loc) • 6.35 kB
JavaScript
/**
* Supplementary structure to `Graph`, holds edges and neighbour nodes for fast access
* Used internally by `Graph`
* @template N
*/
export class NodeContainer {
/**
* Node being described
* @type {N}
*/
node = null;
/**
* Attached edges
* @type {Edge<N>[]}
* @private
*/
__edges = [];
/**
*
* @type {Map<N,number>}
* Maps neighbour node to number of edges to that node from this one
* @private
*/
__neighbors = new Map();
/**
* NOTE: this method allocates memory internally
* @returns {N[]}
*/
get neighbours() {
return Array.from(this.__neighbors.keys());
}
/**
*
* @return {number}
*/
getEdgeCount() {
return this.__edges.length;
}
/**
* Do not modify the returned value
* @return {Edge<N>[]}
*/
getEdges() {
return this.__edges;
}
/**
* NOTE: this method allocates memory internally
* @returns {N[]}
*/
get inNodes() {
return this.inEdges.map(e => e.other(this.node));
}
/**
* NOTE: this method allocates memory internally
* @returns {N[]}
*/
get outNodes() {
return this.outEdges.map(e => e.other(this.node));
}
/**
* NOTE: this method allocates memory internally
* @returns {Edge<N>[]}
*/
get outEdges() {
/**
*
* @type {Edge<N>[]}
*/
const result = [];
this.getOutgoingEdges(result);
return result;
}
/**
* NOTE: this method allocates memory internally
* @returns {Edge<N>[]}
*/
get inEdges() {
/**
*
* @type {Edge<N>[]}
*/
const result = [];
this.getIncomingEdges(result);
return result;
}
/**
*
* @param {Edge<N>[]} result
* @returns {number}
*/
getIncomingEdges(result) {
const edges = this.__edges;
const edge_count = edges.length;
let result_count = 0;
for (let i = 0; i < edge_count; i++) {
const edge = edges[i];
if (edge.isDirectedTowards(this.node)) {
result.push(edge);
result_count++;
}
}
return result_count;
}
/**
*
* @param {Edge<N>[]} result
* @returns {number}
*/
getOutgoingEdges(result) {
const edges = this.__edges;
const edge_count = edges.length;
let result_count = 0;
for (let i = 0; i < edge_count; i++) {
const edge = edges[i];
if (edge.isDirectedAwayFrom(this.node)) {
result.push(edge);
result_count++;
}
}
return result_count;
}
/**
*
* @return {number}
*/
getIncomingEdgeCount() {
let r = 0;
const edges = this.__edges;
const edge_count = edges.length;
const current_node = this.node;
for (let i = 0; i < edge_count; i++) {
const edge = edges[i];
if (edge.isDirectedTowards(current_node)) {
r++;
}
}
return r;
}
/**
*
* @param {N} other
* @returns {boolean}
*/
edgeWithNodeExists(other) {
return this.__neighbors.has(other);
}
/**
*
* @param {function(Edge<N>)} visitor
* @param {*} [thisArg]
* @returns {number}
*/
traverseEdges(visitor, thisArg) {
let i = 0;
const edges = this.__edges;
const n = edges.length;
for (; i < n; i++) {
const edge = edges[i];
visitor.call(thisArg, edge);
}
return i;
}
/**
*
* @param {N} other
* @returns {Edge<N>|undefined}
*/
getAnyEdgeWith(other) {
if (!this.edgeWithNodeExists(other)) {
return undefined;
}
/**
*
* @type {Edge[]}
*/
const edges = this.__edges;
const n = edges.length;
for (let i = 0; i < n; i++) {
const edge = edges[i];
if (edge.contains(other)) {
return edge;
}
}
return undefined;
}
/**
* Undirected edges don't count
* @param {N} other
* @returns {Edge<N>|undefined}
*/
getAnyDirectionEdgeTo(other) {
/**
*
* @type {Edge[]}
*/
const edges = this.__edges;
const n = edges.length;
for (let i = 0; i < n; i++) {
const edge = edges[i];
if (
(edge.second === other && edge.traversableForward())
|| (edge.first === other && edge.traversableBackward())
) {
return edge;
}
}
// not found
return undefined;
}
/**
*
* @param {Edge} e
* @returns {boolean}
*/
addEdge(e) {
if (this.__edges.includes(e)) {
return false;
}
const other_node = e.other(this.node);
this.__edges.push(e);
let existing_count = this.__neighbors.get(other_node);
if (existing_count === undefined) {
existing_count = 0;
}
this.__neighbors.set(other_node, existing_count + 1);
return true;
}
/**
*
* @param {Edge} e
* @returns {boolean}
*/
removeEdge(e) {
const edges = this.__edges;
const i = edges.indexOf(e);
if (i === -1) {
return false;
}
edges.splice(i, 1);
const other_node = e.other(this.node);
const existing_count = this.__neighbors.get(other_node);
if (existing_count <= 1) {
this.__neighbors.delete(other_node);
} else {
this.__neighbors.set(other_node, existing_count - 1);
}
return true;
}
}