本小结将以具体的例子来讲解SequenceNode与ReactiveSequence之间的差别。
一个同步的动作拥有自己的线程,这意味这它允许用户使用阻塞函数,但同时也会将执行的流程返回到行为树中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // Custom type struct Pose2D { double x, y, theta; }; class MoveBaseAction : public AsyncActionNode { public: MoveBaseAction(const std::string& name, const NodeConfiguration& config) : AsyncActionNode(name, config) { } // 声明名为goal的输入端口 static PortsList providedPorts() { return{ InputPort<Pose2D>("goal") }; } NodeStatus tick() override; // This overloaded method is used to stop the execution of this node. void halt() override { _halt_requested.store(true); } private: // 表示在同一时刻只有唯一的线程能够访问这个bool值,是一个原子操作 std::atomic_bool _halt_requested; }; //------------------------- NodeStatus MoveBaseAction::tick() { Pose2D goal; if ( !getInput<Pose2D>("goal", goal)) { throw RuntimeError("missing required input [goal]"); } printf("[ MoveBase: STARTED ]. goal: x=%.f y=%.1f theta=%.2f\n", goal.x, goal.y, goal.theta); _halt_requested.store(false); int count = 0; // Pretend that "computing" takes 250 milliseconds. // It is up to you to check periodicall _halt_requested and interrupt // this tick() if it is true. while (!_halt_requested && count++ < 25) { SleepMS(10); } std::cout << "[ MoveBase: FINISHED ]" << std::endl; return _halt_requested ? NodeStatus::FAILURE : NodeStatus::SUCCESS; }
方法MoveBaseAction::tick()将在非主线程中执行,而主线程则会触发MoveBaseAction::executeTick()方法。
用户需要负责实现一个有效的halt()函数。
Pose2D是自定义类型,用户也需要实现convertFromString方法,目前有个问题,这个自定义的模板方法是定义在哪个文件中,又是何时被调用的??
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // Template specialization to converts a string to Pose2D. namespace BT { template <> inline Pose2D convertFromString(StringView str) { // The next line should be removed... printf("Converting string: \"%s\"\n", str.data() ); // We expect real numbers separated by semicolons auto parts = splitString(str, ';'); if (parts.size() != 3) { throw RuntimeError("invalid input)"); } else{ Pose2D output; output.x = convertFromString<double>(parts[0]); output.y = convertFromString<double>(parts[1]); output.theta = convertFromString<double>(parts[2]); return output; } } } // end namespace BT
Sequence VS ReactiveSequence 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static const char* xml_text = R"( <root> <BehaviorTree> <Sequence> <BatteryOK/> <SaySomething message="mission started..." /> <MoveBase goal="1;2;3"/> <SaySomething message="mission completed!" /> </Sequence> </BehaviorTree> </root> )"; int main() { using namespace DummyNodes; BehaviorTreeFactory factory; factory.registerSimpleCondition("BatteryOK", std::bind(CheckBattery)); factory.registerNodeType<MoveBaseAction>("MoveBase"); factory.registerNodeType<SaySomething>("SaySomething"); auto tree = factory.createTreeFromText(xml_text); NodeStatus status; std::cout << "\n--- 1st executeTick() ---" << std::endl; status = tree.tickRoot(); SleepMS(150); std::cout << "\n--- 2nd executeTick() ---" << std::endl; status = tree.tickRoot(); SleepMS(150); std::cout << "\n--- 3rd executeTick() ---" << std::endl; status = tree.tickRoot(); std::cout << std::endl; return 0; }
以上为Sequence类型节点树,预期输出为
1 2 3 4 5 6 7 8 9 10 --- 1st executeTick() --- [ Battery: OK ] Robot says: "mission started..." [ MoveBase: STARTED ]. goal: x=1 y=2.0 theta=3.00 --- 2nd executeTick() --- [ MoveBase: FINISHED ] --- 3rd executeTick() --- Robot says: "mission completed!"
其中tree.tickRoot()就是上文提到的executeTick(),当它第一次和第二次被调用时,MoveBase节点返回RUNNING,第三次则是返回SUCCESS。
状态节点BatteryOK只执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 <root> <BehaviorTree> <ReactiveSequence> <BatteryOK/> <Sequence> <SaySomething message="mission started..." /> <MoveBase goal="1;2;3"/> <SaySomething message="mission completed!" /> </Sequence> </ReactiveSequence> </BehaviorTree> </root>
更改为ReactiveSequence类型后,预期输出为
1 2 3 4 5 6 7 8 9 10 11 12 --- 1st executeTick() --- [ Battery: OK ] Robot says: "mission started..." [ MoveBase: STARTED ]. goal: x=1 y=2.0 theta=3.00 --- 2nd executeTick() --- [ Battery: OK ] [ MoveBase: FINISHED ] --- 3rd executeTick() --- [ Battery: OK ] Robot says: "mission completed!"
如果使用ReactiveSequence类型的节点树,那么当MoveBase返回RUNNING时,整个序列都将重启,状态节点BatteryOK会再次被执行。
如果BatteryOK节点返回FAILURE,MoveBase动作将被中断(halted)。