topsort cleanups

This commit is contained in:
Josh Deprez 2021-09-17 13:51:24 +10:00
parent 86ee887206
commit 6565fd4160
2 changed files with 28 additions and 44 deletions

View file

@ -2,7 +2,7 @@ package engine
import ( import (
"image" "image"
"log" "math"
"drjosh.dev/gurgle/geom" "drjosh.dev/gurgle/geom"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@ -82,41 +82,36 @@ func (d drawList) Swap(i, j int) {
d.list[i], d.list[j] = d.list[j], d.list[i] d.list[i], d.list[j] = d.list[j], d.list[i]
} }
// Slow topological sort // Slow topological sort. Uses a projection π to flatten bounding boxes for
func (d *drawList) topsort() { // overlap tests, so that the graph is reduced.
// Produce edge lists - O(|V|^2) func (d *drawList) topsort(π geom.Projector) {
// Count indegrees - also O(|V|^2) // Produce edge lists and count indegrees - O(|V|^2)
// TODO: optimise this
edges := make([][]int, len(d.list)) edges := make([][]int, len(d.list))
indegree := make([]int, len(d.list)) indegree := make([]int, len(d.list))
for i, u := range d.list { for i, u := range d.list {
if u == (tombstone{}) { if u == (tombstone{}) {
// Prevents processing this vertex later on
indegree[i] = -1
continue continue
} }
var ub image.Rectangle // If we can't get a more specific bounding rect, assume entire plane.
switch x := u.(type) { ub := image.Rect(math.MinInt, math.MinInt, math.MaxInt, math.MaxInt)
case BoundingBoxer: if x, ok := u.(BoundingBoxer); ok {
ub = x.BoundingBox().BoundingRect(geom.IntProjection{X: 0, Y: 1}) ub = x.BoundingBox().BoundingRect(π)
default:
ub = image.Rect(0, 0, 320, 240)
} }
// For each possible neighbor...
for j, v := range d.list { for j, v := range d.list {
if i == j { if i == j || v == (tombstone{}) {
continue continue
} }
if v == (tombstone{}) { // Does it have a bounding rect? Do overlap test.
continue if y, ok := v.(BoundingBoxer); ok {
} if vb := y.BoundingBox().BoundingRect(π); !ub.Overlaps(vb) {
var vb image.Rectangle continue
switch y := v.(type) { }
case BoundingBoxer:
vb = y.BoundingBox().BoundingRect(geom.IntProjection{X: 0, Y: 1})
default:
vb = image.Rect(0, 0, 320, 240)
}
if !ub.Overlaps(vb) {
// No overlap, no need to emit an edge
continue
} }
// If the edge goes u->v, add it.
if u.DrawBefore(v) || v.DrawAfter(u) { if u.DrawBefore(v) || v.DrawAfter(u) {
edges[i] = append(edges[i], j) edges[i] = append(edges[i], j)
indegree[j]++ indegree[j]++
@ -124,42 +119,31 @@ func (d *drawList) topsort() {
} }
} }
// Start queue with all zero-indegree vertices // Initialise queue with all the zero-indegree vertices
var queue []int var queue []int
for i, n := range indegree { for i, n := range indegree {
if d.list[i] == (tombstone{}) {
continue
}
if n == 0 { if n == 0 {
queue = append(queue, i) queue = append(queue, i)
} }
} }
// Process into new list // Process into new list. O(|V| + |E|)
list := make([]Drawer, 0, len(d.list)) list := make([]Drawer, 0, len(d.list))
for len(queue) > 0 { for len(queue) > 0 {
// Get front of queue.
i := queue[0] i := queue[0]
queue = queue[1:] queue = queue[1:]
// Add to output list.
d.rev[d.list[i]] = len(list) d.rev[d.list[i]] = len(list)
list = append(list, d.list[i]) list = append(list, d.list[i])
// Reduce indegree for all outgoing edges, enqueue if indegree now 0.
for _, j := range edges[i] { for _, j := range edges[i] {
indegree[j]-- indegree[j]--
if indegree[j] <= 0 { if indegree[j] == 0 {
if indegree[j] < 0 {
log.Printf("indegree[%d] = %d (component %v)", j, indegree[j], d.list[j])
}
queue = append(queue, j) queue = append(queue, j)
} }
} }
} }
// Job done!
// Replace list
d.list = list d.list = list
if false {
// Update rev
d.rev = make(map[Drawer]int, len(list))
for i, v := range list {
d.rev[v] = i
}
}
} }

View file

@ -186,7 +186,7 @@ func (g *Game) Update() error {
// Sort the draw list (on every frame - this isn't as bad as it sounds) // Sort the draw list (on every frame - this isn't as bad as it sounds)
if topologicalDrawSort { if topologicalDrawSort {
g.drawList.topsort() g.drawList.topsort(g.Projection)
} else { } else {
sort.Stable(g.drawList) sort.Stable(g.drawList)