大家好,欢迎大家关注我的知乎专栏慢慢悠悠小马车


BehaviorTreeFactory

定义在BehaviorTree.CPP/include/behaviortree_cpp_v3/bt_factory.h,主要包含3个容器来保存数据。

/*** @brief The BehaviorTreeFactory is used to create instances of a* TreeNode at run-time.* Some node types are "builtin", whilst other are used defined and need* to be registered using a unique ID.*/
class BehaviorTreeFactory {
private:std::unordered_map<std::string, NodeBuilder> builders_;std::unordered_map<std::string, TreeNodeManifest> manifests_;std::set<std::string> builtin_IDs_;
}

以BehaviorTree.CPP/examples/t03_generic_ports.cpp为例,在构造BehaviorTreeFactory实例后立即打印这3个容器的元素的名称,可以发现输出结果仅顺序不同,说明Factory构造后默认包含了内建的29个nodes。

builders[1]=Switch4
builders[2]=Switch6
builders[3]=BlackboardCheckDouble
builders[4]=BlackboardCheckInt
builders[5]=SubTree
builders[6]=KeepRunningUntilFailure
builders[7]=Switch5
builders[8]=ReactiveSequence
builders[9]=Parallel
builders[10]=Delay
builders[11]=SetBlackboard
builders[12]=SequenceStar
builders[13]=Fallback
builders[14]=AlwaysSuccess
builders[15]=ReactiveFallback
builders[16]=Sequence
builders[17]=Switch3
builders[18]=Switch2
builders[19]=AlwaysFailure
builders[20]=IfThenElse
builders[21]=WhileDoElse
builders[22]=SubTreePlus
builders[23]=ForceSuccess
builders[24]=Inverter
builders[25]=BlackboardCheckString
builders[26]=RetryUntilSuccesful
builders[27]=ForceFailure
builders[28]=Repeat
builders[29]=Timeoutmanifests[1]=Switch4
manifests[2]=Switch6
manifests[3]=BlackboardCheckDouble
manifests[4]=BlackboardCheckInt
manifests[5]=SubTree
manifests[6]=KeepRunningUntilFailure
manifests[7]=Switch5
manifests[8]=ReactiveSequence
manifests[9]=Parallel
manifests[10]=Delay
manifests[11]=SetBlackboard
manifests[12]=SequenceStar
manifests[13]=Fallback
manifests[14]=AlwaysSuccess
manifests[15]=ReactiveFallback
manifests[16]=Sequence
manifests[17]=Switch3
manifests[18]=Switch2
manifests[19]=AlwaysFailure
manifests[20]=IfThenElse
manifests[21]=WhileDoElse
manifests[22]=SubTreePlus
manifests[23]=ForceSuccess
manifests[24]=Inverter
manifests[25]=BlackboardCheckString
manifests[26]=RetryUntilSuccesful
manifests[27]=ForceFailure
manifests[28]=Repeat
manifests[29]=TimeoutbuiltinNodes[1]=AlwaysFailure
builtinNodes[2]=AlwaysSuccess
builtinNodes[3]=BlackboardCheckDouble
builtinNodes[4]=BlackboardCheckInt
builtinNodes[5]=BlackboardCheckString
builtinNodes[6]=Delay
builtinNodes[7]=Fallback
builtinNodes[8]=ForceFailure
builtinNodes[9]=ForceSuccess
builtinNodes[10]=IfThenElse
builtinNodes[11]=Inverter
builtinNodes[12]=KeepRunningUntilFailure
builtinNodes[13]=Parallel
builtinNodes[14]=ReactiveFallback
builtinNodes[15]=ReactiveSequence
builtinNodes[16]=Repeat
builtinNodes[17]=RetryUntilSuccesful
builtinNodes[18]=Sequence
builtinNodes[19]=SequenceStar
builtinNodes[20]=SetBlackboard
builtinNodes[21]=SubTree
builtinNodes[22]=SubTreePlus
builtinNodes[23]=Switch2
builtinNodes[24]=Switch3
builtinNodes[25]=Switch4
builtinNodes[26]=Switch5
builtinNodes[27]=Switch6
builtinNodes[28]=Timeout
builtinNodes[29]=WhileDoElse

