Update uses Query, Query is fixed

This commit is contained in:
Josh Deprez 2021-09-29 13:38:32 +10:00
parent 6792db5d7a
commit 3ad1cf8d4d
3 changed files with 52 additions and 36 deletions

View file

@ -44,11 +44,10 @@ 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)
if cd == nil { if cd == nil {
log.Printf("collision domain %q not found in game", a.CollisionDomain) log.Printf("collision domain %q not found", a.CollisionDomain)
return false return false
} }
return errCollision == a.game.Query(cd, ColliderType, func(c interface{}) error { return errCollision == a.game.Query(cd, ColliderType, nil, func(c interface{}) error {
log.Printf("checking for collision with %v", c)
if c.(Collider).CollidesWith(bounds) { if c.(Collider).CollidesWith(bounds) {
return errCollision return errCollision
} }

View file

@ -66,24 +66,18 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (w, h int) {
// Update updates everything. // Update updates everything.
func (g *Game) Update() error { func (g *Game) Update() error {
return g.updateRecursive(g) //return g.updateRecursive(g)
} return g.Query(g.Root, UpdaterType,
func(c interface{}) error {
// updateRecursive updates everything in a post-order traversal. It terminates
// the recursion early if the component reports it is Disabled.
func (g *Game) updateRecursive(c interface{}) error {
if d, ok := c.(Disabler); ok && d.Disabled() { if d, ok := c.(Disabler); ok && d.Disabled() {
return nil return Skip
}
if sc, ok := c.(Scanner); ok {
if err := sc.Scan(g.updateRecursive); err != nil {
return err
}
}
if u, ok := c.(Updater); ok && c != g {
return u.Update()
} }
return nil return nil
},
func(c interface{}) error {
return c.(Updater).Update()
},
)
} }
// Ident returns "__GAME__". // Ident returns "__GAME__".
@ -162,23 +156,37 @@ 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 for registered components.
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. // Note that every component is its own ancestor.
g.dbmu.RLock() //
defer g.dbmu.RUnlock() // visitPre is visited before descendants, while visitPost is visited after
return g.queryRecursive(ancestor, behaviour, visit) // descendants. nil visitors are ignored.
} func (g *Game) Query(ancestor interface{}, behaviour reflect.Type, visitPre, visitPost func(interface{}) error) error {
pi := reflect.TypeOf(ancestor).Implements(behaviour)
// Post-order visit p and descendants of p having behaviour b. if pi && visitPre != nil {
func (g *Game) queryRecursive(p interface{}, b reflect.Type, v func(interface{}) error) error { if err := visitPre(ancestor); err != nil {
for x := range g.byAB[abKey{p, b}] {
if err := g.queryRecursive(x, b, v); err != nil {
return err return err
} }
} }
if reflect.TypeOf(p).Implements(b) { // * Update uses Query.
return v(p) // * Updaters can Register new components.
// * Register acquires g.dbmu.Lock.
// ==> Wrapping the whole thing in RLock would deadlock.
// Make the read lock as tight as possible.
g.dbmu.RLock()
q := g.byAB[abKey{ancestor, behaviour}]
g.dbmu.RUnlock()
for x := range q {
if err := g.Query(x, behaviour, visitPre, visitPost); err != nil {
if errors.Is(err, Skip) {
continue
}
return err
}
}
if pi && visitPost != nil {
return visitPost(ancestor)
} }
return nil return nil
} }
@ -416,3 +424,12 @@ func concatOpts(a, b ebiten.DrawImageOptions) ebiten.DrawImageOptions {
} }
return a return a
} }
// Skip is an "error" value that can be returned from visitor callbacks. It
// tells recursive methods of Game to skip processing the current item and its
// descendants, but will otherwise continue processing.
const Skip = skip("skip")
type skip string
func (s skip) Error() string { return string(s) }

View file

@ -151,7 +151,7 @@ func (g *Game) cmdQuery(dst io.Writer, argv []string) {
fmt.Fprintf(dst, "%T\n", c) fmt.Fprintf(dst, "%T\n", c)
} }
return nil return nil
}) }, nil)
if noResults { if noResults {
fmt.Fprintln(dst, "No results") fmt.Fprintln(dst, "No results")
} }