change how byAB works
This commit is contained in:
parent
a9401b505a
commit
6792db5d7a
4 changed files with 84 additions and 37 deletions
|
@ -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 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
|
||||
|
|
|
@ -154,17 +154,17 @@ 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 {
|
||||
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.
|
||||
if db, ok := component.(DrawBoxer); ok {
|
||||
d.registerOne(db)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue