input and output ports
自定义的TreeNodes用于执行任意简单的或者复杂的软件代码。目的是提供一个更高抽象级别的接口。
因此,其概念上与函数并无不同。
与函数的相似之处:
传递参数到一个节点(inputs);
从一个节点中获取参数(outputs);
一个节点的输出可以作为另一个节点的输入;
BehaviorTree.CPP通过端口提供了一个基本的数据流机制,端口不仅使用简单而且灵活且类型安全。
输入端口:
一个有效的输入:
a、静态的字符串可以由节点解析;
b、指针指向黑板的入口,由一个key来识别;
黑板由一个简单的key/value存储器,可以由树中的所有节点来共享;
黑板入口是一个key/value对;
输入端口可以读黑板中的一个入口,而输出端口可以往黑板上去写一个入口项;
假设要创建一个ActionNode称之为SaySomething,其会打印一个给定的字符串到std::cout上。
那么上面的字符串可以通过一个输入端口称之为message的来传递;
下面可替换的XML语法:
<SaySomething name="first" message="hello world"/>
<SaySomething name="second" message="{greetings}"/>
在第一个节点中的属性message表示:
静态字符串hello world被传递到SaySomething的端口message;
这个消息是从XML文件中读取的,因此在运行时不能改变;
第二个语法表示:
读取黑板的入口greetings的当前值;这个值可以在运行时改变;
SaySomething行为节点(ActionNode)可以被以下实现:
//SyncActionNode (synchronous action) with an input port. class SaySomething : public SyncActionNode { public: //If your node has ports, you must use this constructor signature SaySomething(const std::string& name, const NodeConfiguration& config) : SyncActionNode(name, config){} //It is mandaroty to define this static method. static PortsList providedPorts() { //This action has a single input port called "message" // Any port must have a name. The type is optional. return {InputPort<std::string>("message")}; } //As usual, you must override the virtual function tick() NodeStatus tick() override { Optional<std::string> msg = getInput<std::string>("message"); //Check if optional is valid. If not, throw its error if(!msg) { throw BT::RuntiemError("missing required input[message] : ", msg.error()); } //use the method value() to extract the valid message; std::cout <<"Robot says: " << msg.value() << std::endl; return NodeStatus::SUCCESS; } }
在一个简单的函数中可以实现同样的功能;该函数将BT::TreeNode的一个实例作为输入为了能够访问输入端口的message:
//simple function that return a NodeStatus BT::NodeStatus SaySomethingSimple(BT::TreeNode& self) { Optional<std::string> msg = self.getInput<std::string>("message"); // Check if optional is valid. If not, throw its error if (!msg) { throw BT::RuntimeError("missing required input [message]: ", msg.error()); } //use the method value() to extract the valid message. std::cout <<"Robot says: " << msg.value() <<std::endl; return NodeStatus::SUCCESS; }
当一个自定义的TreeNode有输入或者输出端口的时候,这些端口必须使用静态方法来声明:
static MyCustomNode::PortsList providedPorts();
从端口message中的输入可以使用模板方法TreeNode::getInput<T>(key)来获取;
该方法可能由于多种原因而失败。取决于用户去检查返回值的有效性,然后决定再做什么:
a.返回 NodeStatus::FAILURE
b.抛出异常;
c.使用不同的默认值;
注意:
强烈建议方法getInput()在tick()中实现,而不是在类的构造函数中实现;
c++代码禁止假设输入的类型,因为可能是静态的也可能是动态的。动态的输入可能在运行时发生变化,所以需要周期性读取。
输出端口
一个输入端口指向的黑板的入口项是有效的,只有当另一个节点已经往该同样的项中写了值。
下面是输出端口的例子:
class ThinkWhatToSay : public SyncActionNode { public: ThinkWhatToSay(const std::string& name, const NodeConfiguration& config) : SyncActionNode(name, config) { } static PortsList providedPorts() { return { OutputPort<std::string>("text") }; } // This Action writes a value into the port "text" NodeStatus tick() override { // the output may change at each tick(). Here we keep it simple. setOutput("text", "The answer is 42" ); return NodeStatus::SUCCESS; } };
可以替换使用的,大多数调试阶段,可能写一个静态的值到入口项通过使用内建的行为SetBlackboard。
<SetBlackboard output_key="the_answer" value="The answer is 42"/>
一个完整的例子,本例中一个Sequence中有5个action被执行:
a、行为1和4从输入端口message中读取静态字符串;
b、行为3和5读输入端口message从黑板中的the_answer项中读取;
c、行为2向黑板中的项the_answer中写入数据;
SaySomething2是一个简单的SimpleActionNode:
<root main_tree_to_execute = "MainTree" > <BehaviorTree ID="MainTree"> <Sequence name="root_sequence"> <SaySomething message="start thinking..." /> <ThinkWhatToSay text="{the_answer}"/> <SaySomething message="{the_answer}" /> <SaySomething2 message="SaySomething2 works too..." /> <SaySomething2 message="{the_answer}" /> </Sequence> </BehaviorTree> </root>
#include "behaviortree_cpp_v3/bt_factory.h" // file that contains the custom nodes definitions #include "dummy_nodes.h" int main() { using namespace DummyNodes; BehaviorTreeFactory factory; factory.registerNodeType<SaySomething>("SaySomething"); factory.registerNodeType<ThinkWhatToSay>("ThinkWhatToSay"); // SimpleActionNodes can not define their own method providedPorts(). // We should pass a PortsList explicitly if we want the Action to // be able to use getInput() or setOutput(); PortsList say_something_ports = { InputPort<std::string>("message") }; factory.registerSimpleAction("SaySomething2", SaySomethingSimple, say_something_ports ); auto tree = factory.createTreeFromFile("./my_tree.xml"); tree.tickRoot(); /* Expected output: Robot says: start thinking... Robot says: The answer is 42 Robot says: SaySomething2 works too... Robot says: The answer is 42 */ return 0; }
通过指向黑板上的同一个项且该项的类型相同来彼此互连;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2017-04-25 第十一课,ROS与传感器
2017-04-25 第十课,ROS仿真2
2017-04-25 第九课,ROS仿真1