|
| 1 | +# Migration Guide from V2 to V3 |
| 2 | + |
| 3 | +The main goal of this project id to create a Behavior Tree implementation |
| 4 | +that uses the principle of Model Driven Development to separate the role |
| 5 | +of the __Component Developer__ from the __Behavior Designed__ and __System Integrator__. |
| 6 | + |
| 7 | +In practice, this means that: |
| 8 | + |
| 9 | +- Custom Actions (or, in general, custom TreeNodes) must be reusable building |
| 10 | +blocks. Implement them once, reuse them many times. |
| 11 | + |
| 12 | +- To build a BehaviorTree out of TreeNodes, the Behavior Designer must not need to read |
| 13 | +nor modify the source code of the a given TreeNode. |
| 14 | + |
| 15 | +There is a __major design flaw__ that undermines this goal: the way |
| 16 | +the BlackBoard was used in version `2.x` to implement dataflow between nodes. |
| 17 | + |
| 18 | +In general, DataFlow should not be the main concern of a library like this, |
| 19 | +that focuses on Coordination, but it is apparent to anyone that had implemented |
| 20 | +a sufficiently large coordination component that if would be impossible |
| 21 | +to ignore it completely. |
| 22 | + |
| 23 | +As described in [issue #18](https://github.com/BehaviorTree/BehaviorTree.CPP/issues/18) |
| 24 | +there are several potential problems with the Blackboard approach: |
| 25 | + |
| 26 | +- To know which entries of the BB are read/written, you should read the source code. |
| 27 | +- As a consequence, external tools such as __Groot__ can not know which BB entries are accessed. |
| 28 | +- If there is a name clashing (multiple nodes use the same key for different purposes), |
| 29 | + the only way to fit it is modifying the source code. |
| 30 | + |
| 31 | +SMACH solved this problem using [input and output ports](http://wiki.ros.org/smach/Tutorials/User%20Data) |
| 32 | +and remapping to connect them. |
| 33 | + |
| 34 | +In the ROS community, we potentially have the same problem with topics, |
| 35 | +but tools such as __rosinfo__ provides introspection at run-time and name |
| 36 | +clashing is avoided using remapping. |
| 37 | + |
| 38 | +This was the main reason to develop version `3.x` of __Behaviortree.CPP__, but we |
| 39 | +also took the opportunity to do some additional refactoring to make the code |
| 40 | +more understandable. |
| 41 | + |
| 42 | +In this document we will use the following terms often: |
| 43 | + |
| 44 | +- __Composition__: it refers to "composing" TreeNodes into Trees. In general |
| 45 | + we want a TreeNode implementation to be composition-agnostic. |
| 46 | + |
| 47 | +- __Model/Modelling__: it is a description of a Tree or TreeNode that is |
| 48 | +sufficient (and necessary) to describe it, without knowing any additional |
| 49 | +detail about the actual implementation. |
| 50 | + |
| 51 | + |
| 52 | +# 2. Blackboard NodeParameters an DataPorts |
| 53 | + |
| 54 | +In version `2.x` we had the intuition that passing one or more arguments |
| 55 | +to a `TreeNode` would make the node more generic and reusable. |
| 56 | + |
| 57 | +This is similar to the arguments of a funtion in any programming language. |
| 58 | + |
| 59 | +```C++ |
| 60 | +// with arguments |
| 61 | +GoTo("kitchen") |
| 62 | + |
| 63 | +//Without arguments |
| 64 | +GoToKitchen() |
| 65 | +GoToLivingRoom() |
| 66 | +GoToBedRoom1() |
| 67 | +GoToBedroom2() |
| 68 | +// .... |
| 69 | +``` |
| 70 | +
|
| 71 | +On the other hand, we had the Blackboard, that was nothing more than a |
| 72 | +shared __key/value__ table; the key is a `string`, whilst the value is a |
| 73 | +stored in a type-safe container similar to `std::any` or `std:.variant`. |
| 74 | +
|
| 75 | +The problem is that writing/reading in an entry of the BB is done implicitly |
| 76 | +in the source code and it is usually hard-coded. This makes the TreeNode |
| 77 | +not fully reusable. |
| 78 | +
|
| 79 | +To fix this, we still use the Blackboard under the hood, but it can not be |
| 80 | +accessed directly. Entires are read/written using respeticely `InputPorts` |
| 81 | +and `OutputPorts`. |
| 82 | +
|
| 83 | +This ports __must be modelled__ to allow remapping at run-time. |
| 84 | +
|
| 85 | +Let's take a look to an example at the old code: |
| 86 | +
|
| 87 | +```XML |
| 88 | +<root> |
| 89 | + <BehaviorTree> |
| 90 | + <SequenceStar> |
| 91 | + <CalculateGoalPose/> |
| 92 | + <MoveBase goal="${GoalPose}" /> |
| 93 | + </SequenceStar> |
| 94 | + </BehaviorTree> |
| 95 | + </root> |
| 96 | +``` |
| 97 | + |
| 98 | +```C++ |
| 99 | +//Old code (V2) |
| 100 | +NodeStatus CalculateGoalPose(TreeNode& self) |
| 101 | +{ |
| 102 | + const Pose2D mygoal = { 1, 2, 3.14}; |
| 103 | + // "GoalPose" is hardcoded... we don't like that |
| 104 | + self.blackboard()->set("GoalPose", mygoal); |
| 105 | + return NodeStatus::SUCCESS; |
| 106 | +} |
| 107 | + |
| 108 | +class MoveBase : public BT::AsyncActionNode |
| 109 | +{ |
| 110 | + public: |
| 111 | + |
| 112 | + MoveBase(const std::string& name, const BT::NodeParameters& params) |
| 113 | + : AsyncActionNode(name, params) {} |
| 114 | + |
| 115 | + static const BT::NodeParameters& requiredNodeParameters() |
| 116 | + { |
| 117 | + static BT::NodeParameters params = {{"goal", "0;0;0"}}; |
| 118 | + return params; |
| 119 | + } |
| 120 | + |
| 121 | + BT::NodeStatus tick() |
| 122 | + { |
| 123 | + Pose2D goal; |
| 124 | + if (getParam<Pose2D>("goal", goal)) |
| 125 | + { |
| 126 | + printf("[ MoveBase: DONE ]\n"); |
| 127 | + return BT::NodeStatus::SUCCESS; |
| 128 | + } |
| 129 | + else{ |
| 130 | + printf("MoveBase: Failed for some reason\n"); |
| 131 | + return BT::NodeStatus::FAILURE; |
| 132 | + } |
| 133 | + } |
| 134 | + /// etc. |
| 135 | +}; |
| 136 | +``` |
| 137 | +
|
| 138 | +We may noticed that the `NodeParameter` can be remapped in the XML, but |
| 139 | +to change the key "GoalPose" in `CalculateGoalPose`we must inspect the code |
| 140 | +and modify it. |
| 141 | +
|
| 142 | +In other words, `NodeParameter` is already a reasonably good implementation |
| 143 | +of an `InputPort`, but we need a consisten equivalent version for `Outputport`. |
| 144 | +
|
| 145 | +This is the new code: |
| 146 | +
|
| 147 | +```XML |
| 148 | +<root> |
| 149 | + <BehaviorTree> |
| 150 | + <SequenceStar> |
| 151 | + <CalculateGoalPose target="{GoalPose}" /> |
| 152 | + <MoveBase goal ="{GoalPose}" /> |
| 153 | + </SequenceStar> |
| 154 | + </BehaviorTree> |
| 155 | + </root> |
| 156 | +``` |
| 157 | + |
| 158 | +```C++ |
| 159 | +//New code (V3) |
| 160 | +class CalculateGoalPose : public BT::SyncActionNode |
| 161 | +{ |
| 162 | +public: |
| 163 | + |
| 164 | + MoveBase(const std::string& name, const BT::NodeConfiguration& cfg) |
| 165 | + : SyncActionNode(name, cfg) {} |
| 166 | + |
| 167 | + static BT::PortsList providedPorts() |
| 168 | + { |
| 169 | + return { BT::OutputPort<Pose2D>("target") }; |
| 170 | + } |
| 171 | + |
| 172 | + BT::NodeStatus tick() |
| 173 | + { |
| 174 | + const Pose2D myTarget = { 1, 2, 3.14 }; |
| 175 | + setOutput("target", myTarget); |
| 176 | + return BT::NodeStatus::SUCCESS; |
| 177 | + } |
| 178 | +}; |
| 179 | + |
| 180 | +class MoveBase : public BT::AsyncActionNode |
| 181 | +{ |
| 182 | +public: |
| 183 | + |
| 184 | + MoveBase(const std::string& name, const BT::NodeConfiguration& config) |
| 185 | + : AsyncActionNode(name, config) {} |
| 186 | + |
| 187 | + static BT::PortsList providedPorts() |
| 188 | + { |
| 189 | + return { BT::InputPort<Pose2D>("goal", "Port description", "0;0;0") }; |
| 190 | + } |
| 191 | + |
| 192 | + BT::NodeStatus tick() |
| 193 | + { |
| 194 | + Pose2D goal; |
| 195 | + if (auto res = getInput<Pose2D>("goal", goal)) |
| 196 | + { |
| 197 | + printf("[ MoveBase: DONE ]\n"); |
| 198 | + return BT::NodeStatus::SUCCESS; |
| 199 | + } |
| 200 | + else{ |
| 201 | + printf("MoveBase: Failed. Error code: %s\n", res.error()); |
| 202 | + return BT::NodeStatus::FAILURE; |
| 203 | + } |
| 204 | + } |
| 205 | + /// etc. |
| 206 | +}; |
| 207 | +``` |
| 208 | +
|
| 209 | +The main differences are: |
| 210 | +
|
| 211 | +- `requiredNodeParameters()` is now `providedPorts()` and it is used to |
| 212 | + declare both Inputs and Output ports alike. |
| 213 | + |
| 214 | +- `setOutput<>()` has been introduced and must be declared in `providedPorts()`. |
| 215 | +
|
| 216 | +- `getParam<>()` is now called `getInput<>()` to be more consistent with |
| 217 | + `setOutput<>()`. Furthermore, if an error occurs, we can get the error |
| 218 | + message. |
| 219 | + |
| 220 | +- Remapping to a shared entry ("GoalPose") is done at run-time in the XML. |
| 221 | +
|
0 commit comments