大家好,欢迎大家关注我的知乎专栏慢慢悠悠小马车
本文主要分析BehaviorTree.CPP/src/xml_parsing.cpp的内容,因为函数代码都很长,就省略了代码,大家可以与源文件对照理解。搞清楚行为树的解析、加载、构建过程,有利于对其设计思路有更深刻的理解,但是对行为树的使用影响不大,可以跳过。
我认为行为树的精华在于blackboard的设计,实现了节点间、树间的数据共享,但代码层次较深,理解花费时间较多,待以后补充,大家可以期待一下。
树的加载和创建由createTreeFromText() 实现,该函数的第2个参数具有默认参数,即初创建的blackboard,是一个局部变量,但是由智能指针指向它。因此,只要引用计数大于0,该变量仍然不会释放,可以访问得到。
Tree createTreeFromText(const std::string& text,
Blackboard::Ptr blackboard = Blackboard::create());
Tree BehaviorTreeFactory::createTreeFromText(const std::string& text,
Blackboard::Ptr blackboard) {
XMLParser parser(*this);
// 加载和解析文本,检查各项元素是否符合BT的概念要求。
parser.loadFromText(text);
// 创建树和所有节点的实例,构造好树之间、节点之间的父子关系,port的映射关系等。
auto tree = parser.instantiateTree(blackboard);
// 将树的节点信息绑定给树实例变量
tree.manifests = this->manifests();
return tree;
}
createTreeFromText() 主要有3部分。其中的manifests包含了树的所有节点类型信息,其实节点的builder和manifest在树建立之前已经通过register函数传给factory变量了。
template
void registerNodeType(const std::string& ID, PortsList ports) {
...
registerBuilder(CreateManifest(ID, ports), CreateBuilder());
}
void BehaviorTreeFactory::registerBuilder(const TreeNodeManifest& manifest,
const NodeBuilder& builder) {
auto it = builders_.find(manifest.registration_ID);
if (it != builders_.end()) {
throw BehaviorTreeException("ID [", manifest.registration_ID,
"] already registered");
}
builders_.insert({manifest.registration_ID, builder});
manifests_.insert({manifest.registration_ID, manifest});
}
具体由XMLParser::Pimpl::loadDocImpl()执行,主要有如下几个步骤。
分为2个部分。
Tree XMLParser::instantiateTree(const Blackboard::Ptr& root_blackboard) {
Tree output_tree;
...
// first blackboard
output_tree.blackboard_stack.push_back(root_blackboard);
_p->recursivelyCreateTree(main_tree_ID, output_tree, root_blackboard,
TreeNode::Ptr());
return output_tree;
}
接下来对recursivelyCreateTree()展开分析。
函数内递归执行recursiveStep(),注意第1个参数是父节点。
void BT::XMLParser::Pimpl::recursivelyCreateTree(
const std::string& tree_ID, Tree& output_tree, Blackboard::Ptr blackboard,
const TreeNode::Ptr& root_parent) {
std::function recursiveStep;
recursiveStep = [&](const TreeNode::Ptr& parent, const XMLElement* element) {
...
};
auto root_element = tree_roots[tree_ID]->FirstChildElement();
// start recursion
recursiveStep(root_parent, root_element);
}
recursiveStep()分为3部分。
如果是SubtreeNode,就根据__shared_blackboard的值来创建blackboard,并添加映射信息,然后递归调用recursivelyCreateTree()来创建子树。
如果是SubtreePlusNode,就根据__autoremap的值来创建blackboard的port的映射,然后递归调用recursivelyCreateTree()来创建子树。