Async Actions using Coroutines
使用协程的异步操作
BehaviorTree.CPP提供了两种易于使用的抽象来创建异步动作,比如:
- 需要很长时间才能得出结论
- 可能返回"RUNNING"
- 可以中止
第一个类是一个AsyncActionNode
,它在一个__单独的线程__中执行tick()
方法。
在本教程中,我们介绍了CoroActionNode
,一种使用协程来实现类似结果的不同操作。
主要原因是协程不会生成新线程,因此效率更高。此外,在您的代码中无需担心线程安全性…
在协程中,用户应当明确调用一个 yield
方法,当他/她希望暂停动作的执行时。
CoroActionNode
将这个 yield
函数封装到一个方便的方法 setStatusRunningAndYield()
中。
The C++ source example
下面的例子可以作为你自己实现的“模板”。
typedef std::chrono::milliseconds Milliseconds;
class MyAsyncAction: public CoroActionNode
{
public:
MyAsyncAction(const std::string& name):
CoroActionNode(name, {})
{}
private:
// This is the ideal skeleton/template of an async action:
// - A request to a remote service provider.
// - A loop where we check if the reply has been received.
// - You may call setStatusRunningAndYield() to "pause".
// - Code to execute after the reply.
// - A simple way to handle halt().
NodeStatus tick() override
{
std::cout << name() <<": Started. Send Request to server." << std::endl;
TimePoint initial_time = Now();
TimePoint time_before_reply = initial_time + Milliseconds(100);
int count = 0;
bool reply_received = false;
while( !reply_received )
{
if( count++ == 0)
{
// call this only once
std::cout << name() <<": Waiting Reply..." << std::endl;
}
// pretend that we received a reply
if( Now() >= time_before_reply )
{
reply_received = true;
}
if( !reply_received )
{
// set status to RUNNING and "pause/sleep"
// If halt() is called, we will NOT resume execution
setStatusRunningAndYield();
}
}
// This part of the code is never reached if halt() is invoked,
// only if reply_received == true;
std::cout << name() <<": Done. 'Waiting Reply' loop repeated "
<< count << " times" << std::endl;
cleanup(false);
return NodeStatus::SUCCESS;
}
// you might want to cleanup differently if it was halted or successful
void cleanup(bool halted)
{
if( halted )
{
std::cout << name() <<": cleaning up after an halt()\n" << std::endl;
}
else{
std::cout << name() <<": cleaning up after SUCCESS\n" << std::endl;
}
}
void halt() override
{
std::cout << name() <<": Halted." << std::endl;
cleanup(true);
// Do not forget to call this at the end.
CoroActionNode::halt();
}
TimePoint Now()
{
return std::chrono::high_resolution_clock::now();
};
};
正如您可能已经注意到的那样,pretends
动作会等待一个请求消息;后者将在100毫秒后到达。
为了增加乐趣,我们创建了一个包含两个动作的序列,但整个序列将在150毫秒后由超时停止。
<root >
<BehaviorTree>
<Timeout msec="150">
<SequenceStar name="sequence">
<MyAsyncAction name="action_A"/>
<MyAsyncAction name="action_B"/>
</SequenceStar>
</Timeout>
</BehaviorTree>
</root>
No surprises in the main()
…
int main()
{
// Simple tree: a sequence of two asycnhronous actions,
// but the second will be halted because of the timeout.
BehaviorTreeFactory factory;
factory.registerNodeType<MyAsyncAction>("MyAsyncAction");
auto tree = factory.createTreeFromText(xml_text);
//---------------------------------------
// keep executing tick until it returns either SUCCESS or FAILURE
while( tree.tickRoot() == NodeStatus::RUNNING)
{
tree.sleep( std::chrono::milliseconds(10) );
}
return 0;
}
/* Expected output:
action_A: Started. Send Request to server.
action_A: Waiting Reply...
action_A: Done. 'Waiting Reply' loop repeated 11 times
action_A: cleaning up after SUCCESS
action_B: Started. Send Request to server.
action_B: Waiting Reply...
action_B: Halted.
action_B: cleaning up after an halt()
*/