String methods, no separate walkers

This commit is contained in:
Josh Deprez 2021-09-23 14:17:18 +10:00
parent aab99dab8f
commit 4dba7b22c7
12 changed files with 81 additions and 70 deletions

View file

@ -127,3 +127,5 @@ func (a *Actor) Prepare(g *Game) error {
a.game = g
return nil
}
func (a *Actor) String() string { return "Actor@" + a.Pos.String() }

View file

@ -90,6 +90,8 @@ func (c *Camera) Scan(visit func(interface{}) error) error {
return visit(c.Child)
}
func (c *Camera) String() string { return "Camera@" + c.Centre.String() }
// Transform returns the camera transform.
func (c *Camera) Transform() (opts ebiten.DrawImageOptions) {
opts.GeoM.Translate(geom.CFloat(c.Centre.Mul(-1)))

View file

@ -18,6 +18,8 @@ func (c Container) Scan(visit func(interface{}) error) error {
return nil
}
func (c Container) String() string { return "Container" }
// Register records component in the slice, if parent is the container.
func (c *Container) Register(component, parent interface{}) error {
if parent == c {

View file

@ -120,6 +120,8 @@ func (d *DrawDAG) Scan(visit func(interface{}) error) error {
return visit(d.Child)
}
func (d *DrawDAG) String() string { return "DrawDAG" }
// Update checks for any changes to descendants, and updates its internal
// data structures accordingly.
func (d *DrawDAG) Update() error {
@ -325,7 +327,8 @@ func newDAG() *dag {
}
}
func (d *dag) String() string {
// Dot returns a dot-syntax-like description of the graph.
func (d *dag) Dot() string {
var sb strings.Builder
sb.WriteString("digraph {\n")
for v, e := range d.out {

View file

@ -58,3 +58,5 @@ func (d *DrawDFS) draw(component interface{}, screen *ebiten.Image, opts ebiten.
func (d *DrawDFS) Scan(visit func(interface{}) error) error {
return visit(d.Child)
}
func (d *DrawDFS) String() string { return "DrawDFS" }

View file

@ -169,46 +169,6 @@ func (g *Game) Scan(visit func(interface{}) error) error {
return visit(g.Root)
}
// PreorderWalk calls visit with every component and its parent, reachable from
// the given component via Scan, for as long as visit returns nil. The parent
// value passed to visit when visiting component will be nil. The parent will be
// visited before the children.
func PreorderWalk(component interface{}, visit func(component, parent interface{}) error) error {
return preorderWalk(component, nil, visit)
}
func preorderWalk(component, parent interface{}, visit func(component, parent interface{}) error) error {
if err := visit(component, parent); err != nil {
return err
}
if sc, ok := component.(Scanner); ok {
return sc.Scan(func(c interface{}) error {
return preorderWalk(c, component, visit)
})
}
return nil
}
// PostorderWalk calls visit with every component and its parent, reachable from
// the given component via Scan, for as long as visit returns nil. The parent
// value passed to visit when visiting component will be nil. The children will
// be visited before the parent.
func PostorderWalk(component interface{}, visit func(component, parent interface{}) error) error {
return postorderWalk(component, nil, visit)
}
func postorderWalk(component, parent interface{}, visit func(component, parent interface{}) error) error {
if sc, ok := component.(Scanner); ok {
scv := func(c interface{}) error {
return postorderWalk(c, component, visit)
}
if err := sc.Scan(scv); err != nil {
return err
}
}
return visit(component, parent)
}
// Load loads a component and all subcomponents recursively.
// Note that this method does not implement Loader.
func (g *Game) Load(component interface{}, assets fs.FS) error {
@ -258,14 +218,9 @@ func (g *Game) LoadAndPrepare(assets fs.FS) error {
// Build the component databases
startBuild := time.Now()
g.dbmu.Lock()
g.byID = make(map[string]Identifier)
g.byAB = make(map[abKey]map[interface{}]struct{})
g.par = make(map[interface{}]interface{})
if err := PreorderWalk(g, g.registerOne); err != nil {
if err := g.build(); err != nil {
return err
}
g.dbmu.Unlock()
log.Printf("finished building db in %v", time.Since(startBuild))
// Prepare all the Preppers
@ -277,6 +232,15 @@ func (g *Game) LoadAndPrepare(assets fs.FS) error {
return nil
}
func (g *Game) build() error {
g.dbmu.Lock()
defer g.dbmu.Unlock()
g.byID = make(map[string]Identifier)
g.byAB = make(map[abKey]map[interface{}]struct{})
g.par = make(map[interface{}]interface{})
return g.registerRecursive(g, nil)
}
// Register registers a component into the component database (as the
// child of a given parent). Passing a nil component or parent is an error.
// Registering multiple components with the same ID is also an error.
@ -291,8 +255,19 @@ func (g *Game) Register(component, parent interface{}) error {
}
g.dbmu.Lock()
defer g.dbmu.Unlock()
// preorderWalk goes in the right order for registering.
return preorderWalk(component, parent, g.registerOne)
return g.registerRecursive(component, parent)
}
func (g *Game) registerRecursive(component, parent interface{}) error {
if err := g.registerOne(component, parent); err != nil {
return err
}
if sc, ok := component.(Scanner); ok {
return sc.Scan(func(x interface{}) error {
return g.registerRecursive(x, component)
})
}
return nil
}
func (g *Game) registerOne(component, parent interface{}) error {
@ -335,12 +310,21 @@ func (g *Game) Unregister(component interface{}) {
return
}
g.dbmu.Lock()
// postorderWalk goes in the right order for unregistering.
postorderWalk(component, nil, g.unregisterOne)
g.unregisterRecursive(component)
g.dbmu.Unlock()
}
func (g *Game) unregisterOne(component, _ interface{}) error {
func (g *Game) unregisterRecursive(component interface{}) {
if sc, ok := component.(Scanner); ok {
sc.Scan(func(x interface{}) error {
g.unregisterRecursive(x)
return nil
})
}
g.unregisterOne(component)
}
func (g *Game) unregisterOne(component interface{}) {
// unregister from g.byAB, using g.par to trace the path
ct := reflect.TypeOf(component)
for _, b := range Behaviours {
@ -361,9 +345,10 @@ func (g *Game) unregisterOne(component, _ interface{}) error {
if id, ok := component.(Identifier); ok && id.Ident() != "" {
delete(g.byID, id.Ident())
}
return nil
}
func (g *Game) String() string { return "Game" }
// --------- Helper stuff ---------
type abKey struct {

View file

@ -57,3 +57,5 @@ func (r *ImageRef) Load(assets fs.FS) error {
imageCache[assetKey{assets, r.Path}] = r.image
return nil
}
func (r *ImageRef) String() string { return "ImageRef{" + r.Path + "}" }

View file

@ -43,6 +43,8 @@ func (p *Parallax) Scan(visit func(interface{}) error) error {
return visit(p.Child)
}
func (p *Parallax) String() string { return "Parallax" }
// Transform returns a GeoM translation of Factor * camera.Centre.
func (p *Parallax) Transform() (opts ebiten.DrawImageOptions) {
x, y := geom.CFloat(p.camera.Centre)

View file

@ -125,6 +125,8 @@ func (m *PrismMap) Scan(visit func(interface{}) error) error {
return nil
}
func (m *PrismMap) String() string { return "PrismMap" }
// Transform retrurns a translation by the draw offset.
func (m *PrismMap) Transform() (opts ebiten.DrawImageOptions) {
opts.GeoM.Translate(geom.CFloat(m.DrawOffset))

View file

@ -88,24 +88,27 @@ func (g *Game) cmdTree(dst io.Writer, argv []string) {
return
}
}
PreorderWalk(c, func(w, _ interface{}) error {
g.printTreeRecursive(dst, 0, c)
}
func (g *Game) printTreeRecursive(dst io.Writer, depth int, c interface{}) {
indent := ""
l := 0
for p := w; p != c; p = g.par[p] {
l++
if depth > 0 {
indent = strings.Repeat(" ", depth-1) + "↳ "
}
if l > 0 {
indent = strings.Repeat(" ", l-1) + "↳ "
}
i, ok := w.(Identifier)
i, ok := c.(Identifier)
if ok {
fmt.Fprintf(dst, "%s%T %q\n", indent, w, i.Ident())
fmt.Fprintf(dst, "%s%v %q\n", indent, c, i.Ident())
} else {
fmt.Fprintf(dst, "%s%T\n", indent, w)
fmt.Fprintf(dst, "%s%v\n", indent, c)
}
if sc, ok := c.(Scanner); ok {
sc.Scan(func(x interface{}) error {
g.printTreeRecursive(dst, depth+1, x)
return nil
})
}
}
func (g *Game) cmdQuery(dst io.Writer, argv []string) {
if len(argv) < 2 || len(argv) > 3 {

View file

@ -43,6 +43,8 @@ func (s *Scene) Scan(visit func(interface{}) error) error {
return visit(s.Child)
}
func (s *Scene) String() string { return "Scene" }
// SceneRef loads a gzipped, gob-encoded Scene from the asset FS.
// After Load, Scene is usable.
// This is mostly useful for scenes that refer to other scenes, e.g.
@ -83,3 +85,5 @@ func (r *SceneRef) Load(assets fs.FS) error {
func (r *SceneRef) Save() error {
return SaveGobz(r.Scene, filepath.Base(r.Path))
}
func (r *SceneRef) String() string { return "SceneRef{" + r.Path + "}" }

View file

@ -57,3 +57,5 @@ func (s *Sheet) SubImage(i int) *ebiten.Image {
r := image.Rectangle{p, p.Add(s.CellSize)}
return s.Src.Image().SubImage(r).(*ebiten.Image)
}
func (s *Sheet) String() string { return "Sheet" }