UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

273 lines (203 loc) • 7.2 kB
import { assert } from "../../../../../../core/assert.js"; import { Edge, EdgeDirectionType } from "../../../../../../core/graph/Edge.js"; import { Graph } from "../../../../../../core/graph/v2/Graph.js"; import { FunctionModuleReference } from "./FunctionModuleReference.js"; export class FunctionModuleRegistry { /** * * @type {Object<FunctionModule>} */ modules = {}; /** * Materialize entire dependency graph for the registry. * Dependency relationship edges have direction from dependant towards dependency * @private * @returns {Graph<FunctionModule>} */ __buildDependencyGraph() { /** * * @type {Graph<FunctionModule>} */ const result = new Graph(); /** * * @type {FunctionModule[]} */ const open = []; this.traverse(open.push, open); const module_count = open.length; // ingest nodes for (let i = 0; i < module_count; i++) { const module = open[i]; result.addNode(module); } // build dependency edges while (open.length > 0) { /** * * @type {FunctionModule} */ const module = open.pop(); // get dependencies const dependencies = module.dependencies; const dependency_count = dependencies.length; for (let i = 0; i < dependency_count; i++) { const reference = dependencies[i]; const dependency_module = this.getModuleByReference(reference); if (dependency_module === undefined) { throw new Error(`Failed to satisfy dependency for module ${module.id}, dependency reference: ${reference}`); } // create dependency edge const edge = new Edge(module, dependency_module); edge.direction = EdgeDirectionType.Forward; result.addEdge(edge); } } return result; } /** * * @param {FunctionModuleReference} reference * @returns {FunctionModule|undefined} */ getModuleByReference(reference) { assert.defined(reference, 'reference'); assert.equal(reference.isFunctionModuleReference, true, 'reference.isFunctionModuleReference !== true'); const match_by_id = this.getModule(reference.id); if (match_by_id === undefined) { // no ID match return undefined; } if (!match_by_id.signature.equals(reference.signature)) { // no signature match return undefined; } return match_by_id; } /** * * @param {FunctionModuleReference[]} references * @returns {FunctionModuleReference[]} full list of references involved */ resolveDependencyTreeForReferences(references) { /** * * @type {FunctionModuleReference[]} */ const result = []; /** * * @type {FunctionModule[]} */ const open = []; /** * * @type {FunctionModule[]} */ const unordered = []; function addToOpen(module) { if (open.includes(module)) { return; } if (unordered.includes(module)) { return; } open.push(module); } const n = references.length; for (let i = 0; i < n; i++) { const reference = references[i]; const module = this.getModuleByReference(reference); if (module === undefined) { throw new Error(`Module not found: ${reference}`); } addToOpen(module); } const graph = this.__buildDependencyGraph(); /** * * @param {FunctionModule} dependency * @param {Edge} edge */ function visitDependencyEdge(dependency, edge) { addToOpen(dependency); } while (open.length > 0) { const module = open.pop(); // move to closed unordered.push(module); graph.traverseSuccessors(module, visitDependencyEdge); } /** * * @type {FunctionModule[]} */ const ordered = []; ordering_loop:while (unordered.length > 0) { const n = unordered.length; module_loop: for (let i = 0; i < n; i++) { const module = unordered[i]; const moduleDependencies = module.dependencies; const dependency_count = moduleDependencies.length; for (let j = 0; j < dependency_count; j++) { const dependency = moduleDependencies[j]; const dependency_module = this.getModuleByReference(dependency); if (dependency_module === undefined) { throw new Error(`Dependency not found, dependent=${reference}, dependency=${dependency}`); } if (!ordered.includes(dependency_module)) { continue module_loop; } } ordered.push(module); unordered.splice(i, 1); continue ordering_loop; } // if we got here, no module was ordered this time around, meaning we're stuck throw new Error(`No ordering found for modules: ${unordered.map(m => FunctionModuleReference.from(m.id, m.signature)).join(', ')}`); } const ordered_count = ordered.length; for (let i = 0; i < ordered_count; i++) { const module = ordered[i]; const ref = FunctionModuleReference.from(module.id, module.signature); result.push(ref); } return result; } /** * * @param {FunctionModule} module * @returns {boolean} */ add(module) { assert.defined(module, 'module'); assert.equal(module.isFunctionModule, true, 'module.isFunctionModule !== true'); if (this.getModule(module.id) !== undefined) { // id collision return false; } this.modules[module.id] = module; return true; } /** * * @param {string} id * @returns {FunctionModule|undefined} */ getModule(id) { assert.isString(id, 'id'); return this.modules[id]; } /** * * @param {function(FunctionModule)} visitor * @param {*} [thisArg] */ traverse(visitor, thisArg) { for (const module_id in this.modules) { const module = this.modules[module_id]; visitor.call(thisArg, module) } } }