Skip to main content
Version: 4.6

Pre and Post conditions

Leveraging the power of the scripting language introduced in the previous tutorial, BT.CPP 4.x introduces the concept of Pre and Post Conditions, i.e scripts that can run either before or after the actual tick() of a Node.

Pre and Post conditions are supported by all the nodes and don't need any modifications in your C++ code.

caution

The goal of scripting is not to write complex code, but only to improve the readability of the tree and reduce the need for custom C++ Nodes in very simple use cases.

If your scripts become too long, you may want to reconsider your decision to use them.

Pre conditions

NameDescription
_skipIfSkip the execution of this Node, if the condition is true
_failureIfSkip and return FAILURE, if the condition is true
_successIfSkip and return SUCCESS, if the condition is true
_whileSame as _skipIf, but may also interrupt a RUNNING Node if the condition becomes false.

Example

In previous tutorials, we saw how to build an if-then-else logic in the tree using a fallback.

The new syntax is much more compact:

Previous approach:

<Fallback>
<Inverter>
<IsDoorClosed/>
</Inverter>
<OpenDoor/>
</Fallback>

If, instead of using a custom ConditionNode IsDoorOpen, we can store a boolean in an entry called door_closed, the XML can be rewritten as:

<OpenDoor _skipIf="!door_closed"/>

Post conditions

NameDescription
_onSuccessExecute this script, if the Node returned SUCCESS
_onFailureExecute this script, if the Node returned FAILURE
_postExecute this script, if the Node returned either SUCCESS or FAILURE
_onHaltedScript executed if a RUNNING Node was halted

Example

In a tutorial about subtrees, we saw how a specific blackboard variable was written based on the result of MoveBase.

On the left side, you can see how this logic would be implemented in BT.CPP 3.x and how much simpler it is to use post conditions instead. Additionally, the new syntax supports enums.

Previous version:

<Fallback>
<Sequence>
<MoveBase goal="{target}"/>
<SetBlackboard output_key="result" value="0" />
</Sequence>
<ForceFailure>
<SetBlackboard output_key="result" value="-1" />
</ForceFailure>
</Fallback>

New implementation:

<MoveBase goal="{target}" 
_onSuccess="result:=OK"
_onFailure="result:=ERROR"/>

Design pattern: error codes

One of the areas where Behavior Trees may struggle, when compared to State Machines, is in those patterns where a different strategy should be executed based on the result of an Action.

Since BTs are limited to SUCCESS and FAILURE, that could be unintuitive.

A solution is storing the result / error code in the blackboard, but that was cumbersome in version 3.X.

Pre conditions can help us implement more readable code, like this one:

error_codes.svg

In the tree above, we added an Output port return to MoveBase and we conditionally take the second or third branch of the Sequence based on the value of error_code.

Design pattern: states and declarative trees

Even if the promise of Behavior Tree is to free us from the tyranny of states, but the truth is that sometimes it is hard to reason about our application without states.

Using states can make our Tree easier. For instance, we can take a certain branch of the tree only when the robot (or a subsystem) is in a particular state.

Consider this Node and its pre/post conditions:

landing.svg

This node will be executed only if the state is equal to DO_LANDING and, once the value of altitude is small enough, the state is changed to LANDED.

Note as DO_LANDING and LANDED are enums, not strings

tip

A surprising side effect of this pattern is that we made our Node more declarative i.e. it is easier to move this specific Node/Subtree into a different portion of the tree.