Reactive Sequences and Asynchronous Nodes
响应式序列和异步节点
下一个例子展示了SequenceNode
和ReactiveSequence
之间的区别。
一个异步动作有它自己的线程。这允许用户使用阻塞函数,但将执行流返回到树。
!!! 警告 “了解更多关于异步操作”
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;
}
方法MoveBaseAction::tick()
在不同于调用MoveBaseAction::executeTick()
的主线程中执行。
您需要负责实现一个有效的__halt()__功能。
用户还必须实现convertFromString<Pose2D>(StringView)
,就像在之前的教程中所示。
Sequence VS ReactiveSequence
以下示例应该使用简单的“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;
}
预期输出:
--- 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!"
您可能已经注意到,当调用executeTick()
时,MoveBase
在第一次和第二次返回__RUNNING__,最终第三次返回__SUCCESS__。
BatteryOK
只会被执行一次。
如果我们使用ReactiveSequence
,当子MoveBase
返回RUNNING时,序列将被重新启动,并且条件BatteryOK
将再次执行。
如果在任何时候,BatteryOK
返回 FAILURE,MoveBase
动作将会被 中断(具体来说是_停止_)。
<root>
<BehaviorTree>
<ReactiveSequence>
<BatteryOK/>
<Sequence>
<SaySomething message="mission started..." />
<MoveBase goal="1;2;3"/>
<SaySomething message="mission completed!" />
</Sequence>
</ReactiveSequence>
</BehaviorTree>
</root>
预期输出:
--- 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!"
事件驱动树?
!!! 重要
我们使用命令tree.sleep()
而不是std::this_thread::sleep_for()
是有原因的。
方法Tree::sleep()
应该是首选,因为当"某些东西发生变化"时,它可以被树中的一个节点中断。
当AsyncActionNode::tick()
完成或更一般地,调用方法TreeNode::emitStateChanged()
时,Tree::sleep()
会被中断。