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 (
|
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,12 +43,17 @@ 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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue