Query now visits everything on the path

This is used fixes some bugs related to parent behaviours not correctly
affecting descendants where the parent component does not have the
behaviour being queried.
This commit is contained in:
Josh Deprez 2021-10-04 15:57:31 +11:00
parent d28762468f
commit 463115efcc
5 changed files with 70 additions and 42 deletions

View file

@ -64,7 +64,7 @@ func (a *Actor) CollidesAt(p geom.Int3) bool {
return false
}
return errCollision == a.game.Query(cd, ColliderType, nil, func(c interface{}) error {
if c.(Collider).CollidesWith(bounds) {
if cl, ok := c.(Collider); ok && cl.CollidesWith(bounds) {
return errCollision
}
return nil

View file

@ -170,7 +170,9 @@ func (d *DrawDAG) Update() error {
// descendants of a different DrawManager.
func (d *DrawDAG) Register(component, _ interface{}) error {
return d.game.Query(component, DrawBoxerType, func(c interface{}) error {
d.registerOne(c.(DrawBoxer))
if db, ok := c.(DrawBoxer); ok {
d.registerOne(db)
}
if _, isDM := c.(DrawManager); isDM && c != d {
return Skip
}
@ -235,7 +237,9 @@ func (d *DrawDAG) registerOne(x DrawBoxer) {
// Unregister unregisters the component and all subcomponents.
func (d *DrawDAG) Unregister(component interface{}) {
d.game.Query(component, DrawBoxerType, func(c interface{}) error {
d.unregisterOne(c.(DrawBoxer))
if db, ok := c.(DrawBoxer); ok {
d.unregisterOne(db)
}
if _, isDM := c.(DrawManager); isDM && c != d {
return Skip
}

View file

@ -46,39 +46,43 @@ type DrawDFS struct {
}
func (d *DrawDFS) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
d.drawRecursive(screen, *opts, d)
stack := []ebiten.DrawImageOptions{*opts}
d.game.Query(d, DrawerType,
// visitPre
func(x interface{}) error {
if h, ok := x.(Hider); ok && h.Hidden() {
return Skip
}
opts := stack[len(stack)-1]
if tf, ok := x.(Transformer); ok {
opts = concatOpts(tf.Transform(), opts)
stack = append(stack, opts)
}
if x == d { // neither draw nor skip d itself
return nil
}
if dr, ok := x.(Drawer); ok {
dr.Draw(screen, &opts)
}
if _, isDM := x.(DrawManager); isDM {
return Skip
}
return nil
},
// visitPost
func(x interface{}) error {
if _, ok := x.(Transformer); ok {
stack = stack[:len(stack)-1]
}
return nil
},
)
}
// ManagesDrawingSubcomponents is present so DrawDFS is recognised as a
// DrawManager.
func (DrawDFS) ManagesDrawingSubcomponents() {}
func (d *DrawDFS) drawRecursive(screen *ebiten.Image, opts ebiten.DrawImageOptions, component interface{}) {
// Hidden? stop drawing
if h, ok := component.(Hider); ok && h.Hidden() {
return
}
// Has a transform? apply to opts
if tf, ok := component.(Transformer); ok {
opts = concatOpts(tf.Transform(), opts)
}
if component != d {
// Does it draw itself? Draw
if dr, ok := component.(Drawer); ok {
dr.Draw(screen, &opts)
}
// Is it a DrawManager? It manages drawing all its subcomponents.
if _, ok := component.(DrawManager); ok {
return
}
}
// Has subcomponents? recurse
d.game.Children(component).Scan(func(x interface{}) error {
d.drawRecursive(screen, opts, x)
return nil
})
}
func (d *DrawDFS) Prepare(g *Game) error {
d.game = g
return nil

View file

@ -84,12 +84,16 @@ func (g *Game) Update() error {
return g.Query(g.Root, UpdaterType,
func(c interface{}) error {
if d, ok := c.(Disabler); ok && d.Disabled() {
// Do not update this component or descendants.
return Skip
}
return nil
},
func(c interface{}) error {
return c.(Updater).Update()
if u, ok := c.(Updater); ok {
return u.Update()
}
return nil
},
)
}
@ -167,21 +171,29 @@ func (g *Game) ReversePath(component interface{}) []interface{} {
return stack
}
// Query looks for components having both a given ancestor and implementing
// a given behaviour (see Behaviors in interface.go). This only returns sensible
// values for registered components.
// Query recursively searches for components having both a given ancestor and
// implementing a given behaviour (see Behaviors in interface.go).
// visitPre is called before descendants are visited, while visitPost is called
// after descendants are visited. nil visitPre/visitPost are ignored.
//
// Note that every component is its own ancestor.
// It is up to the visitPre and visitPost callbacks to handle components that
// do not themselves implement the behaviour - more specifically, every ancestor
// (up to the given one) of each component with the behaviour will be visited.
// Visiting components in the tree that *don't* implement the behaviour is
// important when behaviours of the parent need to influence the behaviours of
// the children (e.g. a component can be a Hider and hiding all descendants, but
// not necessarily be a Drawer itself).
//
// visitPre is visited before descendants, while visitPost is visited after
// descendants. nil visitors are ignored.
// Query only visits components that are registered.
//
// Note that every component is an ancestor of itself.
//
// Query returns the first error returned from either visitor callback, except
// Skip when it is returned from a recursive call. Returning Skip from visitPre
// will cause the descendents of the component to not be visited.
// will cause the descendants of the component to be skipped (see the
// implementation of Update for an example).
func (g *Game) Query(ancestor interface{}, behaviour reflect.Type, visitPre, visitPost VisitFunc) error {
pi := reflect.TypeOf(ancestor).Implements(behaviour)
if pi && visitPre != nil {
if visitPre != nil {
if err := visitPre(ancestor); err != nil {
return err
}
@ -205,7 +217,7 @@ func (g *Game) Query(ancestor interface{}, behaviour reflect.Type, visitPre, vis
}); err != nil {
return err
}
if pi && visitPost != nil {
if visitPost != nil {
return visitPost(ancestor)
}
return nil
@ -219,6 +231,8 @@ func (g *Game) Scan(visit VisitFunc) error {
// Load loads a component and all subcomponents recursively.
// Note that this method does not implement Loader itself.
func (g *Game) Load(component interface{}, assets fs.FS) error {
// Query cannot be used for this method because Load might cause
// subcomponents to spring into existence.
if l, ok := component.(Loader); ok {
if err := l.Load(assets); err != nil {
return err
@ -238,7 +252,10 @@ func (g *Game) Prepare(component interface{}) error {
// Postorder traversal, in case ancestors depend on descendants being
// ready to answer queries.
return g.Query(component, PrepperType, nil, func(c interface{}) error {
return c.(Prepper).Prepare(g)
if p, ok := c.(Prepper); ok {
return p.Prepare(g)
}
return nil
})
}

View file

@ -157,6 +157,9 @@ func (g *Game) cmdQuery(dst io.Writer, argv []string) {
noResults := true
g.Query(ancestor, behaviour, func(c interface{}) error {
if !reflect.TypeOf(c).Implements(behaviour) {
return nil
}
noResults = false
i, ok := c.(Identifier)
if ok {