The next example shows the difference between a SequenceNode and a
ReactiveSequence.
An Asynchronous Action has it's own thread. This allows the user to use blocking functions but to return the flow of execution to the tree.
!!! warning "Learn more about Asynchronous Actions"
Users should fully understand how Concurrency is achieved in BT.CPP
and learn best practices about how to develop their own Asynchronous
Actions. You will find an extensive article
[here](asynchronous_nodes.md).
// Custom type
struct Pose2D
{
double x, y, theta;
};
class MoveBaseAction : public AsyncActionNode
{
public:
MoveBaseAction(const std::string& name, const NodeConfiguration& config)
: AsyncActionNode(name, config)
{ }
static PortsList providedPorts()
{
return{ InputPort<Pose2D>("goal") };
}
NodeStatus tick() override;
// This overloaded method is used to stop the execution of this node.
void halt() override
{
_halt_requested.store(true);
}
private:
std::atomic_bool _halt_requested;
};
//-------------------------
NodeStatus MoveBaseAction::tick()
{
Pose2D goal;
if ( !getInput<Pose2D>("goal", goal))
{
throw RuntimeError("missing required input [goal]");
}
printf("[ MoveBase: STARTED ]. goal: x=%.f y=%.1f theta=%.2f\n",
goal.x, goal.y, goal.theta);
_halt_requested.store(false);
int count = 0;
// Pretend that "computing" takes 250 milliseconds.
// It is up to you to check periodically _halt_requested and interrupt
// this tick() if it is true.
while (!_halt_requested && count++ < 25)
{
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
}
std::cout << "[ MoveBase: FINISHED ]" << std::endl;
return _halt_requested ? NodeStatus::FAILURE : NodeStatus::SUCCESS;
}The method MoveBaseAction::tick() is executed in a thread different from the
main thread that invoked MoveBaseAction::executeTick().
You are responsible for the implementation of a valid halt() functionality.
The user must also implement convertFromString<Pose2D>(StringView),
as shown in the previous tutorial.
The following example should use a simple SequenceNode.
<root>
<BehaviorTree>
<Sequence>
<BatteryOK/>
<SaySomething message="mission started..." />
<MoveBase goal="1;2;3"/>
<SaySomething message="mission completed!" />
</Sequence>
</BehaviorTree>
</root>int main()
{
using namespace DummyNodes;
using std::chrono::milliseconds;
BehaviorTreeFactory factory;
factory.registerSimpleCondition("BatteryOK", std::bind(CheckBattery));
factory.registerNodeType<MoveBaseAction>("MoveBase");
factory.registerNodeType<SaySomething>("SaySomething");
auto tree = factory.createTreeFromText(xml_text);
NodeStatus status;
std::cout << "\n--- 1st executeTick() ---" << std::endl;
status = tree.tickRoot();
tree.sleep( milliseconds(150) );
std::cout << "\n--- 2nd executeTick() ---" << std::endl;
status = tree.tickRoot();
tree.sleep( milliseconds(150) );
std::cout << "\n--- 3rd executeTick() ---" << std::endl;
status = tree.tickRoot();
std::cout << std::endl;
return 0;
}Expected output:
--- 1st executeTick() ---
[ Battery: OK ]
Robot says: "mission started..."
[ MoveBase: STARTED ]. goal: x=1 y=2.0 theta=3.00
--- 2nd executeTick() ---
[ MoveBase: FINISHED ]
--- 3rd executeTick() ---
Robot says: "mission completed!"
You may have noticed that when executeTick() was called, MoveBase returned
RUNNING the 1st and 2nd time, and eventually SUCCESS the 3rd time.
BatteryOK is executed only once.
If we use a ReactiveSequence instead, when the child MoveBase returns RUNNING,
the sequence is restarted and the condition BatteryOK is executed again.
If, at any point, BatteryOK returned FAILURE, the MoveBase action
would be interrupted (halted, to be specific).
<root>
<BehaviorTree>
<ReactiveSequence>
<BatteryOK/>
<Sequence>
<SaySomething message="mission started..." />
<MoveBase goal="1;2;3"/>
<SaySomething message="mission completed!" />
</Sequence>
</ReactiveSequence>
</BehaviorTree>
</root>Expected output:
--- 1st executeTick() ---
[ Battery: OK ]
Robot says: "mission started..."
[ MoveBase: STARTED ]. goal: x=1 y=2.0 theta=3.00
--- 2nd executeTick() ---
[ Battery: OK ]
[ MoveBase: FINISHED ]
--- 3rd executeTick() ---
[ Battery: OK ]
Robot says: "mission completed!"
!!! important
We used the command tree.sleep() instead of std::this_thread::sleep_for() for a reason.
The method Tree::sleep() should be preferred, because it can be interrupted by a Node in the tree when
"something changed".
Tree::sleep() will be interrupted when an AsyncActionNode::tick() is completed or, more generally,
when the method TreeNode::emitStateChanged() is invoked.