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 (
"encoding/gob"
"errors"
"log"
"drjosh.dev/gurgle/geom"
)
@ -12,6 +14,8 @@ var _ interface {
Prepper
} = &Actor{}
var errCollision = errors.New("collision detected")
func init() {
gob.Register(&Actor{})
}
@ -39,12 +43,17 @@ func (a *Actor) BoundingBox() geom.Box {
func (a *Actor) CollidesAt(p geom.Int3) bool {
bounds := a.Bounds.Add(p)
cd := a.game.Component(a.CollisionDomain)
for c := range a.game.Query(cd, ColliderType) {
if c.(Collider).CollidesWith(bounds) {
return true
}
if cd == nil {
log.Printf("collision domain %q not found in game", a.CollisionDomain)
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

View file

@ -154,15 +154,15 @@ func (d *DrawDAG) Update() error {
// descendants of a different DrawManager.
func (d *DrawDAG) Register(component, _ interface{}) error {
// *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.
for dm := range d.game.Query(d, DrawManagerType) {
if dm == d {
continue
}
dbs := d.game.Query(dm, DrawBoxerType)
if _, found := dbs[component]; found {
return nil
for p := component; p != d && p != nil; p = d.game.Parent(p) {
if _, isDM := p.(DrawManager); isDM {
if p == d {
break
} else {
return nil
}
}
}
// Register db, and then subcomponents recursively.

View file

@ -8,6 +8,7 @@ import (
"io/fs"
"log"
"reflect"
"strings"
"sync"
"time"
@ -33,9 +34,6 @@ func init() {
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
// component must be the designated root component.
type Game struct {
@ -48,7 +46,7 @@ type Game struct {
dbmu sync.RWMutex
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
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.
// This only returns sensible values for registered components (e.g. after
// LoadAndPrepare).
// This only returns sensible values for registered components.
func (g *Game) Parent(c interface{}) interface{} {
g.dbmu.RLock()
defer g.dbmu.RUnlock()
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 {
g.dbmu.RLock()
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
// a given behaviour (see Behaviors in interface.go). This only returns sensible
// 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()
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.
@ -301,13 +314,16 @@ func (g *Game) registerOne(component, parent interface{}) error {
if !ct.Implements(b) {
continue
}
// TODO: better than O(len(path)^2) time and memory?
for p := component; p != nil; p = g.parent[p] {
for c, p := component, g.parent[component]; p != nil; c, p = p, g.parent[p] {
k := abKey{p, b}
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
@ -336,15 +352,17 @@ func (g *Game) unregisterRecursive(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)
for _, b := range Behaviours {
if !ct.Implements(b) {
continue
}
for p := component; p != nil; p = g.parent[p] {
if k := (abKey{p, b}); g.byAB[k] != nil {
delete(g.byAB[k], component)
for c, p := component, g.parent[component]; p != nil; c, p = p, g.parent[p] {
k := abKey{p, b}
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 ---------
// 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 {
ancestor interface{}
parent interface{}
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).
func concatOpts(a, b ebiten.DrawImageOptions) ebiten.DrawImageOptions {
a.ColorM.Concat(b.ColorM)

View file

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