4

I'm trying to implement a behavior tree in go, and I'm struggling with its composition features. Basically, I need Tick() implemented below to call the method defined by wherever it was embedded.

Here is behavior.go:

type IBehavior interface {
  Tick() Status
  Update() Status
}

type Behavior struct {
  Status Status
}

func (n *Behavior) Tick() Status {
  fmt.Println("ticking!")
  if n.Status != RUNNING { n.Initialize() }
  status := n.Update()
  if n.Status != RUNNING { n.Terminate(status) }

  return status
}

func (n *Behavior) Update() Status {
  fmt.Println("This update is being called")
  return n.Status
}

And here is the Behavior struct being embedded:

type IBehaviorTree interface {
  IBehavior
}

type BehaviorTree struct {
  Behavior

  Root IBehavior
}

func (n *BehaviorTree) Update() Status {
  fmt.Printf("Tree tick! %#v\n", n.Root)
  return n.Root.Tick()
}

A few more files to make this example make sense:

type ILeaf interface {
  IBehavior
}

type Leaf struct {
  Behavior
}

And this one:

type Test struct {
  Leaf

  Status Status
}

func NewTest() *Test {
    return &Test{}
}

func (n Test) Update() Status {
    fmt.Println("Testing!")
    return SUCCESS
}

And here is an example of its usage:

tree := ai.NewBehaviorTree()
test := ai.NewTest()
tree.Root = test

tree.Tick()

I was expecting the tree to tick normally by printing this:

ticking!
Tree tick!

But instead I'm getting:

ticking!
This update is being called

Could anyone help me with this issue?

Edit: Added a few extra files to illuminate the issue. Also, I don't understand the downvotes. I have an honest go question. Am I only supposed to ask questions that make sense to me already?

SashaZd
  • 3,315
  • 1
  • 26
  • 48
Felipe Rocha
  • 127
  • 8
  • In your example of usage, `test` is assigned to `tree.Root` but `test` is not defined. What is it and where is it coming from? – Adrian Dec 11 '17 at 17:46
  • 1
    Go has absolutely no notion of inheritance (embedding is not inheritance) and you simply cannot do parent/child stuff in Go. Redesign. – Volker Dec 11 '17 at 17:50
  • @Adrian Added that information. – Felipe Rocha Dec 11 '17 at 18:03
  • @Volker I get that, but I was here because I got stuck in trying to find that design since I'm not used to this way of designing code. It would be illuminating if you could help a bit more instead of just saying "Redesign," which doesn't help at all. – Felipe Rocha Dec 11 '17 at 18:05
  • 1
    There is no inheritance, but composition does allow for parent/child relationships; not sure what @Volker is getting at here. Regardless, in your usage example, it still does not show how the variable `test` assigned to `tree.Root` is created. Is it supposed to be `tree.Root = n1` maybe? – Adrian Dec 11 '17 at 18:14
  • 1
    @FelipeRocha it's a fine question, don't mind the downvotes. For some reason the golang community on this site aggressively downvotes questions, often regardless of their validity. Maybe try reducing your question down to a standalone example in a single function (e.g. that doesn't require multiple files). – maerics Dec 11 '17 at 18:17
  • @Adrian Oh! Sorry! Typo! `n1` is supposed to read `test`. I'll fix it! – Felipe Rocha Dec 11 '17 at 18:29
  • 2
    See also https://stackoverflow.com/questions/29390736/go-embedded-struct-call-child-method-instead-parent-method, and https://stackoverflow.com/questions/45974464/how-to-call-parent-method-and-pass-any-child-that-extending-the-parent-struct-as – JimB Dec 11 '17 at 18:29
  • @maerics Yeah, it's not the first time I run into the downvotes, which is why I asked. Sorry, but I already tried to distill it to its basics! Anything less than this, and the question won't make much sense... – Felipe Rocha Dec 11 '17 at 18:31
  • 1
    Unfortunately it already doesn't make much sense; the structure is so convoluted and complicated it's hard to quickly trace execution. This is the benefit of distilling it down to [MCVE](https://stackoverflow.com/help/mcve) - oftentimes the problem reveals itself when you remove some of the unnecessary complexity. – Adrian Dec 11 '17 at 18:34
  • @FelipeRocha I think you can still simplify your question, there are several lines of code that aren't relevant to the issue (type Status, code listings, unused interface methods, etc). The problem domain doesn't matter here, only the specifics of interfaces, composition, and dynamic dispatch. – maerics Dec 11 '17 at 19:18
  • Sorry. I was at work, and waiting for lunch to come back to this. I removed all the extraneous information to help clarify the ask – Felipe Rocha Dec 11 '17 at 20:44

2 Answers2

2

Your issue here is that Tick() is not defined on your BehaviorTree structure. As a result, when you call tree.Tick(), there's no direct method defined, so it calls the promoted Tick() method of the embedded Behavior struct. That Behavior struct has no idea what a BehaviorTree is! In Go’s embedding style of pseudo-inheritance, “child” types have no concept of their "parents", nor any reference or access to them. Embedded methods are called with the embedded type as their receiver, not the embedding struct.

If you want your expected behavior, you need to define a Tick() method on your BehaviorTree type, and have that method to call its own Update() method (and then call sub Tick() or Update() methods if you wish). For example:

type BehaviorTree struct {
  Behavior

  Root IBehavior
}

func (n *BehaviorTree) Tick() Status {
    n.Update() // TODO: do you want this status or the Root.Tick() status?
    return n.Root.Tick()
}

func (n *BehaviorTree) Update() Status {
  fmt.Printf("Tree tick! %#v\n", n.Root)
  return nil
}
Kaedys
  • 9,600
  • 1
  • 33
  • 40
  • Interesting. I was trying to avoid redefining that behavior... Is there really no way around it? – Felipe Rocha Dec 11 '17 at 22:00
  • You could embed an interface that defines the method and call that. But no, there's no way to call a method of a "child" object, and then have that "child" object call a method of the "parent" object, unless you have circular references (ie. the child has a handle for the parent as well). From an embedding perspective, the general solution is to have a structure that defines the external methods, and it embeds or more commonly simply has a field for an interface that holds the method you want to dynamically define. – Kaedys Dec 11 '17 at 22:38
0

As Volker said

Go has absolutely no notion of inheritance (embedding is not inheritance) and you simply cannot do parent/child stuff in Go. Redesign

What you want as far as I can tell is a function which uses an interface to perform the same task multiple times.

func Tick(n IBehavior) Status {
  fmt.Println("ticking!")
  if n.Status != RUNNING { n.Initialize() }
  status := n.Update()
  if n.Status != RUNNING { n.Terminate(status) }
  return status
}

Of course Initialize will then have to be in the interface.

Benjamin Kadish
  • 1,472
  • 11
  • 20