Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a7933a7

Browse files
author
Davide Faconti
committed
Merge branch 'ver_3' of https://github.com/BehaviorTree/BehaviorTree.CPP into ver_3
2 parents f206e25 + 086e92f commit a7933a7

File tree

7 files changed

+334
-42
lines changed

7 files changed

+334
-42
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
*~
22
/CMakeLists.txt.user
33
build*
4-
site

MigrationGuide.md

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
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+

examples/t06_subtree_port_remapping.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ static const char* xml_text = R"(
2828
<Sequence name="main_sequence">
2929
<SetBlackboard output_key="move_goal" value="1;2;3" />
3030
<SubTree ID="MoveRobot" target="move_goal" output="move_result" />
31-
<!-- elternatively use the verbose version...
32-
<SubTree ID="MoveRobot">
33-
<remap internal="target" external="move_goal"/>
34-
<remap internal="output" external="move_result"/>
35-
</SubTree>
36-
-->
3731
<SaySomething message="{move_result}"/>
3832
</Sequence>
3933
@@ -56,12 +50,6 @@ static const char* xml_text = R"(
5650

5751
// clang-format on
5852

59-
/** using the <remap> tag we where able to connect the ports as follows:
60-
*
61-
* MoveRobot->target is connected to MainTree->move_goal
62-
* MoveRobot->output is connected to MainTree->move_result
63-
*
64-
*/
6553

6654
using namespace BT;
6755
using namespace DummyNodes;

0 commit comments

Comments
 (0)