query
This commit is contained in:
parent
cf34a7785c
commit
12e3254a34
4 changed files with 142 additions and 12 deletions
|
@ -25,7 +25,7 @@ type Actor struct {
|
||||||
Pos image.Point
|
Pos image.Point
|
||||||
Size image.Point
|
Size image.Point
|
||||||
|
|
||||||
collisionDomain interface{}
|
collisionDomain []Collider
|
||||||
xRem, yRem float64
|
xRem, yRem float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func (a *Actor) BoundingRect() image.Rectangle { return image.Rectangle{a.Pos, a
|
||||||
|
|
||||||
func (a *Actor) CollidesAt(p image.Point) bool {
|
func (a *Actor) CollidesAt(p image.Point) bool {
|
||||||
bounds := image.Rectangle{Min: p, Max: p.Add(a.Size)}
|
bounds := image.Rectangle{Min: p, Max: p.Add(a.Size)}
|
||||||
return nil != Walk(a.collisionDomain, func(c interface{}, _ []interface{}) error {
|
/*return nil != Walk(a.collisionDomain, func(c interface{}, _ []interface{}) error {
|
||||||
coll, ok := c.(Collider)
|
coll, ok := c.(Collider)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
@ -42,7 +42,13 @@ func (a *Actor) CollidesAt(p image.Point) bool {
|
||||||
return Collision{With: coll}
|
return Collision{With: coll}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})*/
|
||||||
|
for _, c := range a.collisionDomain {
|
||||||
|
if c.CollidesWith(bounds) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) MoveX(dx float64, onCollide func()) {
|
func (a *Actor) MoveX(dx float64, onCollide func()) {
|
||||||
|
@ -86,7 +92,13 @@ func (a *Actor) MoveY(dy float64, onCollide func()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) Prepare(g *Game) error {
|
func (a *Actor) Prepare(g *Game) error {
|
||||||
a.collisionDomain = g.Component(a.CollisionDomain)
|
//a.collisionDomain = g.Component(a.CollisionDomain)
|
||||||
|
|
||||||
|
cs := g.Query(g.Component(a.CollisionDomain), ColliderType)
|
||||||
|
a.collisionDomain = make([]Collider, 0, len(cs))
|
||||||
|
for _, c := range cs {
|
||||||
|
a.collisionDomain = append(a.collisionDomain, c.(Collider))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
@ -24,7 +25,13 @@ type Game struct {
|
||||||
Root DrawUpdater // typically a *Scene or SceneRef though
|
Root DrawUpdater // typically a *Scene or SceneRef though
|
||||||
|
|
||||||
dbmu sync.RWMutex
|
dbmu sync.RWMutex
|
||||||
db map[string]Identifier // Components by ID
|
db map[string]Identifier // Named components by ID
|
||||||
|
dex map[dexKey][]interface{} // Ancestor/behaviour index
|
||||||
|
}
|
||||||
|
|
||||||
|
type dexKey struct {
|
||||||
|
ancestor interface{}
|
||||||
|
behaviour reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw draws the entire thing, with default draw options.
|
// Draw draws the entire thing, with default draw options.
|
||||||
|
@ -48,7 +55,22 @@ func (g *Game) Update() error {
|
||||||
return g.Root.Update()
|
return g.Root.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) registerComponent(c interface{}, p []interface{}) error {
|
func (g *Game) registerComponent(c interface{}, path []interface{}) error {
|
||||||
|
// register in g.dex
|
||||||
|
for _, b := range Behaviours {
|
||||||
|
ct := reflect.TypeOf(c)
|
||||||
|
if !ct.Implements(b) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := dexKey{c, b}
|
||||||
|
g.dex[k] = append(g.dex[k], c)
|
||||||
|
for _, p := range path {
|
||||||
|
k := dexKey{p, b}
|
||||||
|
g.dex[k] = append(g.dex[k], c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register in g.db
|
||||||
i, ok := c.(Identifier)
|
i, ok := c.(Identifier)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
@ -65,12 +87,22 @@ func (g *Game) registerComponent(c interface{}, p []interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component returns the component with a given ID, or nil if there is none.
|
// Component returns the component with a given ID, or nil if there is none.
|
||||||
|
// This only returns sensible values after LoadAndPrepare.
|
||||||
func (g *Game) Component(id string) Identifier {
|
func (g *Game) Component(id string) Identifier {
|
||||||
g.dbmu.RLock()
|
g.dbmu.RLock()
|
||||||
defer g.dbmu.RUnlock()
|
defer g.dbmu.RUnlock()
|
||||||
return g.db[id]
|
return g.db[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) []interface{} {
|
||||||
|
g.dbmu.RLock()
|
||||||
|
defer g.dbmu.RUnlock()
|
||||||
|
return g.dex[dexKey{ancestor, behaviour}]
|
||||||
|
}
|
||||||
|
|
||||||
// Scan implements Scanner.
|
// Scan implements Scanner.
|
||||||
func (g *Game) Scan() []interface{} { return []interface{}{g.Root} }
|
func (g *Game) Scan() []interface{} { return []interface{}{g.Root} }
|
||||||
|
|
||||||
|
@ -97,11 +129,9 @@ func walk(c interface{}, p []interface{}, v func(interface{}, []interface{}) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAndPrepare first calls Load on all Loaders. Once loading is complete,
|
// LoadAndPrepare first calls Load on all Loaders. Once loading is complete, it
|
||||||
// it builds the component database and then calls Prepare on every Preparer.
|
// builds the component databases and then calls Prepare on every Preparer.
|
||||||
// You must call Prepare before any calls
|
// LoadAndPrepare must be called before any calls to Component or Query.
|
||||||
// to Component. You may call Prepare again (e.g. as an alternative to
|
|
||||||
// fastidiously calling RegisterComponent/UnregisterComponent).
|
|
||||||
func (g *Game) LoadAndPrepare(assets fs.FS) error {
|
func (g *Game) LoadAndPrepare(assets fs.FS) error {
|
||||||
// Load all the Loaders
|
// Load all the Loaders
|
||||||
if err := Walk(g, func(c interface{}, _ []interface{}) error {
|
if err := Walk(g, func(c interface{}, _ []interface{}) error {
|
||||||
|
@ -117,6 +147,7 @@ func (g *Game) LoadAndPrepare(assets fs.FS) error {
|
||||||
// Build the component databases
|
// Build the component databases
|
||||||
g.dbmu.Lock()
|
g.dbmu.Lock()
|
||||||
g.db = make(map[string]Identifier)
|
g.db = make(map[string]Identifier)
|
||||||
|
g.dex = make(map[dexKey][]interface{})
|
||||||
if err := Walk(g, g.registerComponent); err != nil {
|
if err := Walk(g, g.registerComponent); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,53 @@ package engine
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Reflection types used for queries... Is there a better way?
|
||||||
|
var (
|
||||||
|
// TypeOf(pointer to interface).Elem() is "idiomatic" -
|
||||||
|
// see https://pkg.go.dev/reflect#example-TypeOf
|
||||||
|
AnimerType = reflect.TypeOf((*Animer)(nil)).Elem()
|
||||||
|
BounderType = reflect.TypeOf((*Bounder)(nil)).Elem()
|
||||||
|
ColliderType = reflect.TypeOf((*Collider)(nil)).Elem()
|
||||||
|
DisablerType = reflect.TypeOf((*Disabler)(nil)).Elem()
|
||||||
|
DrawerType = reflect.TypeOf((*Drawer)(nil)).Elem()
|
||||||
|
DrawOrdererType = reflect.TypeOf((*DrawOrderer)(nil)).Elem()
|
||||||
|
DrawUpdaterType = reflect.TypeOf((*DrawUpdater)(nil)).Elem()
|
||||||
|
HiderType = reflect.TypeOf((*Hider)(nil)).Elem()
|
||||||
|
IdentifierType = reflect.TypeOf((*Identifier)(nil)).Elem()
|
||||||
|
LoaderType = reflect.TypeOf((*Loader)(nil)).Elem()
|
||||||
|
ParallaxScalerType = reflect.TypeOf((*ParallaxScaler)(nil)).Elem()
|
||||||
|
PrepperType = reflect.TypeOf((*Prepper)(nil)).Elem()
|
||||||
|
ScannerType = reflect.TypeOf((*Scanner)(nil)).Elem()
|
||||||
|
ScenerType = reflect.TypeOf((*Scener)(nil)).Elem()
|
||||||
|
SaverType = reflect.TypeOf((*Saver)(nil)).Elem()
|
||||||
|
UpdaterType = reflect.TypeOf((*Updater)(nil)).Elem()
|
||||||
|
|
||||||
|
// Behaviours lists all the behaviours that can be queried with Game.Query.
|
||||||
|
Behaviours = []reflect.Type{
|
||||||
|
AnimerType,
|
||||||
|
BounderType,
|
||||||
|
ColliderType,
|
||||||
|
DisablerType,
|
||||||
|
DrawerType,
|
||||||
|
DrawOrdererType,
|
||||||
|
DrawUpdaterType,
|
||||||
|
HiderType,
|
||||||
|
IdentifierType,
|
||||||
|
LoaderType,
|
||||||
|
ParallaxScalerType,
|
||||||
|
PrepperType,
|
||||||
|
ScannerType,
|
||||||
|
ScenerType,
|
||||||
|
SaverType,
|
||||||
|
UpdaterType,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Animer components have a current frame index.
|
// Animer components have a current frame index.
|
||||||
type Animer interface {
|
type Animer interface {
|
||||||
Updater
|
Updater
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,6 +34,8 @@ func (g *Game) REPL(src io.Reader, dst io.Writer, assets fs.FS) error {
|
||||||
g.cmdReload(dst, assets)
|
g.cmdReload(dst, assets)
|
||||||
case "tree":
|
case "tree":
|
||||||
g.cmdTree(dst, argv)
|
g.cmdTree(dst, argv)
|
||||||
|
case "query":
|
||||||
|
g.cmdQuery(dst, argv)
|
||||||
}
|
}
|
||||||
fmt.Fprint(dst, prompt)
|
fmt.Fprint(dst, prompt)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +78,7 @@ func (g *Game) cmdTree(dst io.Writer, argv []string) {
|
||||||
if len(argv) < 1 || len(argv) > 2 {
|
if len(argv) < 1 || len(argv) > 2 {
|
||||||
fmt.Println(dst, "Usage: tree [ID]")
|
fmt.Println(dst, "Usage: tree [ID]")
|
||||||
}
|
}
|
||||||
var c interface{} = g
|
c := interface{}(g)
|
||||||
if len(argv) == 2 { // subtree
|
if len(argv) == 2 { // subtree
|
||||||
id := argv[1]
|
id := argv[1]
|
||||||
c = g.Component(id)
|
c = g.Component(id)
|
||||||
|
@ -98,3 +101,44 @@ func (g *Game) cmdTree(dst io.Writer, argv []string) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Game) cmdQuery(dst io.Writer, argv []string) {
|
||||||
|
if len(argv) < 2 || len(argv) > 3 {
|
||||||
|
fmt.Fprintln(dst, "Usage: query BEHAVIOUR [ANCESTOR_ID]")
|
||||||
|
}
|
||||||
|
|
||||||
|
var behav reflect.Type
|
||||||
|
for _, b := range Behaviours {
|
||||||
|
if b.Name() == argv[1] {
|
||||||
|
behav = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if behav == nil {
|
||||||
|
fmt.Fprintf(dst, "Unknown behaviour %q\n", argv[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
ances := interface{}(g)
|
||||||
|
if len(argv) == 3 {
|
||||||
|
id := argv[2]
|
||||||
|
ances = g.Component(id)
|
||||||
|
if ances == nil {
|
||||||
|
fmt.Fprintf(dst, "Component %q not found\n", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x := g.Query(ances, behav)
|
||||||
|
if len(x) == 0 {
|
||||||
|
fmt.Fprintln(dst, "No results")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range x {
|
||||||
|
i, ok := c.(Identifier)
|
||||||
|
if ok {
|
||||||
|
fmt.Fprintf(dst, "%T %s\n", c, i.Ident())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(dst, "%T\n", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue