Skip to content

Commit eaeae7d

Browse files
facontidavideclaude
andcommitted
docs: add missing node documentation and improve existing pages
- Improve Repeat/RetryUntilSuccessful with port tables, XML examples, SKIPPED status handling, and infinite loop docs (#988) - Add Timeout decorator documentation (#933) - Create Parallel and ParallelAll node page (#185) - Create IfThenElse and WhileDoElse page (#657) - Create Switch node page (#499) - Add BidirectionalPort section to ports tutorial (#664) - Update cross-reference disclaimers across existing pages (#649) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent c82aa68 commit eaeae7d

7 files changed

Lines changed: 313 additions & 14 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
sidebar_position: 5
3+
sidebar_label: Conditional Control Nodes
4+
---
5+
6+
# Conditional Control Nodes
7+
8+
These control nodes select which child to execute based on the result of a condition (the first child).
9+
10+
Currently the framework provides two kinds:
11+
12+
- IfThenElse
13+
- WhileDoElse
14+
15+
Both must have exactly **2 or 3 children**.
16+
17+
## IfThenElse
18+
19+
IfThenElse is a **non-reactive** conditional node.
20+
21+
- The **1st child** is the condition ("if").
22+
- The **2nd child** is executed if the condition returns SUCCESS ("then").
23+
- The **3rd child** (optional) is executed if the condition returns FAILURE ("else").
24+
25+
If only 2 children are provided and the condition returns FAILURE, the node returns FAILURE
26+
(equivalent to having `AlwaysFailure` as the 3rd child).
27+
28+
The condition is evaluated **only once**. If the 2nd or 3rd child returns RUNNING,
29+
the condition is **not** re-evaluated on subsequent ticks.
30+
31+
```xml
32+
<IfThenElse>
33+
<IsDoorOpen/> <!-- condition -->
34+
<WalkThrough/> <!-- then branch -->
35+
<TryAnotherPath/> <!-- else branch (optional) -->
36+
</IfThenElse>
37+
```
38+
39+
## WhileDoElse
40+
41+
WhileDoElse is the **reactive** variant of IfThenElse. It re-evaluates the condition
42+
at **every tick**.
43+
44+
- The **1st child** is the condition, evaluated on every tick ("while").
45+
- The **2nd child** is executed while the condition returns SUCCESS ("do").
46+
- The **3rd child** (optional) is executed while the condition returns FAILURE ("else").
47+
48+
If the 2nd or 3rd child is RUNNING and the condition result **changes**, the running
49+
child is **halted** before switching to the other branch.
50+
51+
```xml
52+
<WhileDoElse>
53+
<IsBatteryOK/> <!-- condition, re-evaluated each tick -->
54+
<ContinueMission/> <!-- do branch -->
55+
<GoToChargingStation/> <!-- else branch (optional) -->
56+
</WhileDoElse>
57+
```
58+
59+
## IfThenElse vs WhileDoElse
60+
61+
| Feature | IfThenElse | WhileDoElse |
62+
|---------|------------|-------------|
63+
| Re-evaluates condition? | No | Yes (every tick) |
64+
| Reactive | No | Yes |
65+
| Halts running child on condition change? | N/A | Yes |
66+
| Use case | One-time branching | Continuous monitoring |

docs/nodes-library/DecoratorNode.md

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A decorator is a node that must have a single child.
55
It is up to the Decorator to decide if, when and how many times the child should be
66
ticked.
77

8-
> Some nodes are not listed yet. See [decorators](https://github.com/BehaviorTree/BehaviorTree.CPP/tree/master/include/behaviortree_cpp/decorators) on Github for complete definitions.
8+
> For the complete list of built-in nodes, see the other pages in this section and the [source code](https://github.com/BehaviorTree/BehaviorTree.CPP/tree/master/include/behaviortree_cpp) on Github.
99
1010
## Inverter
1111

@@ -28,24 +28,45 @@ Otherwise, it returns always FAILURE.
2828

2929
## Repeat
3030

31-
Tick the child up to N times (within one of its tick), where N is passed as [Input Port](tutorial-basics/tutorial_02_basic_ports.md) `num_cycles`,
32-
as long as the child returns SUCCESS.
33-
Return SUCCESS after the N repetitions in the case that the child always returned SUCCESS.
34-
If `num_cycles` is -1, repeat indefinitely.
31+
Tick the child up to N times, as long as the child returns SUCCESS.
3532

36-
Interrupt the loop if the child returns FAILURE and, in that case, return FAILURE too.
33+
| Port | Type | Default | Description |
34+
|------|------|---------|-------------|
35+
| `num_cycles` | InputPort\<int\> | (required) | Number of repetitions. Use `-1` for infinite loop. |
3736

38-
If the child returns RUNNING, this node returns RUNNING too and the repetitions will continue without incrementing on the next tick of the Repeat node.
37+
- Returns **SUCCESS** after all N repetitions complete successfully.
38+
- Returns **FAILURE** immediately if the child returns FAILURE (loop is interrupted).
39+
- Returns **RUNNING** if the child returns RUNNING; the counter is **not** incremented and the same iteration resumes on the next tick.
40+
- If the child returns **SKIPPED**, the child is reset but the counter is not incremented.
41+
42+
```xml
43+
<Repeat num_cycles="3">
44+
<ClapYourHandsOnce/>
45+
</Repeat>
46+
```
3947

4048
## RetryUntilSuccessful
4149

42-
Tick the child up to N times, where N is passed as [Input Port](tutorial-basics/tutorial_02_basic_ports.md) `num_attempts`,
43-
as long as the child returns FAILURE.
44-
Return FAILURE after the N attempts in the case that the child always returned FAILURE.
50+
Tick the child up to N times, as long as the child returns FAILURE.
51+
52+
| Port | Type | Default | Description |
53+
|------|------|---------|-------------|
54+
| `num_attempts` | InputPort\<int\> | (required) | Number of attempts. Use `-1` for infinite retries. |
55+
56+
- Returns **SUCCESS** immediately if the child returns SUCCESS (loop is interrupted).
57+
- Returns **FAILURE** after all N attempts are exhausted.
58+
- Returns **RUNNING** if the child returns RUNNING; the attempt counter is **not** incremented and the same iteration resumes on the next tick.
59+
- If the child returns **SKIPPED**, the child is reset and SKIPPED is returned.
4560

46-
Interrupt the loop if the child returns SUCCESS and, in that case, return SUCCESS too.
61+
```xml
62+
<RetryUntilSuccessful num_attempts="3">
63+
<OpenDoor/>
64+
</RetryUntilSuccessful>
65+
```
4766

48-
If the child returns RUNNING, this node returns RUNNING too and the attempts will continue without incrementing on the next tick of the RetryUntilSuccessful node.
67+
:::note
68+
The deprecated name `RetryUntilSuccesful` (single 's') is still supported for backward compatibility but should not be used in new trees.
69+
:::
4970

5071
## KeepRunningUntilFailure
5172

@@ -55,6 +76,35 @@ The KeepRunningUntilFailure node returns always FAILURE (FAILURE in child) or RU
5576

5677
Tick the child after a specified time has passed. The delay is specified as [Input Port](tutorial-basics/tutorial_02_basic_ports.md) `delay_msec`. If the child returns RUNNING, this node returns RUNNING too and will tick the child on next tick of the Delay node. Otherwise, return the status of the child node.
5778

79+
## Timeout
80+
81+
Halt a running child if it has been RUNNING longer than a given duration. This is the opposite of Delay: while Delay waits *before* ticking the child, Timeout interrupts a child that takes *too long*.
82+
83+
| Port | Type | Default | Description |
84+
|------|------|---------|-------------|
85+
| `msec` | InputPort\<unsigned\> | (required) | Timeout duration in milliseconds. |
86+
87+
- If the child completes (SUCCESS or FAILURE) before the timeout, its status is returned.
88+
- If the child is still RUNNING when the timeout expires, it is halted and **FAILURE** is returned.
89+
90+
```xml
91+
<Timeout msec="5000">
92+
<KeepYourBreath/>
93+
</Timeout>
94+
```
95+
96+
:::tip
97+
Combine Timeout with RetryUntilSuccessful for a robust retry-with-timeout pattern:
98+
99+
```xml
100+
<RetryUntilSuccessful num_attempts="3">
101+
<Timeout msec="5000">
102+
<LongRunningAction/>
103+
</Timeout>
104+
</RetryUntilSuccessful>
105+
```
106+
:::
107+
58108
## RunOnce
59109

60110
The RunOnce node is used when you want to execute the child only once.

docs/nodes-library/FallbackNode.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ To understand how the two ControlNodes differ, refer to the following table:
3636
same child is ticked again. Previous siblings, which returned FAILURE already,
3737
are not ticked again.
3838

39-
> Some nodes are not listed yet. See [controls](https://github.com/BehaviorTree/BehaviorTree.CPP/tree/master/include/behaviortree_cpp/controls) on Github for complete definitions.
39+
> For the complete list of built-in nodes, see the other pages in this section and the [source code](https://github.com/BehaviorTree/BehaviorTree.CPP/tree/master/include/behaviortree_cpp) on Github.
4040
4141
## Fallback
4242

docs/nodes-library/ParallelNode.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
sidebar_position: 4
3+
sidebar_label: Parallel Nodes
4+
---
5+
6+
# Parallel Nodes
7+
8+
Parallel nodes execute all their children **concurrently**, but **not** in separate threads.
9+
All children are ticked in sequence within a single tick of the tree, and multiple children
10+
may be in the RUNNING state at the same time.
11+
12+
:::caution
13+
"Parallel" refers to the fact that multiple children can be RUNNING simultaneously.
14+
The children are still ticked sequentially within the same thread.
15+
For actual multi-threaded execution, children must be asynchronous nodes internally.
16+
:::
17+
18+
Currently the framework provides two kinds of nodes:
19+
20+
- Parallel
21+
- ParallelAll
22+
23+
## Parallel
24+
25+
The ParallelNode is the **only** node that can have multiple children RUNNING at the same time.
26+
It is completed when either the SUCCESS or FAILURE threshold is reached. Any remaining
27+
running children are halted.
28+
29+
| Port | Type | Default | Description |
30+
|------|------|---------|-------------|
31+
| `success_count` | InputPort\<int\> | -1 | Number of children that must succeed to return SUCCESS. |
32+
| `failure_count` | InputPort\<int\> | 1 | Number of children that must fail to return FAILURE. |
33+
34+
Threshold values support **Python-style negative indexing**: `-1` is equivalent to the
35+
total number of children. For example, with 4 children, `success_count="-1"` means all 4
36+
must succeed.
37+
38+
**Default behavior** (success_count=-1, failure_count=1): all children must succeed; if any
39+
single child fails, the node returns FAILURE.
40+
41+
```xml
42+
<Parallel success_count="2" failure_count="2">
43+
<ActionA/>
44+
<ActionB/>
45+
<ActionC/>
46+
</Parallel>
47+
```
48+
49+
In this example with 3 children:
50+
- If 2 children return SUCCESS (before 2 fail), the node returns SUCCESS.
51+
- If 2 children return FAILURE (before 2 succeed), the node returns FAILURE.
52+
- Remaining RUNNING children are halted when either threshold is reached.
53+
54+
## ParallelAll
55+
56+
Unlike Parallel, the ParallelAll node **always executes ALL children to completion**.
57+
It never halts children early. This is useful when you need all side effects to complete.
58+
59+
| Port | Type | Default | Description |
60+
|------|------|---------|-------------|
61+
| `max_failures` | InputPort\<int\> | 1 | Maximum number of child failures allowed before returning FAILURE. |
62+
63+
- Returns **SUCCESS** if the number of FAILUREs is **less than** `max_failures`.
64+
- Returns **FAILURE** if the number of FAILUREs **equals or exceeds** `max_failures`.
65+
- Use `max_failures="-1"` (number of children) to always return SUCCESS regardless of child results.
66+
67+
```xml
68+
<ParallelAll max_failures="2">
69+
<ActionA/>
70+
<ActionB/>
71+
<ActionC/>
72+
</ParallelAll>
73+
```
74+
75+
## Parallel vs ParallelAll
76+
77+
| Feature | Parallel | ParallelAll |
78+
|---------|----------|-------------|
79+
| Early termination | Yes (halts children when threshold reached) | No (always runs all children) |
80+
| Success threshold | Configurable (`success_count`) | All non-failed children |
81+
| Failure threshold | Configurable (`failure_count`) | Configurable (`max_failures`) |
82+
| Use case | Race conditions, N-of-M success | All tasks must attempt completion |

docs/nodes-library/SequenceNode.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ To understand how the three ControlNodes differ, refer to the following table:
3434
same child is ticked again. Previous siblings, which returned SUCCESS already,
3535
are not ticked again.
3636

37-
> Some nodes are not listed yet. See [controls](https://github.com/BehaviorTree/BehaviorTree.CPP/tree/master/include/behaviortree_cpp/controls) on Github for complete definitions.
37+
> For the complete list of built-in nodes, see the other pages in this section and the [source code](https://github.com/BehaviorTree/BehaviorTree.CPP/tree/master/include/behaviortree_cpp) on Github.
3838
3939
## Sequence
4040

docs/nodes-library/SwitchNode.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
sidebar_position: 6
3+
sidebar_label: Switch Node
4+
---
5+
6+
# Switch Node
7+
8+
The SwitchNode is equivalent to a `switch` statement: it selects which child to
9+
execute based on the value of a blackboard variable.
10+
11+
Available variants: `Switch2`, `Switch3`, `Switch4`, `Switch5`, `Switch6`, where the
12+
number indicates how many case branches are supported.
13+
14+
A SwitchN node must have exactly **N + 1 children**: N case branches plus one **default**
15+
branch (always the last child).
16+
17+
| Port | Type | Description |
18+
|------|------|-------------|
19+
| `variable` | InputPort\<string\> | The blackboard variable to compare against cases. |
20+
| `case_1` | InputPort\<string\> | Value to match for the 1st child. |
21+
| `case_2` | InputPort\<string\> | Value to match for the 2nd child. |
22+
| ... | ... | Additional cases up to N. |
23+
24+
The `variable` value is compared to each `case_N` string in order. The child
25+
corresponding to the first match is executed. If no case matches, the **last child**
26+
(the default branch) is executed.
27+
28+
Comparison supports strings, integers, and doubles. Enum values registered via
29+
`ScriptingEnumsRegistry` are also supported.
30+
31+
```xml
32+
<Switch3 variable="{robot_state}" case_1="IDLE" case_2="WORKING" case_3="ERROR">
33+
<HandleIdle/> <!-- executed when robot_state == "IDLE" -->
34+
<HandleWorking/> <!-- executed when robot_state == "WORKING" -->
35+
<HandleError/> <!-- executed when robot_state == "ERROR" -->
36+
<HandleUnknownState/> <!-- default: executed when no case matches -->
37+
</Switch3>
38+
```
39+
40+
If a previously matched child is RUNNING and the `variable` value changes on a subsequent
41+
tick, the running child is **halted** before the newly matched child is executed.
42+
43+
:::tip
44+
The same behavior can be achieved using multiple Sequences, Fallbacks, and Conditions,
45+
but Switch is more concise and readable.
46+
:::

docs/tutorial-basics/tutorial_02_basic_ports.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,61 @@ If you are migrating from BT.CPP 3.X, __Script__ is a drop-in replacement
167167
for __SetBlackboard__, which is now discouraged.
168168
:::
169169

170+
## Bidirectional ports
171+
172+
Sometimes a node needs to both **read and modify** a value on the blackboard.
173+
For these cases, use `BidirectionalPort<T>`.
174+
175+
A bidirectional port combines the capabilities of InputPort and OutputPort. This is
176+
particularly useful when a node needs to modify a shared data structure in place
177+
(e.g., appending to a vector).
178+
179+
``` cpp
180+
class PushIntoVector : public SyncActionNode
181+
{
182+
public:
183+
PushIntoVector(const std::string& name, const NodeConfig& config)
184+
: SyncActionNode(name, config)
185+
{ }
186+
187+
static PortsList providedPorts()
188+
{
189+
return { BidirectionalPort<std::vector<int>>("vector"),
190+
InputPort<int>("value") };
191+
}
192+
193+
NodeStatus tick() override
194+
{
195+
const int number = getInput<int>("value").value();
196+
197+
// Use getLockedPortContent() for thread-safe read+write access
198+
if(auto any_ptr = getLockedPortContent("vector"))
199+
{
200+
if(any_ptr->empty())
201+
{
202+
any_ptr.assign(std::vector<int>{ number });
203+
}
204+
else if(auto* vect = any_ptr->castPtr<std::vector<int>>())
205+
{
206+
vect->push_back(number);
207+
}
208+
return NodeStatus::SUCCESS;
209+
}
210+
return NodeStatus::FAILURE;
211+
}
212+
};
213+
```
214+
215+
``` xml
216+
<PushIntoVector vector="{vect}" value="3"/>
217+
<PushIntoVector vector="{vect}" value="5"/>
218+
```
219+
220+
:::tip
221+
Use `getLockedPortContent()` instead of `getInput()`/`setOutput()` when you need
222+
atomic read-modify-write access to a blackboard entry.
223+
:::
224+
170225
## A complete example
171226

172227
In this example, a Sequence of 3 Actions is executed:

0 commit comments

Comments
 (0)