|
| 1 | +# Migration Guide from V2 to V3 |
| 2 | + |
| 3 | +The main goal of this project is to create a Behavior Tree implementation |
| 4 | +that uses the principles of Model Driven Development to separate the role |
| 5 | +of the __Component Developer__ from the __Behavior Designer__ and |
| 6 | +__System Integrator__. |
| 7 | + |
| 8 | +In practice, this means that: |
| 9 | + |
| 10 | +- Custom Actions (or, in general, custom TreeNodes) must be reusable building |
| 11 | +blocks. Implement them once, reuse them many times. |
| 12 | + |
| 13 | +- To build a Behavior Tree out of TreeNodes, the Behavior Designer must |
| 14 | +not need to read nor modify the source code of the a given TreeNode. |
| 15 | + |
| 16 | +There is a __major design flaw__ that undermines this goal in version `2.x`: |
| 17 | +the way the BlackBoard was used to implement DataFlow between nodes. |
| 18 | + |
| 19 | +As described in [issue #18](https://github.com/BehaviorTree/BehaviorTree.CPP/issues/18) |
| 20 | +there are several potential problems with the Blackboard approach: |
| 21 | + |
| 22 | +- To know which entries of the BB are read/written, you should read the source code. |
| 23 | + |
| 24 | +- As a consequence, external tools such as __Groot__ can not know which BB entries are accessed. |
| 25 | + |
| 26 | +- If there is a name clashing (multiple nodes use the same key for different purposes), |
| 27 | + the only way to fit it is modifying the source code. |
| 28 | + |
| 29 | +SMACH solved this problem using [input and output ports](http://wiki.ros.org/smach/Tutorials/User%20Data) |
| 30 | +and remapping to connect them. |
| 31 | + |
| 32 | +In the ROS community, we potentially have the same problem with topics, |
| 33 | +but tools such as __rosinfo__ provides introspection at run-time and name |
| 34 | +clashing is avoided using remapping. |
| 35 | + |
| 36 | +This was the main reason to develop version `3.x` of __Behaviortree.CPP__, but we |
| 37 | +also took the opportunity to do some additional refactoring to make the code |
| 38 | +more understandable. |
| 39 | + |
| 40 | +In this document we will use the following terms quite often: |
| 41 | + |
| 42 | +- __Composition__: it refers to "composing" TreeNodes into Trees. In general |
| 43 | + we want a TreeNode implementation to be composition-agnostic. |
| 44 | + |
| 45 | +- __Model/Modelling__: it is a description of a Tree or TreeNode that is |
| 46 | +sufficient (and necessary) to describe it, without knowing any additional |
| 47 | +detail about the actual C++ implementation. |
| 48 | + |
| 49 | + |
| 50 | +## Blackboard, NodeParameters an DataPorts |
| 51 | + |
| 52 | +In version `2.x` we had the intuition that passing one or more arguments |
| 53 | +to a `TreeNode` would make the node more generic and reusable. |
| 54 | + |
| 55 | +This is similar to the arguments of a function in any programming language. |
| 56 | + |
| 57 | +```C++ |
| 58 | +// with arguments |
| 59 | +GoTo("kitchen") |
| 60 | + |
| 61 | +//Without arguments |
| 62 | +GoToKitchen() |
| 63 | +GoToLivingRoom() |
| 64 | +GoToBedRoom1() |
| 65 | +GoToBedroom2() |
| 66 | +// .... |
| 67 | +``` |
| 68 | +
|
| 69 | +To pass NodeParameters we used the Blackboard, that is nothing more than a |
| 70 | +shared __key/value__ table, i.e. a glorified bunch of global variables. |
| 71 | +
|
| 72 | +The key is a `string`, whilst the value is |
| 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 reusable. |
| 78 | +
|
| 79 | +To fix this, we still use the Blackboard under the hood, but it can not be |
| 80 | +accessed directly anymore. Entries are read/written using respectively `InputPorts` |
| 81 | +and `OutputPorts`. |
| 82 | +
|
| 83 | +These 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 | + <CalculateGoal/> |
| 92 | + <MoveBase goal="${GoalPose}" /> |
| 93 | + </SequenceStar> |
| 94 | + </BehaviorTree> |
| 95 | + </root> |
| 96 | +``` |
| 97 | + |
| 98 | +```C++ |
| 99 | +using namespace BT; |
| 100 | +//Old code (V2) |
| 101 | +NodeStatus CalculateGoal(TreeNode& self) |
| 102 | +{ |
| 103 | + const Pose2D mygoal = { 1, 2, 3.14}; |
| 104 | + // "GoalPose" is hardcoded... we don't like that |
| 105 | + self.blackboard()->set("GoalPose", mygoal); |
| 106 | + return NodeStatus::SUCCESS; |
| 107 | +} |
| 108 | + |
| 109 | +class MoveBase : public AsyncActionNode |
| 110 | +{ |
| 111 | + public: |
| 112 | + |
| 113 | + MoveBase(const std::string& name, const NodeParameters& params) |
| 114 | + : AsyncActionNode(name, params) {} |
| 115 | + |
| 116 | + static const NodeParameters& requiredNodeParameters() |
| 117 | + { |
| 118 | + static NodeParameters params = {{"goal", "0;0;0"}}; |
| 119 | + return params; |
| 120 | + } |
| 121 | + |
| 122 | + NodeStatus tick() |
| 123 | + { |
| 124 | + Pose2D goal; |
| 125 | + if (getParam<Pose2D>("goal", goal)) |
| 126 | + { |
| 127 | + printf("[ MoveBase: DONE ]\n"); |
| 128 | + return NodeStatus::SUCCESS; |
| 129 | + } |
| 130 | + else{ |
| 131 | + printf("MoveBase: Failed for some reason\n"); |
| 132 | + return NodeStatus::FAILURE; |
| 133 | + } |
| 134 | + } |
| 135 | + /// etc. |
| 136 | +}; |
| 137 | +``` |
| 138 | +
|
| 139 | +We may notice that the `NodeParameter` can be remapped in the XML, but |
| 140 | +to change the key "GoalPose" in `CalculateGoalPose`we must inspect the code |
| 141 | +and modify it. |
| 142 | +
|
| 143 | +In other words, `NodeParameter` is already a reasonably good implementation |
| 144 | +of an `InputPort`, but we need to introduce a consistent `OutputPort` too. |
| 145 | +
|
| 146 | +This is the new code: |
| 147 | +
|
| 148 | +```XML |
| 149 | +<root> |
| 150 | + <BehaviorTree> |
| 151 | + <SequenceStar> |
| 152 | + <CalculateGoal target="{GoalPose}" /> |
| 153 | + <MoveBase goal="{GoalPose}" /> |
| 154 | + </SequenceStar> |
| 155 | + </BehaviorTree> |
| 156 | + </root> |
| 157 | +``` |
| 158 | + |
| 159 | +```C++ |
| 160 | +using namespace BT; |
| 161 | +//New code (V3) |
| 162 | +class CalculateGoalPose : public SyncActionNode |
| 163 | +{ |
| 164 | +public: |
| 165 | + |
| 166 | + MoveBase(const std::string& name, const NodeConfiguration& cfg) |
| 167 | + : SyncActionNode(name, cfg) {} |
| 168 | + |
| 169 | + static PortsList providedPorts() |
| 170 | + { |
| 171 | + return { OutputPort<Pose2D>("target") }; |
| 172 | + } |
| 173 | + |
| 174 | + BT::NodeStatus tick() |
| 175 | + { |
| 176 | + const Pose2D myTarget = { 1, 2, 3.14 }; |
| 177 | + setOutput("target", myTarget); |
| 178 | + return NodeStatus::SUCCESS; |
| 179 | + } |
| 180 | +}; |
| 181 | + |
| 182 | +class MoveBase : public AsyncActionNode |
| 183 | +{ |
| 184 | +public: |
| 185 | + |
| 186 | + MoveBase(const std::string& name, const NodeConfiguration& config) |
| 187 | + : AsyncActionNode(name, config) {} |
| 188 | + |
| 189 | + static PortsList providedPorts() |
| 190 | + { |
| 191 | + return { InputPort<Pose2D>("goal", "Port description", "0;0;0") }; |
| 192 | + } |
| 193 | + |
| 194 | + NodeStatus tick() |
| 195 | + { |
| 196 | + Pose2D goal; |
| 197 | + if (auto res = getInput<Pose2D>("goal", goal)) |
| 198 | + { |
| 199 | + printf("[ MoveBase: DONE ]\n"); |
| 200 | + return NodeStatus::SUCCESS; |
| 201 | + } |
| 202 | + else{ |
| 203 | + printf("MoveBase: Failed. Error code: %s\n", res.error()); |
| 204 | + return NodeStatus::FAILURE; |
| 205 | + } |
| 206 | + } |
| 207 | + /// etc. |
| 208 | +}; |
| 209 | +``` |
| 210 | +
|
| 211 | +The main differences are: |
| 212 | +
|
| 213 | +- `requiredNodeParameters()` was replaced by `providedPorts()`, that |
| 214 | + is used to declare both Inputs and Output ports alike. |
| 215 | + |
| 216 | +- `setOutput<>()` has been introduced. The method `blackboard()`can not be |
| 217 | + accessed anymore. |
| 218 | +
|
| 219 | +- `getParam<>()` is now called `getInput<>()` to be more consistent with |
| 220 | + `setOutput<>()`. Furthermore, if an error occurs, we can get the error |
| 221 | + message. |
| 222 | + |
| 223 | +- Remapping to a shared entry ("GoalPose") is done at run-time in the XML. |
| 224 | + You will never need to modify the C++ source code. |
| 225 | +
|
| 226 | +## SubTrees, remapping and isolated Blackboards |
| 227 | +
|
| 228 | +Thanks to ports we solved the problem of __reusability of single treeNodes__. |
| 229 | +
|
| 230 | +But we still need to address the problem of __reusability of entire Trees/SubTrees__. |
| 231 | +
|
| 232 | +According to the rule of __hierarchical composition__, |
| 233 | +from the point of view of a parent Node if should not matter if the |
| 234 | +child is a LeafNode, a DecoratorNode a ControlNode or an entire Tree. |
| 235 | +
|
| 236 | +As mentioned earlier, the Blackboard used to be a large key/value table. |
| 237 | +
|
| 238 | +Unfortunately, this might be challenging when we reuse multiple SubTree, once again |
| 239 | +because of name clashing. |
| 240 | +
|
| 241 | +The solution in version `3.x` is to have a separated and isolated Blackboard |
| 242 | +for each Tree/Subtree. If we want to connect the "internal" ports of a SubTree |
| 243 | +with the other ports of the BB of the parent, we must explicitly do a |
| 244 | +remapping in the XML definition. No C++ code need to be modified. |
| 245 | +
|
| 246 | +From the point of view of the XML, remapped ports of a SubTree looks exactly |
| 247 | +like the ports of a single node. |
| 248 | +
|
| 249 | +For more details, refer to the example __t06_subtree_port_remapping.cpp_. |
| 250 | +
|
| 251 | +
|
| 252 | +## ControlNodes renamed/refactored |
| 253 | +
|
| 254 | +The [principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment) |
| 255 | +applies to user interface and software design. A typical formulation of the principle, from 1984, is: |
| 256 | +
|
| 257 | +>"If a necessary feature has a high astonishment factor, it may be necessary |
| 258 | +to redesign the feature. |
| 259 | +
|
| 260 | +In my opinion, the two main building blocks of BehaviorTree.CPP, the `SequenceNode` |
| 261 | +and the `FallbackNode` have a very high astonishment factor, because they are |
| 262 | +__"reactive"__. |
| 263 | +
|
| 264 | +By "reactive" we mean that: |
| 265 | +
|
| 266 | +- Children (usually `ConditionNodes`) that returned |
| 267 | + a valid value, such as SUCCESS or FAILURE, might be ticked again if another |
| 268 | + child returns RUNNING. |
| 269 | + |
| 270 | +- A different result in that Condition might abort/halt the RUNNING asynchronous child. |
| 271 | +
|
| 272 | +
|
| 273 | +The main concern of the original author of this library was to build reactive |
| 274 | +Behavior Trees (see for reference this [publication](0https://arxiv.org/abs/1709.00084). |
| 275 | +
|
| 276 | +I share this goal, but I prefer to have more explicit names, because reactive |
| 277 | +ControlNodes are useful but hard to reason about sometimes. |
| 278 | +
|
| 279 | +I don't think reactive Controlnodes should be the mindlessly by default. |
| 280 | +
|
| 281 | +For instance, most of the time users I talked with should have used `SequenceStar` |
| 282 | +instead of `Sequence` in many cases. |
| 283 | +
|
| 284 | +I renamed the ControlNodes to reflect this reality: |
| 285 | +
|
| 286 | +
|
| 287 | +| Old Name (v2) | New name (v3) | Is reactive? | |
| 288 | +|:--- |:--- |:---:| |
| 289 | +| Sequence | ReactiveSequence | YES | |
| 290 | +| SequenceStar (reset_on_failure=true) | Sequence | NO | |
| 291 | +| SequenceStar (reset_on_failure=false) | SequenceStar | NO | |
| 292 | +| Fallback | ReactiveFallback | YES | |
| 293 | +| FallbackStar | Fallback | NO | |
| 294 | +| Parallel | Parallel | Yes(v2) / No(v3) | |
| 295 | +
|
| 296 | +
|
| 297 | +A reactive `ParallelNode` was very confusing and error prone; in most cases, |
| 298 | +what you really want is you want to use a `ReactiveSequence` instead. |
| 299 | +
|
| 300 | +In version `2.x` it was unclear what would happen if a "reactive" node has |
| 301 | +more than a single asynchronous child. |
| 302 | +
|
| 303 | +The new recommendation is: |
| 304 | +
|
| 305 | +>__Reactive nodes shouldn't have more than a single asynchronous child__. |
| 306 | +
|
| 307 | +This is a very opinionated decision and for this reason it is documented but |
| 308 | +not enforced by the implementation. |
| 309 | +
|
| 310 | +
|
| 311 | +
|
| 312 | +
|
| 313 | +
|
| 314 | +
|
| 315 | +
|
| 316 | + |
| 317 | +
|
| 318 | +
|
| 319 | +
|
| 320 | +
|
| 321 | +
|
| 322 | +
|
0 commit comments