大家好,欢迎大家关注我的知乎专栏慢慢悠悠小马车
本文是继 BT11:行为树内外的数据传输 - 知乎 之后更深入的从源码角度分析BehaviorTree.CPP的Blackboard的数据传输机制,是这一系列文章的精华。
树内即ports之间
类Blackboard(以下简称BB或bb)有3个重要的数据成员:BT中数据的保存依赖于storage_(Entry集合),树间的映射依赖于parent_和internal_to_external_。
// 保存了blackboard的所有entry的信息,包含entry所对应的port的实时值
std::unordered_map<std::string, Entry> storage_;
// 指向父blackboard(父树的blackboard)的指针
// 若不是nullptr,说明该tree被其他树引用了,是subtree
std::weak_ptr<Blackboard> parent_bb_;
// 保存了blackboard中向外(向父blackboard)重映射的port名称
std::unordered_map<std::string, std::string> internal_to_external_;
struct Entry {
Any value; // port的值
const PortInfo port_info; // port的其他信息
Entry(const PortInfo& info) : port_info(info) {}
Entry(Any&& other_any, const PortInfo& info)
: value(std::move(other_any)), port_info(info) {}
};
我们可以理解为:node的数据读写通过port,但数据是放在对应着该port的Entry中,树中所有nodes的数据整体放在blackboard的storage_中。这是通过xml中如下语句实现的,EntryName就是storage_中元素的第1项string,“{EntryName}”是1个blackboard pointer。
<NodeName PortName="{EntryName}" />
上面的语句不涉及树之间的关系,所以对internal_to_external_没影响。
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence>
<SaySomething message="666" />
<ThinkWhatToSay text="{the_answer}"/>
<SaySomething message="{the_answer}" />
</Sequence>
</BehaviorTree>
</root>
以BehaviorTree.CPP/examples/t02_basic_ports.cpp中的树为例,如上。当树构建第1个SaySomething节点时,在XMLParser::Pimpl::createNodeFromXML()中,会将pair{message,666}存入该node.config_.input_ports中。其中,config_是NodeConfiguration类型,input_ports是PortsRemapping类型,即unordered_map<string, string>类型。因为“666”是普通的字面字符串,不是blackboard pointer(不带花括号),就与blackboard无关,数据是静态的,node构建后就不会改变,所以这个数据是存在node自身的数据结构中,当获取名为message的port的值时,也不会去bb中查找。
struct NodeConfiguration {
Blackboard::Ptr blackboard;
PortsRemapping input_ports; // 输入port的映射关系
PortsRemapping output_ports; // 输出port的映射关系
};
当树构建ThinkWhatToSay节点时,会将pair{text,{the_answer}}存入该node.config_.input_ports中。发现"{the_answer}"是bb pointer,就会把pair{the_answer, Entry}存入所在树的bb的storage_。此时Entry还未赋值,因为树构建时节点并未运行tick(),也就没有对port数据的任何操作,仅仅定义了关系。这样,通过text port读写值,就变成了对bb的名为the_answer的