1
\$\begingroup\$

I'm still learning about Behaviour Trees and my understanding of the "blackboard" is essentially a state object. When passing the state object through the ticks to the function calls (which are nodes and leaves), how should I share the states amongst all the executions of the trees triggered from all previous ticks?

I initially thought the states should be immutable so that the functions can be "pure". However, if the states are immutable, that would mean that whatever consequences triggered from a previous tick will have to be performed in its entirety even if a later tick that came in the middle of the execution has a different state value.

For example, if the behaviour tree was something like this: enter image description here

When Tick_1 first came in, the state was something like this:

{ 
  user: { cash: 10 }
}

So, at Tick_1, user has $10 and has enough cash to buy milk. The behaviour tree will go down the path of walking to the store and then buying milk.

However, the action of "Walking to Store" is asynchronous and takes some time before the "Buy Milk" action can take place. Now, when the "Walking to Store" action from Tick_1 is still taking place, another tick Tick_2 came in at this time with a state that the user now has $0 in his pocket:

{ 
  user: { cash: 0 }
}

Based on the latest state from Tick_2 the user has no more money probably due to something that happened during Tick_2. But because the previous execution of the tree from Tick_1 is still in progress and has no knowledge about the new state since its copy of the state is the old one and they don't shared a common mutable state, it will eventually go on and perform the "Buy Milk" action when it really shouldn't because by this time, the user has no more cash!

It seems to me that if the states passed to the ticks are immutable separate copy of states, the previous decision made has to be completed and we can't "cancel" the previous decision or execution.

If we use a shared state, however, this would mean the state is mutable and any of the functions or actions can change them through any side effects. It might turn out to be very messy and difficult to maintain with too many places changing a common state.

My question is:

  • How should the blackboard or states of a behaviour tree be constructed? Should the states be mutable or immutable?
  • Should every ticks share a common copy of states so that executions of the tree from the previous ticks are aware of the latest values in the states so that they either stop proceeding or react differently?
    • If so, how should this be done elegantly?

I'm using Typescript in my case but I'm assuming(?) the concept should be similar in any other context and languages.

PS: I guess I could swap the positions of the condition and "walk to store" to reduce the problem in my example but I'm trying to use this to illustrate the dilemma I'm having.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

As far as I understand, the blackboard is not a state but more a dumping ground for storing things that the AI should know.

The blackboard is mutable: each behaviour can read from it and write to it, either for itself or for a future behaviour.

The tree per se has also it's own "state": in what node/leaf am I. It is mutable by the tree structure itself.

The behaviours are unaware of the tree state, and do not care.

The tree is not aware of the content of the blackboard, and does not care.

There can only be one tick active at a time, so in the example you provide, it would not be possible that the agent suddenly has no money to buy the milk when it gets to the store, because the behaviours are executed in sequence and the blackboard is not changed otherwise.

How should the blackboard or states of a behaviour tree be constructed? Should the states be mutable or immutable?

The blackboard is mutable by the leaves, while another structure is required for the rest of your tree.

Should every ticks share a common copy of states so that executions of the tree from the previous ticks are aware of the latest values in the states so that they either stop proceeding or react differently?

Yes.

If so, how should this be done elegantly?

The blackboard can be either a reference to an instance that you pass to your tree when you tick it, or you can pass the reference to each behaviour when you create the tree.


I'm not a behaviour tree guru, but that is essentially the way I used it lately, based on BehaviorTree.CPP.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks! I still don’t quite understand why it wouldn’t be possible that the agent would suddenly have no money because the “walk to store” action is an asynchronous process and it takes time to happen. During this time, in a subsequent tick, maybe something could have happened and the agent suddenly has no money, wouldn’t it? \$\endgroup\$
    – xenon
    May 20, 2022 at 19:31
  • \$\begingroup\$ Also, just to clarify, you mentioned that blackboards can be shared between behaviours. “Behaviour” in this case is referring to each tree, is that correct? Which means in my example, I only have 1 tree, hence only 1 behaviour. So, in some way, blackboards are like shared states between multiple trees. Is my understanding correct? Or is “behaviour” referring to the action nodes? \$\endgroup\$
    – xenon
    May 20, 2022 at 19:35
  • \$\begingroup\$ Well, technically, sure, an external event could have happened, and they were robbed from their money. In this case, likely, the buy milk behaviour would fail, the sequence would fail, and this tree would fail. \$\endgroup\$
    – Vaillancourt
    May 20, 2022 at 19:36
  • \$\begingroup\$ And maybe the terminology I used is not precise, by behaviour, I mean "leaf node"/action nodes. Where actions are taking place. It's the same blackboard for the whole tree. That being said, you can share the same blackboard among all the trees of the same agent. \$\endgroup\$
    – Vaillancourt
    May 20, 2022 at 19:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .