diff --git a/app/ecs-systems/index.js b/app/ecs-systems/index.js index 3f255d9..404ce7c 100644 --- a/app/ecs-systems/index.js +++ b/app/ecs-systems/index.js @@ -1,3 +1,6 @@ +import System from '@/ecs/system.js'; import gather from '@/util/gather.js'; -export default gather(import.meta.glob('./*.js', {eager: true, import: 'default'})); +const gathered = gather(import.meta.glob('./*.js', {eager: true, import: 'default'})); + +export default System.sort(gathered); diff --git a/app/ecs/system.js b/app/ecs/system.js index ef67acf..b4aacec 100644 --- a/app/ecs/system.js +++ b/app/ecs/system.js @@ -1,3 +1,4 @@ +import Digraph from '@/util/digraph.js'; import Query from './query.js'; @@ -54,6 +55,12 @@ export default class System { this.ecs.insertMany(components); } + static get priority() { + return { + phase: 'normal', + } + } + static queries() { return {}; } @@ -76,6 +83,38 @@ export default class System { return this.queries[query].select(); } + static sort(Systems) { + const phases = { + 'pre': new Digraph(), + 'normal': new Digraph(), + 'post': new Digraph(), + }; + for (const systemName in Systems) { + const {priority} = Systems[systemName]; + const phase = phases[priority.phase || 'normal']; + phase.ensureTail(systemName); + if (priority.before) { + for (const before of Array.isArray(priority.before) ? priority.before : [priority.before]) { + phase.addDependency(before, systemName); + } + } + if (priority.after) { + for (const after of Array.isArray(priority.after) ? priority.after : [priority.after]) { + phase.addDependency(systemName, after); + } + } + } + const sorted = [ + ...phases['pre'].sort(), + ...phases['normal'].sort(), + ...phases['post'].sort(), + ]; + return Object.fromEntries( + Object.entries(Systems) + .toSorted(([l], [r]) => sorted.indexOf(l) - sorted.indexOf(r)), + ); + } + tickDestruction() { this.deindex(this.destroying); this.destroying = []; diff --git a/app/util/digraph.js b/app/util/digraph.js new file mode 100644 index 0000000..2ed63b9 --- /dev/null +++ b/app/util/digraph.js @@ -0,0 +1,87 @@ +export default class Digraph { + + arcs = new Map(); + + addDependency(head, tail) { + this.ensureTail(head); + this.ensureTail(tail).add(head); + } + + detectCycles() { + const cycles = []; + const visited = new Set(); + const walking = new Set(); + const walk = (vertex) => { + if (!visited.has(vertex)) { + visited.add(vertex); + walking.add(vertex); + const it = this.neighbors(vertex); + for (let current = it.next(); true !== current.done; current = it.next()) { + const {value: neighbor} = current; + if (!visited.has(neighbor)) { + walk(neighbor); + } + else if (walking.has(neighbor)) { + cycles.push([vertex, neighbor]); + } + } + } + walking.delete(vertex); + }; + const {tails} = this; + for (let current = tails.next(); true !== current.done; current = tails.next()) { + walk(current.value); + } + return cycles; + } + + ensureTail(tail) { + if (!this.arcs.has(tail)) { + this.arcs.set(tail, new Set()); + } + return this.arcs.get(tail); + } + + neighbors(vertex) { + return this.arcs.get(vertex).values(); + } + + sort() { + const visited = new Set(); + const scores = new Map(); + const walk = (vertex, score) => { + visited.add(vertex); + const neighbors = this.neighbors(vertex); + for (let current = neighbors.next(); true !== current.done; current = neighbors.next()) { + const {value: neighbor} = current; + if (!visited.has(neighbor)) { + score = walk(neighbor, score); + } + } + scores.set(vertex, score); + return score - 1; + }; + let score = this.arcs.size - 1; + const {tails} = this; + for (let current = tails.next(); true !== current.done; current = tails.next()) { + const {value: vertex} = current; + if (!visited.has(vertex)) { + score = walk(vertex, score); + } + } + return Array.from(scores.entries()) + .sort(([, l], [, r]) => l - r) + .map(([vertex]) => vertex); + } + + removeDependency(head, tail) { + if (this.arcs.has(tail)) { + this.arcs.get(tail).delete(head); + } + } + + get tails() { + return this.arcs.keys(); + } + +}