tolerate cyclic graphs
This commit is contained in:
parent
eed623647c
commit
aab99dab8f
1 changed files with 38 additions and 14 deletions
|
@ -3,6 +3,8 @@ package engine
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"drjosh.dev/gurgle/geom"
|
"drjosh.dev/gurgle/geom"
|
||||||
|
@ -55,7 +57,6 @@ func (d *DrawDAG) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// Draw everything in d.dag, where not hidden (itself or any parent)
|
// Draw everything in d.dag, where not hidden (itself or any parent)
|
||||||
// TODO: handle descendant DrawLayers
|
|
||||||
d.dag.topWalk(func(x Drawer) {
|
d.dag.topWalk(func(x Drawer) {
|
||||||
// Is d hidden itself?
|
// Is d hidden itself?
|
||||||
if h, ok := x.(Hider); ok && h.Hidden() {
|
if h, ok := x.(Hider); ok && h.Hidden() {
|
||||||
|
@ -370,7 +371,8 @@ func (d *dag) removeVertex(v Drawer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// topWalk visits each vertex in topological order, in time O(|V| + |E|) and
|
// topWalk visits each vertex in topological order, in time O(|V| + |E|) and
|
||||||
// O(|V|) temporary memory.
|
// O(|V|) temporary memory (for acyclic graphs) and a bit longer if it has to
|
||||||
|
// break cycles.
|
||||||
func (d *dag) topWalk(visit func(Drawer)) {
|
func (d *dag) topWalk(visit func(Drawer)) {
|
||||||
// Count indegrees - indegree(v) = len(d.in[v]) for each vertex v.
|
// Count indegrees - indegree(v) = len(d.in[v]) for each vertex v.
|
||||||
// If indegree(v) = 0, enqueue. Total: O(|V|).
|
// If indegree(v) = 0, enqueue. Total: O(|V|).
|
||||||
|
@ -386,19 +388,41 @@ func (d *dag) topWalk(visit func(Drawer)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visit every vertex (O(|V|)) and decrement indegrees for every out edge
|
for len(indegree) > 0 || len(queue) > 0 {
|
||||||
// of each vertex visited (O(|E|)). Total: O(|V|+|E|).
|
if len(queue) == 0 {
|
||||||
for len(queue) > 0 {
|
// Getting here means there are unprocessed vertices, because none
|
||||||
u := queue[0]
|
// have zero indegree, otherwise they would be in the queue.
|
||||||
visit(u)
|
// Arbitrarily break a cycle by enqueueing the least-indegree vertex
|
||||||
queue = queue[1:]
|
mind, minv := math.MaxInt, Drawer(nil)
|
||||||
|
for v, d := range indegree {
|
||||||
|
if d < mind {
|
||||||
|
mind, minv = d, v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("breaking cycle in 'DAG' by enqueueing %v with indegree %d", minv, mind)
|
||||||
|
queue = append(queue, minv)
|
||||||
|
delete(indegree, minv)
|
||||||
|
}
|
||||||
|
|
||||||
// Decrement indegree for all out edges, and enqueue target if its
|
// Visit every vertex (O(|V|)) and decrement indegrees for every out edge
|
||||||
// indegree is now 0.
|
// of each vertex visited (O(|E|)). Total: O(|V|+|E|).
|
||||||
for v := range d.out[u] {
|
for len(queue) > 0 {
|
||||||
indegree[v]--
|
u := queue[0]
|
||||||
if indegree[v] == 0 {
|
visit(u)
|
||||||
queue = append(queue, v)
|
queue = queue[1:]
|
||||||
|
|
||||||
|
// Decrement indegree for all out edges, and enqueue target if its
|
||||||
|
// indegree is now 0.
|
||||||
|
for v := range d.out[u] {
|
||||||
|
if _, ready := indegree[v]; !ready {
|
||||||
|
// Vertex already drawn. This happens if there was a cycle.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
indegree[v]--
|
||||||
|
if indegree[v] == 0 {
|
||||||
|
queue = append(queue, v)
|
||||||
|
delete(indegree, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue