remove sort method

This commit is contained in:
Josh Deprez 2021-09-17 14:56:00 +10:00
parent f03ab319b2
commit f5219da56f
7 changed files with 54 additions and 178 deletions

View file

@ -8,8 +8,6 @@ import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
const commonDrawerComparisons = true
var _ Drawer = tombstone{} var _ Drawer = tombstone{}
type tombstone struct{} type tombstone struct{}
@ -26,63 +24,55 @@ type drawList struct {
rev map[Drawer]int rev map[Drawer]int
} }
func (d drawList) Less(i, j int) bool { // edge reports if there is a draw ordering constraint between u and v (where
// Deal with tombstones first, in case anything else thinks it // u draws before v).
// needs to go last. func edge(u, v Drawer) bool {
if d.list[i] == (tombstone{}) { // Common logic for known interfaces (BoundingBoxer, ZPositioner), to
return false // simplify DrawOrderer implementations.
} switch x := u.(type) {
if d.list[j] == (tombstone{}) { case BoundingBoxer:
return true xb := x.BoundingBox()
} switch y := v.(type) {
if commonDrawerComparisons {
// Common logic for known interfaces (BoundingBoxer, ZPositioner), to
// simplify Draw{Before,After} implementations.
switch x := d.list[i].(type) {
case BoundingBoxer: case BoundingBoxer:
xb := x.BoundingBox() yb := y.BoundingBox()
switch y := d.list[j].(type) { if xb.Min.Z >= yb.Max.Z { // x is in front of y
case BoundingBoxer: return false
yb := y.BoundingBox() }
if xb.Min.Z >= yb.Max.Z { // x is in front of y if xb.Max.Z <= yb.Min.Z { // x is behind y
return false return true
} }
if xb.Max.Z <= yb.Min.Z { // x is behind y if xb.Max.Y <= yb.Min.Y { // x is above y
return true return false
} }
if xb.Max.Y <= yb.Min.Y { // x is above y if xb.Min.Y >= yb.Max.Y { // x is below y
return false return true
}
if xb.Min.Y >= yb.Max.Y { // x is below y
return true
}
case ZPositioner:
return xb.Max.Z < y.ZPos() // x is before y
} }
case ZPositioner: case ZPositioner:
switch y := d.list[j].(type) { return xb.Max.Z < y.ZPos() // x is before y
case BoundingBoxer: }
return x.ZPos() < y.BoundingBox().Min.Z
case ZPositioner: case ZPositioner:
return x.ZPos() < y.ZPos() switch y := v.(type) {
} case BoundingBoxer:
return x.ZPos() < y.BoundingBox().Min.Z
case ZPositioner:
return x.ZPos() < y.ZPos()
} }
} }
// Fallback case: ask the components themselves // Fallback case: ask the components themselves if they have an opinion
return d.list[i].DrawBefore(d.list[j]) || d.list[j].DrawAfter(d.list[i]) if do, ok := u.(DrawOrderer); ok && do.DrawBefore(v) {
return true
}
if do, ok := v.(DrawOrderer); ok && do.DrawAfter(u) {
return true
}
// No relation
return false
} }
func (d drawList) Len() int { return len(d.list) } // Topological sort. Uses a projection π to flatten bounding boxes for
func (d drawList) Swap(i, j int) {
d.rev[d.list[i]], d.rev[d.list[j]] = j, i
d.list[i], d.list[j] = d.list[j], d.list[i]
}
// Slow topological sort. Uses a projection π to flatten bounding boxes for
// overlap tests, in order to reduce edge count. // overlap tests, in order to reduce edge count.
func (d *drawList) topsort(π geom.Projector) { func (d *drawList) topsort(π geom.Projector) {
// Produce edge lists and count indegrees - O(|V|^2) // Produce edge lists and count indegrees - O(|V|^2)
@ -113,7 +103,7 @@ func (d *drawList) topsort(π geom.Projector) {
} }
// If the edge goes u->v, add it. // If the edge goes u->v, add it.
if d.Less(i, j) { if edge(u, v) {
edges[i] = append(edges[i], j) edges[i] = append(edges[i], j)
indegree[j]++ indegree[j]++
} }

View file

@ -8,7 +8,6 @@ import (
"io/fs" "io/fs"
"log" "log"
"reflect" "reflect"
"sort"
"sync" "sync"
"time" "time"
@ -17,7 +16,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
const topologicalDrawSort = true const showDrawListSize = true
var _ interface { var _ interface {
Disabler Disabler
@ -114,7 +113,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
d.Draw(screen, &st.opts) d.Draw(screen, &st.opts)
} }
if true { if showDrawListSize {
// Infodump about draw list // Infodump about draw list
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("len(drawList.list) = %d", len(g.drawList.list)), 0, 30) ebitenutil.DebugPrintAt(screen, fmt.Sprintf("len(drawList.list) = %d", len(g.drawList.list)), 0, 30)
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("len(drawList.rev) = %d", len(g.drawList.list)), 0, 45) ebitenutil.DebugPrintAt(screen, fmt.Sprintf("len(drawList.rev) = %d", len(g.drawList.list)), 0, 45)
@ -185,19 +184,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 { g.drawList.topsort(g.Projection)
g.drawList.topsort(g.Projection)
} else {
sort.Stable(g.drawList)
// Truncate tombstones from the end.
for i := g.drawList.Len() - 1; i >= 0; i-- {
if g.drawList.list[i] != (tombstone{}) {
break
}
g.drawList.list = g.drawList.list[:i]
}
}
return nil return nil
} }

View file

@ -72,6 +72,11 @@ type Disabler interface {
// passed to Game.Register or returned from Scan). // passed to Game.Register or returned from Scan).
type Drawer interface { type Drawer interface {
Draw(*ebiten.Image, *ebiten.DrawImageOptions) Draw(*ebiten.Image, *ebiten.DrawImageOptions)
}
// DrawOrderer components have more specific ideas about draw ordering than
// merely "my Z is bigger than yours".
type DrawOrderer interface {
DrawAfter(Drawer) bool DrawAfter(Drawer) bool
DrawBefore(Drawer) bool DrawBefore(Drawer) bool
} }

View file

@ -40,30 +40,8 @@ func (h *Hides) Hide() { *h = true }
// Show sets h to false. // Show sets h to false.
func (h *Hides) Show() { *h = false } func (h *Hides) Show() { *h = false }
// ZPosition implements DrawAfter and DrawBefore using only Z information. // ZPosition implements ZPositioner directly (as an int).
type ZPosition int type ZPosition int
// DrawAfter reports if z >= x.Max.Z.
func (z ZPosition) DrawAfter(x Drawer) bool {
switch x := x.(type) {
case BoundingBoxer:
return int(z) >= x.BoundingBox().Max.Z
case ZPositioner:
return z.ZPos() > x.ZPos()
}
return false
}
// DrawBefore reports if z < x.Min.Z.
func (z ZPosition) DrawBefore(x Drawer) bool {
switch x := x.(type) {
case BoundingBoxer:
return int(z) < x.BoundingBox().Min.Z
case ZPositioner:
return z.ZPos() < x.ZPos()
}
return false
}
// ZPos returns itself. // ZPos returns itself.
func (z ZPosition) ZPos() int { return int(z) } func (z ZPosition) ZPos() int { return int(z) }

View file

@ -158,20 +158,6 @@ func (p *Prism) DrawAfter(x Drawer) bool {
case BoundingBoxer: case BoundingBoxer:
xb := x.BoundingBox() xb := x.BoundingBox()
if !commonDrawerComparisons {
if pb.Max.Z <= xb.Min.Z { // p is behind x
return false
}
if pb.Min.Z >= xb.Max.Z { // p is in front of x
return true
}
if pb.Min.Y >= xb.Max.Y { // p is below x
return false
}
if pb.Max.Y <= xb.Min.Y { // p is above x
return true
}
}
// The prism special // The prism special
split := p.m.topext[geom.North].X split := p.m.topext[geom.North].X
threshold := p.m.topext[geom.East].Y threshold := p.m.topext[geom.East].Y
@ -184,9 +170,6 @@ func (p *Prism) DrawAfter(x Drawer) bool {
if pb.Min.Z+threshold >= xb.Max.Z { // x is behind the front half of p if pb.Min.Z+threshold >= xb.Max.Z { // x is behind the front half of p
return true return true
} }
case ZPositioner:
return pb.Min.Z > x.ZPos() // p is after x
} }
return false return false
} }
@ -204,20 +187,6 @@ func (p *Prism) DrawBefore(x Drawer) bool {
case BoundingBoxer: case BoundingBoxer:
xb := x.BoundingBox() xb := x.BoundingBox()
if !commonDrawerComparisons {
if pb.Min.Z >= xb.Max.Z { // p is in front of x
return false
}
if pb.Max.Z <= xb.Min.Z { // p is behind x
return true
}
if pb.Max.Y <= xb.Min.Y { // p is above x
return false
}
if pb.Min.Y >= xb.Max.Y { // p is below x
return true
}
}
// The prism special // The prism special
split := p.m.topext[geom.North].X split := p.m.topext[geom.North].X
threshold := p.m.topext[geom.East].Y threshold := p.m.topext[geom.East].Y
@ -230,9 +199,6 @@ func (p *Prism) DrawBefore(x Drawer) bool {
if pb.Min.Z+threshold <= xb.Min.Z { // x is in front of the front half of p if pb.Min.Z+threshold <= xb.Min.Z { // x is in front of the front half of p
return true return true
} }
case ZPositioner:
return pb.Max.Z < x.ZPos() // p is before x
} }
return false return false
} }

View file

@ -40,58 +40,6 @@ func (s *Sprite) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
screen.DrawImage(s.Sheet.SubImage(s.anim.Cell()), opts) screen.DrawImage(s.Sheet.SubImage(s.anim.Cell()), opts)
} }
// DrawAfter reports if the sprite should be drawn after x.
func (s *Sprite) DrawAfter(x Drawer) bool {
if !commonDrawerComparisons {
sb := s.BoundingBox()
switch x := x.(type) {
case BoundingBoxer:
xb := x.BoundingBox()
if sb.Max.Z <= xb.Min.Z { // s is behind x
return false
}
if sb.Min.Z >= xb.Max.Z { // s is in front of x
return true
}
if sb.Min.Y >= xb.Max.Y { // s is below x
return false
}
if sb.Max.Y <= xb.Min.Y { // s is above x
return true
}
case ZPositioner:
return sb.Min.Z > x.ZPos() // s is after
}
}
return false
}
// DrawBefore reports if the sprite should be drawn before x.
func (s *Sprite) DrawBefore(x Drawer) bool {
if !commonDrawerComparisons {
sb := s.BoundingBox()
switch x := x.(type) {
case BoundingBoxer:
xb := x.BoundingBox()
if sb.Min.Z >= xb.Max.Z { // s is in front of x
return false
}
if sb.Max.Z <= xb.Min.Z { // s is behind x
return true
}
if sb.Max.Y <= xb.Min.Y { // s is above x
return false
}
if sb.Min.Y >= xb.Max.Y { // s is below x
return true
}
case ZPositioner:
return sb.Max.Z < x.ZPos() // s is before
}
}
return false
}
// Scan returns the Actor and the Sheet. // Scan returns the Actor and the Sheet.
func (s *Sprite) Scan() []interface{} { func (s *Sprite) Scan() []interface{} {
return []interface{}{ return []interface{}{

View file

@ -11,6 +11,8 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
) )
const awakemanProducesBubbles = true
var _ interface { var _ interface {
engine.Identifier engine.Identifier
engine.Disabler engine.Disabler
@ -109,7 +111,7 @@ func (aw *Awakeman) realUpdate() error {
bubblePeriod = 6 bubblePeriod = 6
) )
if true { if awakemanProducesBubbles {
// Add a bubble? // Add a bubble?
aw.bubbleTimer-- aw.bubbleTimer--
if aw.bubbleTimer <= 0 { if aw.bubbleTimer <= 0 {