WIP: topsort adjustments

This commit is contained in:
Josh Deprez 2021-09-17 12:50:29 +10:00
parent 091d36094b
commit 45d7f32a2a
4 changed files with 69 additions and 46 deletions

View file

@ -1,7 +1,7 @@
package engine package engine
import ( import (
"errors" "log"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
@ -81,52 +81,65 @@ func (d drawList) Swap(i, j int) {
} }
// Bad, slow, topological sort // Bad, slow, topological sort
func (d *drawList) topsort() error { func (d *drawList) topsort() {
// Count indegrees - O(|V|^2) // Produce edge lists - O(|V|^2)
indegree := make(map[Drawer]int) // Count indegrees - also O(|V|^2)
for _, u := range d.list { edges := make([][]int, len(d.list))
indegree[u] += 0 indegree := make([]int, len(d.list))
for _, v := range d.list { for i, u := range d.list {
if u == v { if u == (tombstone{}) {
continue
}
for j, v := range d.list {
if i == j {
continue
}
if v == (tombstone{}) {
continue continue
} }
if u.DrawBefore(v) || v.DrawAfter(u) { if u.DrawBefore(v) || v.DrawAfter(u) {
indegree[v]++ edges[i] = append(edges[i], j)
indegree[j]++
} }
} }
} }
//log.Printf("indegree: %v", indegree)
// Sort into new list // Start queue with all zero-indegree vertices
var queue []int
for i, n := range indegree {
if d.list[i] == (tombstone{}) {
continue
}
if n == 0 {
queue = append(queue, i)
}
}
// Process into new list
list := make([]Drawer, 0, len(d.list)) list := make([]Drawer, 0, len(d.list))
for len(indegree) > 0 { for len(queue) > 0 {
var bag []Drawer i := queue[0]
for v, n := range indegree { queue = queue[1:]
if n == 0 { if false {
bag = append(bag, v) d.rev[d.list[i]] = len(list)
}
} }
//log.Printf("zero indegree vertices: %v", bag) list = append(list, d.list[i])
if len(bag) == 0 { for _, j := range edges[i] {
//log.Printf("remaining vertices: %v", indegree) indegree[j]--
return errors.New("no vertices with zero indegree") if indegree[j] <= 0 {
} if indegree[j] < 0 {
list = append(list, bag...) log.Printf("indegree[%d] = %d (component %v)", j, indegree[j], d.list[j])
for _, u := range bag {
delete(indegree, u)
}
for _, u := range bag {
for v := range indegree {
if u.DrawBefore(v) || v.DrawAfter(u) {
indegree[v]--
} }
queue = append(queue, j)
} }
} }
} }
// Replace list // Replace list
d.list = list d.list = list
// Update rev // Update rev
d.rev = make(map[Drawer]int, len(list))
for i, v := range list { for i, v := range list {
d.rev[v] = i d.rev[v] = i
} }
return nil
} }

View file

@ -8,6 +8,7 @@ import (
"io/fs" "io/fs"
"log" "log"
"reflect" "reflect"
"sort"
"sync" "sync"
"time" "time"
@ -16,6 +17,8 @@ import (
"github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
const topologicalDrawSort = true
var _ interface { var _ interface {
Disabler Disabler
Hider Hider
@ -182,16 +185,18 @@ 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)
//sort.Stable(g.drawList) if topologicalDrawSort {
if err := g.drawList.topsort(); err != nil { g.drawList.topsort()
return fmt.Errorf("drawList.topsort: %v", err) } else {
} sort.Stable(g.drawList)
// Truncate tombstones from the end.
for i := g.drawList.Len() - 1; i >= 0; i-- { // Truncate tombstones from the end.
if g.drawList.list[i] != (tombstone{}) { for i := g.drawList.Len() - 1; i >= 0; i-- {
break if g.drawList.list[i] != (tombstone{}) {
break
}
g.drawList.list = g.drawList.list[:i]
} }
g.drawList.list = g.drawList.list[:i]
} }
return nil return nil
} }

View file

@ -114,7 +114,7 @@ func (aw *Awakeman) realUpdate() error {
aw.bubbleTimer-- aw.bubbleTimer--
if aw.bubbleTimer <= 0 { if aw.bubbleTimer <= 0 {
aw.bubbleTimer = bubblePeriod aw.bubbleTimer = bubblePeriod
bubble := NewBubble(aw.Sprite.Actor.Pos.Add(geom.Pt3(1, -15, -1))) bubble := NewBubble(aw.Sprite.Actor.Pos.Add(geom.Pt3(-3, -20, -1)))
if err := engine.PreorderWalk(bubble, func(c, _ interface{}) error { if err := engine.PreorderWalk(bubble, func(c, _ interface{}) error {
if p, ok := c.(engine.Loader); ok { if p, ok := c.(engine.Loader); ok {
return p.Load(Assets) return p.Load(Assets)

View file

@ -67,10 +67,15 @@ func (b *Bubble) Update() error {
if b.Life <= 0 { if b.Life <= 0 {
b.game.Unregister(b) b.game.Unregister(b)
} }
// not using MoveX/MoveY/... because collisions are unnecessary - if false { // not using MoveX/MoveY/... because collisions are unnecessary -
// this is an effect particle, if it overlaps a solid, who cares // this is an effect particle, if it overlaps a solid, who cares
b.Sprite.Actor.Pos = b.Sprite.Actor.Pos.Add(geom.Pt3( b.Sprite.Actor.Pos = b.Sprite.Actor.Pos.Add(geom.Pt3(
rand.Intn(3)-1, -1, rand.Intn(2)-1, // --lint:ignore SA4000 one random minus another is not always zero...
)) rand.Intn(3)-1, rand.Intn(2)-1, 0, // rand.Intn(2)-rand.Intn(2),
))
} else {
b.Sprite.Actor.MoveX(float64(rand.Intn(3)-1), nil)
b.Sprite.Actor.MoveY(float64(rand.Intn(2)-1), nil)
}
return nil return nil
} }