在factory添加了2个node后,再次打印,builders和manifests就多了2个对应的元素。 registerNodeType()的入参,就表明了实际节点类型T(CalculateGoal)在树中的名称(同样是CalculateGoal)。建议大家令实际类型和入参ID相同,可以减少很多迷惑。

factory.registerNodeType<CalculateGoal>("CalculateGoal");
factory.registerNodeType<PrintTarget>("PrintTarget");
/** registerNodeType is the method to use to register your custom TreeNode.**  It accepts only classed derived from either ActionNodeBase, DecoratorNode,*  ControlNode or ConditionNode.*/
template <typename T>
void registerNodeType(const std::string& ID);

在加载tree之前,开发者必须先将自定义的node注册进入factory,才能正确解析xml。1个factory可以包含多个tree。

auto tree = factory.createTreeFromText(xml_text);

NodeBuilder

NodeBuilder定义在BehaviorTree.CPP/include/behaviortree_cpp_v3/bt_factory.h,使用了建造者模式,可以理解为模板化的node构造函数,是偏特化的模板类定义。根据构造函数的参数个数区别,选择不同的builder,返回一个智能指针。

/// The term "Builder" refers to the Builder Pattern (https://en.wikipedia.org/wiki/Builder_pattern)
typedef std::function<std::unique_ptr<TreeNode>(const std::string&, const NodeConfiguration&)>NodeBuilder;// 检查T是否有带参(const std::string&)的默认构造函数
template <typename T>
using has_default_constructor = typename std::is_constructible<T, const std::string&>;// 检查T是否有带参(const std::string&, const NodeConfiguration&)的构造函数
template <typename T>
using has_params_constructor =typename std::is_constructible<T, const std::string&, const NodeConfiguration&>;// 对应T既有默认构造函数,又有带2个参数构造函数的情况,即上述2条判断都为true。注意make_unique的参数区分
// 定义了1个T类型的指针,默认值是nullptr。这不重要,重要的是enable_if的条件检查
template <typename T>
inline NodeBuilder
CreateBuilder(typename std::enable_if<has_default_constructor<T>::value &&has_params_constructor<T>::value>::type* = nullptr) {return [](const std::string& name, const NodeConfiguration& config) {// Special case. Use default constructor if parameters are emptyif (config.input_ports.empty() && config.output_ports.empty() &&has_default_constructor<T>::value) {return std::make_unique<T>(name);}return std::make_unique<T>(name, config);};
}// 对应T只有带2参构造函数的情况,注意new的参数
template <typename T>
inline NodeBuilder
CreateBuilder(typename std::enable_if<!has_default_constructor<T>::value &&has_params_constructor<T>::value>::type* = nullptr) {return [](const std::string& name, const NodeConfiguration& params) {return std::unique_ptr<TreeNode>(new T(name, params));};
}// 对应T只有默认构造函数(带1参)的情况,注意new的参数
template <typename T>
inline NodeBuilder
CreateBuilder(typename std::enable_if<has_default_constructor<T>::value &&!has_params_constructor<T>::value>::type* = nullptr) {return [](const std::string& name, const NodeConfiguration&) {return std::unique_ptr<TreeNode>(new T(name));};
}

语法参考资料:

​​is_constructible - C++ Reference

std::enable_if - cppreference.com

std::enable_if 的几种用法 ← Yee

C++11新特性--std::enable_if和SFINAE - 简书

Blackboard

定义在BehaviorTree.CPP/include/behaviortree_cpp_v3/blackboard.h,是树中nodes传输数据的方式,所有nodes共享。每棵树都有自己的blackboard,开发者需要显式的在不同的父树、子树的blackboard间创建映射关联。这个操作无需通过代码实现,只需要在xml中编辑。同样,nodes执行顺序也是在xml中设定的,这使得调试时可以节省大量的程序编译时间。Blackboard类中存储数据的容器有3个。

private:std::unordered_map<std::string, Entry> storage_;  // 保存键值对// 指向父blackboard的指针,会与本blackboard有重映射关系std::weak_ptr<Blackboard> parent_bb_; // weak_ptr可以避免循环引用// 保存映射的内外对应关系std::unordered_map<std::string,std::string> internal_to_external_;

Entry就是blackboard保存的1个元素。

struct Entry {Any value;  // port存储的值const PortInfo port_info; // port的其他信息,不含名字和值
}

Port

Port是节点间交换数据的机制,通过Blackboard的相同key联系起来。节点的ports的数量、名称、类型等,在编译时就已知了,体现在xml文件中。

如果一个节点有输入/输出port,必须在providedPorts()函数中声明。

获取port的值可以用getInput(),设置port的值可以用setOutput()。

typedef std::unordered_map<std::string, PortInfo> PortsList;static PortsList providedPorts();template <typename T>
Result getInput(const std::string& key, T& destination) const;template <typename T>
Optional<T> getInput(const std::string& key) const;template <typename T>
Result setOutput(const std::string& key, const T& value);
class PortInfo {
private:PortDirection _type;const std::type_info* _info;// 将port输入/输出的string自动转换为指定类型的函数StringConverter _converter;std::string description_; // port的含义描述std::string default_value_; // port没有设置值时的默认值
}
typedef std::function<Any(StringView)> StringConverter;

PortDirection分为INPUT、OUTPUT、INOUT 3种。开发者最好不要对输入的port和数据做任何假设,推荐在tick()中周期性的读取输入值,以应对外界变化,而不是只在构造函数或初始化时只读取一次保存下来。保存历史信息会伤害行为树的reactive特性,使得节点的行为不仅与现在情景有关,还与过去有关。

template <typename T>
inline T convertFromString(StringView /*str*/);

当在xml中通过SetBlackboard设置port后,代码中运行getInput()会后台调用convertFromString(),将读入的string转换为对应的类型。BehaviorTree.CPP/include/behaviortree_cpp_v3/basic_types.h中实现了StirngView向string, const char*, int, unsigned, long, unsigned long, float, double, vector<int>, bool, NodeStatus, Nodetype, PortDirection等内建类型的转换。如果开发者想转换为自定义类型的话,可以参考basic_types.cpp的代码。

树中的nodes间互相读写port是不会触发convertFromString()的,毕竟这是一个string到type的单向转换。

// 转换为枚举类型
template <>
NodeType convertFromString<NodeType>(StringView str) {if (str == "Action") return NodeType::ACTION;if (str == "Condition") return NodeType::CONDITION;if (str == "Control") return NodeType::CONTROL;if (str == "Decorator") return NodeType::DECORATOR;if (str == "SubTree" || str == "SubTreePlus") return NodeType::SUBTREE;return NodeType::UNDEFINED;
}
struct Position2D {double x, y;
};// 转换为自定义类型
template <>
inline Position2D convertFromString(StringView str) {// real numbers separated by semicolonsauto parts = splitString(str, ';');if (parts.size() != 2) {throw RuntimeError("invalid input)");} else {Position2D output;output.x = convertFromString<double>(parts[0]);output.y = convertFromString<double>(parts[1]);return output;}
}

std::optional - cppreference.com

std::any - cppreference.com

C++17之std::any_janeqi1987的专栏-CSDN博客_std::any

BT4:库中基本类型——Factory和Blackboard相关推荐

  1. Swift HandyJSON库中的类型相互转换的实现

    前言 阅读优秀的开源框架,对提升自己的能力有很大帮助.HandyJSON库就是其中的优秀框架之一, 本文介绍一下HandyJSON库是如何处理类型间相互转换的. 我们在开发时,常见的类型转换如下: D ...

  2. python怎么显示提示_Python中的类型提示(中)

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 3.接口存根文件 这个选项允许你如下图一般保存你的代码: 并在原文件的旁边添加一个扩展名为pyi的文件: ...

  3. 为什么写了value属性 jq赋值value值不显示_[Go基础]理解 Go 标准库中的 atomic.Value 类型

    转载声明 文章作者:喵叔 上次更新:2019-03-15 许可协议:CC BY-NC-ND 4.0(转载请注明出处) 原文链接:https://blog.betacat.io/post/golang- ...

  4. java jceks 密钥_Java中不同类型的密钥库(Keystore) – 概述

    阅读: 877 密钥库是用于存储加密密钥和证书的存储工具 ,最常用于SSL通信,以证明服务器和客户端的身份.密钥库可以是文件或硬件设备.有三种类型的条目可以存储在密钥库中,取决于密钥库的类型,这三种类 ...

  5. 计算机oleaut32.dll,OLEAUT32.dll模块中处理类型库的相关函数可导致代码执行 -电脑资料...

    tombkeeper[Base64Decode("dG9tYmtlZXBlckB4Zm9jdXMub3Jn")] 2009.10.1 刚下载完几部不错的片子,但是考虑到做人要讲信用 ...

  6. 如何将AD类型的封装导成Allegro库中的封装

    1.首先使用的是LCSC下载的封装,以电容0201封装为例.下载下来格式如下: 2.中间需要使用PADS转一下格式,转成asc格式的.AD的不知道可不可以直接转,我也是之前在某个教程中学的,可能操作复 ...

  7. Openzwave库中对Zwave产品配置文件的使用

    Openzwave库中对Zwave产品配置文件的使用 在openzwave库中通过配置文件定义一些可配置参数,对于每一个zwave命令类来说,我们都可以通过配置文件定义这些参数:在openzwave中 ...

  8. 全面理解Python中的类型提示(Type Hints)

    众所周知,Python 是动态类型语言,运行时不需要指定变量类型.这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发 ...

  9. Dictionary作为数据源绑定,调用c++库中返回为BYTE*的函数,listView项排序

    最近在做一个电子档案管理的项目.现在还处于初期,只是做一个简单demo拿去跟客户演示.至于最后谈不谈得下来,到底做不做,反正我是不看好,但没因为这样就马马虎虎.草草了事.这个项目算是b/s加c/s混合 ...

最新文章

  1. 美国《时代》周刊公布年度25大最佳发明名单
  2. declare命令用法
  3. 柱状图中xy轴怎么出现_烤烟烘烤中出现叶片发霉怎么办?
  4. Android自定义View之仿QQ侧滑菜单实现
  5. ifm management of technology q and a session 2
  6. updatebyprimarykeyselective的where条件是全部字段_ArcGIS 字段计算器
  7. sudo 安装 常见错误
  8. oracle之单行函数2
  9. 技术交底软件_【干货分享】软件类产品如何进行专利挖掘与技术交底书撰写?...
  10. Linux 管理员技术
  11. 如何开发ORACLE存储过程
  12. CentOS录屏快捷键
  13. redis在Windows下以后台服务一键搭建集群(多机器)
  14. Kafka和Unix管道的示例
  15. Spring MVC JSON自己定义类型转换
  16. 摄影测量学——解析法相对定向
  17. c语言编写比赛评分程序,比赛评分系统c语言课程设计.doc
  18. 【智能无线小车系列二】车体的组装
  19. 编写一个fun函数,该函数地功能是:统计一行字符串中单词的个数,并作为函数值返回。字符串在主函数中输入,规定所有的单词由小写字母组成,单词之间有若干个空格隔开,一行的开始没有空格!
  20. ADFS Change Token SigningEncryption Certificate Expiration Date

热门文章

  1. 电到底是怎么工作的?
  2. matlab读写xlsx文件和txt文件
  3. Photoshop 人物素描化
  4. ajax实现登录成功后设置cookie,使用jquery的cookie实现登录页记住用户名和密码的方法...
  5. “FCK编辑器”版本识别及信息收集技术
  6. Dual-awareness Attention for Few-Shot Object Detection
  7. 小冰与51CTO的前世今生
  8. 51单片机(三十)—— 矩阵键盘计算器
  9. 数学黑洞(一)令人拍案叫绝的卡布列克常数
  10. HTML+CSS页面练习——legend第五部分