大家好,欢迎大家关注我的知乎专栏慢慢悠悠小马车
下面的文章有关于blackboard、entry、port、subtree remapping的简介,这是理解行为树的数据解析和传递的重点和难点。行为树不是封闭的,对内有不同subtree、不同port间的数据传递,对外有调用方的状态或结果获取,毕竟很多情况调用方不会满足于只知道执行结果的成功与失败,还想知道为何失败等详细信息。BT4:库中基本类型——Factory和Blackboard_silver bullet-CSDN博客基本的类定义https://blog.csdn.net/linxigjs/article/details/123033365?spm=1001.2014.3001.5501
树内即ports之间
参考BehaviorTree.CPP/examples/t02_basic_ports.cpp 的示例。
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root">
<SaySomething message="start thinking..." />
<ThinkWhatToSay text="{the_answer}"/>
<SaySomething message="{the_answer}" />
<SaySomething2 message="SaySomething2 works too..." />
<SaySomething2 message="{the_answer}" />
</Sequence>
</BehaviorTree>
</root>
类ThinkWhatToSay有1个string类型的OutputPort,并会在tick()时向该port写入值。
class ThinkWhatToSay : public BT::SyncActionNode {
public:
BT::NodeStatus tick() override {
setOutput("text", "The answer is 42");
return BT::NodeStatus::SUCCESS;
}
static BT::PortsList providedPorts() {
return {BT::OutputPort<std::string>("text")};
}
};
类SaySomething有1个string类型的InputPort,并会在tick()时从该port读取值。
class SaySomething : public BT::SyncActionNode {
public:
BT::NodeStatus tick() override {
auto msg = getInput<std::string>("message");
...
std::cout << "Robot says: " << msg.value() << std::endl;
return BT::NodeStatus::SUCCESS;
}
static BT::PortsList providedPorts() {
return {BT::InputPort<std::string>("message")};
}
};
ThinkWhatToSay和SaySomething,通过所在树的blackboard的1个名称为"the_answer"的entry进行数据读写传输。entry可以传输的数据的类型,由port限定。我们把blackboard的数量和包含的entry的名称打印出来,size=1因为没有子树,且只有1个名为"the_answer"的entry。
std::cout << tree.blackboard_stack.size() << std::endl;
for(const auto str : tree.blackboard_stack[0]->getKeys()) {
std::cout << str << std::endl;
}
1
the_answer
subtree之间
参考BehaviorTree.CPP/examples/t06_basic_ports.cpp 的示例。
<root main_tree_to_execute = "MainTree">
<BehaviorTree ID="MainTree">
<Sequence name="main_sequence">
<SetBlackboard output_key="move_goal" value="1;2;3" />
<SubTree ID="MoveRobot" target="move_goal" output="move_result" />
<SaySomething message="{move_result}"/>
</Sequence>
</BehaviorTree>
<BehaviorTree ID="MoveRobot">
<Fallback name="move_robot_main">
<SequenceStar>
<MoveBase goal="{target}"/>
<SetBlackboard output_key="output" value="mission accomplished" />
</SequenceStar>
<ForceFailure>
<SetBlackboard output_key="output" value="mission failed" />
</ForceFailure>
</Fallback>
</BehaviorTree>
</root>
MainTree中包含1个MoveRobot subtree,所以有2个blackboard,blackboard[0]是MainTree的(一定是最外层树的),具有move_result和move_goal 2个entry,而MoveRobot具有output和target 2个entry。在树运行之后,使用debugMessage()可以打印树之间的ports映射如下。full表明对应port已经被设置值,可以被外部读取。
move_result (std::string) -> full
move_goal (Pose2D) -> full
--------------
output (std::string) -> remapped to parent [move_result]
target (Pose2D) -> remapped to parent [move_goal]
如果我们调整下debugMessage()在树创建之后、运行之前,映射会变成什么样呢?
int main() {
...
auto tree = factory.createTreeFromText(xml_text);
// 在树创建之后、运行之前打印映射信息
std::cout << "--------------" << std::endl;
tree.blackboard_stack[0]->debugMessage();
std::cout << "--------------" << std::endl;
tree.blackboard_stack[1]->debugMessage();
std::cout << "--------------" << std::endl;
// auto p = tree.blackboard_stack[0]->get<Pose2D>("move_goal");
// std::cout << "get pose (" << p.x << "," << p.y << "," << p.theta << ")" << std::endl;
NodeStatus status = NodeStatus::RUNNING;
while (status == NodeStatus::RUNNING) {
status = tree.tickRoot();
SleepMS(1); // optional sleep to avoid "busy loops"
}
return 0;
}
结果如下,可见MainTree的2个entry都没有设置值(empty),且MoveRobot subtree也少了1个entry信息,因为此时树未运行SetBlackboard节点,自然也就不会创建出output entry。若读取empty的entry,就会发生异常。
move_result (std::string) -> empty
move_goal (Pose2D) -> empty
--------------
target (Pose2D) -> remapped to parent [move_goal]
树与调用方之间
本小节需要和上一小节结合起来看。
当debugMessage()打印key->full时,才可以在外部读取树的blackboard的entry的值。
while (status == NodeStatus::RUNNING) {
status = tree.tickRoot();
SleepMS(1); // optional sleep to avoid "busy loops"
}
// 添加在树运行后和return之间,此时entry的值是保持的。
// 如果添加在树运行之前,可能会因为port还未被设置而读取触发异常。
auto p = tree.blackboard_stack[0]->get<Pose2D>("move_goal");
std::cout << "get pose (" << p.x << "," << p.y << "," << p.theta << ")" << std::endl;
return 0;
运行上面的代码,我们会得到:
get pose (1,2,3) // 是XML中SetBlackboard的结果
当debugMessage()打印key->empty时, 读取值抛出异常,参考上一小节被注释的代码。
terminate called after throwing an instance of 'std::runtime_error'
what(): Any::cast failed because it is empty
当然,从外部设置entry的值并不受其是empty/full的影响。
Pose2D p, q;
p.x = 10, p.y = 11, p.theta = 3.14;
tree.blackboard_stack[0]->set<Pose2D>("move_goal", p);
tree.blackboard_stack[0]->get<Pose2D>("move_goal", q);
std::cout << "get pose (" << q.x << "," << q.y << "," << q.theta << ")" << std::endl;
运行上面的代码,我们会得到:
get pose (10,11,3.14)