change how byAB works

This commit is contained in:
Josh Deprez 2021-09-29 11:55:09 +10:00
parent a9401b505a
commit 6792db5d7a
4 changed files with 84 additions and 37 deletions

View file

@ -2,6 +2,8 @@ package engine
import ( import (
"encoding/gob" "encoding/gob"
"errors"
"log"
"drjosh.dev/gurgle/geom" "drjosh.dev/gurgle/geom"
) )
@ -12,6 +14,8 @@ var _ interface {
Prepper Prepper
} = &Actor{} } = &Actor{}
var errCollision = errors.New("collision detected")
func init() { func init() {
gob.Register(&Actor{}) gob.Register(&Actor{})
} }
@ -39,13 +43,18 @@ func (a *Actor) BoundingBox() geom.Box {
func (a *Actor) CollidesAt(p geom.Int3) bool { func (a *Actor) CollidesAt(p geom.Int3) bool {
bounds := a.Bounds.Add(p) bounds := a.Bounds.Add(p)
cd := a.game.Component(a.CollisionDomain) cd := a.game.Component(a.CollisionDomain)
for c := range a.game.Query(cd, ColliderType) { if cd == nil {
if c.(Collider).CollidesWith(bounds) { log.Printf("collision domain %q not found in game", a.CollisionDomain)
return true
}
}
return false return false
} }
return errCollision == a.game.Query(cd, ColliderType, func(c interface{}) error {
log.Printf("checking for collision with %v", c)
if c.(Collider).CollidesWith(bounds) {
return errCollision
}
return nil
})
}
// MoveX moves the actor x units in world space. It takes Game.VoxelScale into // MoveX moves the actor x units in world space. It takes Game.VoxelScale into
// account (so MoveX(x) moves the actor x/VoxelScale.X voxel units). onCollide // account (so MoveX(x) moves the actor x/VoxelScale.X voxel units). onCollide

View file

@ -154,17 +154,17 @@ func (d *DrawDAG) Update() error {
// descendants of a different DrawManager. // descendants of a different DrawManager.
func (d *DrawDAG) Register(component, _ interface{}) error { func (d *DrawDAG) Register(component, _ interface{}) error {
// *Don't* register the component if it is inside a descendant DrawManager. // *Don't* register the component if it is inside a descendant DrawManager.
// These queries work because component should be registered in game before // Using Parent works because component should be registered in game before
// this call. // this call.
for dm := range d.game.Query(d, DrawManagerType) { for p := component; p != d && p != nil; p = d.game.Parent(p) {
if dm == d { if _, isDM := p.(DrawManager); isDM {
continue if p == d {
} break
dbs := d.game.Query(dm, DrawBoxerType) } else {
if _, found := dbs[component]; found {
return nil return nil
} }
} }
}
// Register db, and then subcomponents recursively. // Register db, and then subcomponents recursively.
if db, ok := component.(DrawBoxer); ok { if db, ok := component.(DrawBoxer); ok {
d.registerOne(db) d.registerOne(db)

View file

@ -8,6 +8,7 @@ import (
"io/fs" "io/fs"
"log" "log"
"reflect" "reflect"
"strings"
"sync" "sync"
"time" "time"
@ -33,9 +34,6 @@ func init() {
gob.Register(&Game{}) gob.Register(&Game{})
} }
// ComponentSet is a set of components.
type ComponentSet map[interface{}]struct{}
// Game implements the ebiten methods using a collection of components. One // Game implements the ebiten methods using a collection of components. One
// component must be the designated root component. // component must be the designated root component.
type Game struct { type Game struct {
@ -48,7 +46,7 @@ type Game struct {
dbmu sync.RWMutex dbmu sync.RWMutex
byID map[string]Identifier // Named components by ID byID map[string]Identifier // Named components by ID
byAB map[abKey]ComponentSet // Ancestor/behaviour index byAB map[abKey]ComponentSet // paths matching interface
parent map[interface{}]interface{} // parent[x] is parent of x parent map[interface{}]interface{} // parent[x] is parent of x
children map[interface{}]ComponentSet // children[x] are chilren of x children map[interface{}]ComponentSet // children[x] are chilren of x
} }
@ -101,14 +99,15 @@ func (g *Game) Component(id string) Identifier {
} }
// Parent returns the parent of a given component, or nil if there is none. // Parent returns the parent of a given component, or nil if there is none.
// This only returns sensible values for registered components (e.g. after // This only returns sensible values for registered components.
// LoadAndPrepare).
func (g *Game) Parent(c interface{}) interface{} { func (g *Game) Parent(c interface{}) interface{} {
g.dbmu.RLock() g.dbmu.RLock()
defer g.dbmu.RUnlock() defer g.dbmu.RUnlock()
return g.parent[c] return g.parent[c]
} }
// Children returns the direct subcomponents of the given component, or nil if
// there are none. This only returns sensible values for registered components.
func (g *Game) Children(c interface{}) ComponentSet { func (g *Game) Children(c interface{}) ComponentSet {
g.dbmu.RLock() g.dbmu.RLock()
defer g.dbmu.RUnlock() defer g.dbmu.RUnlock()
@ -164,10 +163,24 @@ func (g *Game) ReversePath(component interface{}) []interface{} {
// Query looks for components having both a given ancestor and implementing // Query looks for components having both a given ancestor and implementing
// a given behaviour (see Behaviors in interface.go). This only returns sensible // a given behaviour (see Behaviors in interface.go). This only returns sensible
// values after LoadAndPrepare. Note that every component is its own ancestor. // values after LoadAndPrepare. Note that every component is its own ancestor.
func (g *Game) Query(ancestor interface{}, behaviour reflect.Type) ComponentSet { func (g *Game) Query(ancestor interface{}, behaviour reflect.Type, visit func(interface{}) error) error {
// NB: per the godoc, do not use RLock for recursive read locking.
g.dbmu.RLock() g.dbmu.RLock()
defer g.dbmu.RUnlock() defer g.dbmu.RUnlock()
return g.byAB[abKey{ancestor, behaviour}] return g.queryRecursive(ancestor, behaviour, visit)
}
// Post-order visit p and descendants of p having behaviour b.
func (g *Game) queryRecursive(p interface{}, b reflect.Type, v func(interface{}) error) error {
for x := range g.byAB[abKey{p, b}] {
if err := g.queryRecursive(x, b, v); err != nil {
return err
}
}
if reflect.TypeOf(p).Implements(b) {
return v(p)
}
return nil
} }
// Scan visits g.Root. // Scan visits g.Root.
@ -301,13 +314,16 @@ func (g *Game) registerOne(component, parent interface{}) error {
if !ct.Implements(b) { if !ct.Implements(b) {
continue continue
} }
// TODO: better than O(len(path)^2) time and memory? for c, p := component, g.parent[component]; p != nil; c, p = p, g.parent[p] {
for p := component; p != nil; p = g.parent[p] {
k := abKey{p, b} k := abKey{p, b}
if g.byAB[k] == nil { if g.byAB[k] == nil {
g.byAB[k] = make(ComponentSet) g.byAB[k] = ComponentSet{c: {}}
continue
} }
g.byAB[k][component] = struct{}{} if _, exists := g.byAB[k][c]; exists {
break
}
g.byAB[k][c] = struct{}{}
} }
} }
return nil return nil
@ -336,15 +352,17 @@ func (g *Game) unregisterRecursive(component interface{}) {
} }
func (g *Game) unregisterOne(component interface{}) { func (g *Game) unregisterOne(component interface{}) {
// unregister from g.byAB, using g.par to trace the path // unregister from g.byAB
ct := reflect.TypeOf(component) ct := reflect.TypeOf(component)
for _, b := range Behaviours { for _, b := range Behaviours {
if !ct.Implements(b) { if !ct.Implements(b) {
continue continue
} }
for p := component; p != nil; p = g.parent[p] { for c, p := component, g.parent[component]; p != nil; c, p = p, g.parent[p] {
if k := (abKey{p, b}); g.byAB[k] != nil { k := abKey{p, b}
delete(g.byAB[k], component) delete(g.byAB[k], c)
if len(g.byAB[k]) > 0 {
break
} }
} }
} }
@ -363,11 +381,29 @@ func (g *Game) String() string { return "Game" }
// --------- Helper stuff --------- // --------- Helper stuff ---------
// ComponentSet is a set of components.
type ComponentSet map[interface{}]struct{}
func (c ComponentSet) String() string {
var b strings.Builder
b.WriteString("{")
for x := range c {
fmt.Fprint(&b, " ", x)
}
b.WriteString(" }")
return b.String()
}
// abKey is the key type for game.byAB.
type abKey struct { type abKey struct {
ancestor interface{} parent interface{}
behaviour reflect.Type behaviour reflect.Type
} }
func (a abKey) String() string {
return fmt.Sprintf("(%v %s)", a.parent, a.behaviour.Name())
}
// concatOpts returns the combined options (as though a was applied and then b). // concatOpts returns the combined options (as though a was applied and then b).
func concatOpts(a, b ebiten.DrawImageOptions) ebiten.DrawImageOptions { func concatOpts(a, b ebiten.DrawImageOptions) ebiten.DrawImageOptions {
a.ColorM.Concat(b.ColorM) a.ColorM.Concat(b.ColorM)

View file

@ -128,6 +128,7 @@ func (g *Game) cmdQuery(dst io.Writer, argv []string) {
} }
if behaviour == nil { if behaviour == nil {
fmt.Fprintf(dst, "Unknown behaviour %q\n", argv[1]) fmt.Fprintf(dst, "Unknown behaviour %q\n", argv[1])
return
} }
var ancestor interface{} = g var ancestor interface{} = g
@ -135,23 +136,24 @@ func (g *Game) cmdQuery(dst io.Writer, argv []string) {
c := g.Component(argv[2]) c := g.Component(argv[2])
if c == nil { if c == nil {
fmt.Fprintf(dst, "Component %q not found\n", argv[2]) fmt.Fprintf(dst, "Component %q not found\n", argv[2])
return
} }
ancestor = c ancestor = c
} }
x := g.Query(ancestor, behaviour) noResults := true
if len(x) == 0 { g.Query(ancestor, behaviour, func(c interface{}) error {
fmt.Fprintln(dst, "No results") noResults = false
return
}
for c := range x {
i, ok := c.(Identifier) i, ok := c.(Identifier)
if ok { if ok {
fmt.Fprintf(dst, "%T %q\n", c, i.Ident()) fmt.Fprintf(dst, "%T %q\n", c, i.Ident())
} else { } else {
fmt.Fprintf(dst, "%T\n", c) fmt.Fprintf(dst, "%T\n", c)
} }
return nil
})
if noResults {
fmt.Fprintln(dst, "No results")
} }
} }