Skip to content

Commit acd4356

Browse files
Merge branch 'master' into feature/remove-lexy
2 parents 847f502 + 061fd8e commit acd4356

26 files changed

Lines changed: 2735 additions & 89 deletions

.github/workflows/cmake_ubuntu.yml

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,18 @@ jobs:
9999
--ignore-errors unused
100100
lcov --list coverage.info
101101
102-
- name: Upload coverage reports to Codecov
103-
uses: codecov/codecov-action@v5
104-
continue-on-error: true
105-
with:
106-
files: coverage.info
107-
flags: unittests
108-
disable_search: true
109-
disable_file_fixes: false
110-
plugins: noop
111-
network_filter: >-
112-
include/behaviortree_cpp/,src/
113-
token: ${{ secrets.CODECOV_TOKEN }}
102+
# - name: Upload coverage reports to Codecov
103+
# uses: codecov/codecov-action@v5
104+
# continue-on-error: true
105+
# with:
106+
# files: coverage.info
107+
# flags: unittests
108+
# disable_search: true
109+
# disable_file_fixes: false
110+
# plugins: noop
111+
# network_filter: >-
112+
# include/behaviortree_cpp/,src/
113+
# token: ${{ secrets.CODECOV_TOKEN }}
114114

115115
# --- Coveralls ---
116116
- name: Upload to Coveralls
@@ -121,14 +121,6 @@ jobs:
121121
format: lcov
122122
github-token: ${{ secrets.GITHUB_TOKEN }}
123123

124-
# --- Codacy ---
125-
- name: Upload to Codacy
126-
uses: codacy/codacy-coverage-reporter-action@v1
127-
continue-on-error: true
128-
with:
129-
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
130-
coverage-reports: coverage.info
131-
132124
# --- SonarCloud ---
133125
- name: Run SonarCloud analysis
134126
uses: SonarSource/sonarcloud-github-action@v5

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ tags
2525
/llvm.sh
2626
t11_groot_howto.btlog
2727
minitrace.json
28+
2829
TODO.md
30+
/.worktrees/*
31+
/docs/plans/*
32+
/coverage_report/*
33+

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![conan Windows](https://github.com/BehaviorTree/BehaviorTree.CPP/actions/workflows/cmake_windows.yml/badge.svg)](https://github.com/BehaviorTree/BehaviorTree.CPP/actions/workflows/cmake_windows.yml)
44
[![ros2](https://github.com/BehaviorTree/BehaviorTree.CPP/actions/workflows/ros2.yaml/badge.svg)](https://github.com/BehaviorTree/BehaviorTree.CPP/actions/workflows/ros2.yaml)
55
[![pixi (Conda)](https://github.com/BehaviorTree/BehaviorTree.CPP/actions/workflows/pixi.yaml/badge.svg)](https://github.com/BehaviorTree/BehaviorTree.CPP/actions/workflows/pixi.yaml)
6-
[![codecov](https://codecov.io/github/BehaviorTree/BehaviorTree.CPP/graph/badge.svg)](https://app.codecov.io/github/BehaviorTree/BehaviorTree.CPP)
6+
[![Coverage Status](https://coveralls.io/repos/github/BehaviorTree/BehaviorTree.CPP/badge.svg?branch=master)](https://coveralls.io/github/BehaviorTree/BehaviorTree.CPP?branch=master)
77

88
# BehaviorTree.CPP 4.8
99

docs/pre_postconditions.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Pre-conditions and Post-conditions
2+
3+
This document describes the pre-condition and post-condition attributes that can be attached to any node in XML.
4+
5+
## Pre-conditions
6+
7+
Pre-conditions are evaluated **before** a node's `tick()` method is called. They can short-circuit the node execution by returning a status immediately.
8+
9+
### Available Pre-conditions
10+
11+
| Attribute | When Evaluated | Behavior |
12+
|-----------|----------------|----------|
13+
| `_failureIf` | IDLE only | If true, return FAILURE without calling tick() |
14+
| `_successIf` | IDLE only | If true, return SUCCESS without calling tick() |
15+
| `_skipIf` | IDLE only | If true, return SKIPPED without calling tick() |
16+
| `_while` | IDLE and RUNNING | If false when IDLE, return SKIPPED. If false when RUNNING, halt node and return SKIPPED |
17+
18+
### Evaluation Order
19+
20+
When a node has multiple pre-conditions, they are evaluated in this order:
21+
1. `_failureIf`
22+
2. `_successIf`
23+
3. `_skipIf`
24+
4. `_while`
25+
26+
The first condition that triggers will determine the result.
27+
28+
### Important: One-time Evaluation
29+
30+
**`_failureIf`, `_successIf`, and `_skipIf` are evaluated only once** when the node transitions from IDLE (or SKIPPED) to another state. They are **NOT re-evaluated** while the node is RUNNING.
31+
32+
This means if you have:
33+
```xml
34+
<MyAction _successIf="condition"/>
35+
```
36+
37+
The `condition` is checked only when `MyAction` starts. If `MyAction` returns RUNNING, subsequent ticks will continue executing `MyAction` without re-checking the condition.
38+
39+
### The `_while` Exception
40+
41+
`_while` is the only pre-condition that is re-evaluated on every tick, even while the node is RUNNING. If the condition becomes false while the node is running, the node is halted and returns SKIPPED.
42+
43+
```xml
44+
<MyAction _while="battery_ok"/>
45+
```
46+
47+
If `battery_ok` becomes false while `MyAction` is running, the action is interrupted.
48+
49+
### When to Use Each Pre-condition
50+
51+
- **`_skipIf`**: Skip a node without failing the parent (useful in Sequences)
52+
- **`_failureIf`**: Fail early based on a condition (useful in Fallbacks)
53+
- **`_successIf`**: Succeed early based on a condition
54+
- **`_while`**: Guard that must remain true for the entire execution
55+
56+
### Re-evaluating Conditions Every Tick
57+
58+
If you need a condition to be checked on every tick (not just when transitioning from IDLE), use the `<Precondition>` decorator node instead of inline attributes:
59+
60+
```xml
61+
<!-- This checks the condition on every tick while child is RUNNING -->
62+
<Precondition if="my_condition" else="RUNNING">
63+
<MyAction/>
64+
</Precondition>
65+
```
66+
67+
With `else="RUNNING"`, if the condition is false, the decorator returns RUNNING (keeping the tree alive) rather than SUCCESS/FAILURE/SKIPPED.
68+
69+
## Post-conditions
70+
71+
Post-conditions are scripts executed **after** a node completes (or is halted).
72+
73+
### Available Post-conditions
74+
75+
| Attribute | When Executed |
76+
|-----------|---------------|
77+
| `_onSuccess` | After node returns SUCCESS |
78+
| `_onFailure` | After node returns FAILURE |
79+
| `_onHalted` | After node is halted (including by `_while`) |
80+
| `_post` | After any completion (SUCCESS, FAILURE, or HALTED) |
81+
82+
### Example
83+
84+
```xml
85+
<MyAction
86+
_onSuccess="result := 'ok'"
87+
_onFailure="result := 'failed'"
88+
_onHalted="result := 'interrupted'"/>
89+
```
90+
91+
## Common Patterns
92+
93+
### Conditional Execution in Sequence
94+
95+
```xml
96+
<Sequence>
97+
<CheckBattery/>
98+
<MoveToGoal _skipIf="already_at_goal"/>
99+
<PickObject/>
100+
</Sequence>
101+
```
102+
103+
If `already_at_goal` is true, `MoveToGoal` is skipped and the sequence continues with `PickObject`.
104+
105+
### Early Exit in Fallback
106+
107+
```xml
108+
<Fallback>
109+
<CachedResult _successIf="cache_valid"/>
110+
<ComputeResult/>
111+
</Fallback>
112+
```
113+
114+
If `cache_valid` is true, `CachedResult` succeeds immediately without executing.
115+
116+
### Guarded Action
117+
118+
```xml
119+
<MoveToGoal _while="battery_level > 20"/>
120+
```
121+
122+
The movement continues only while battery is sufficient. If battery drops, the action is halted.
123+
124+
## Related
125+
126+
- [Port Connection Rules](PORT_CONNECTION_RULES.md) - How ports connect between nodes
127+
- [Name Validation Rules](name_validation_rules.md) - Valid names for ports and nodes

include/behaviortree_cpp/loggers/abstract_logger.h

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ enum class TimestampType
1515
class StatusChangeLogger
1616
{
1717
public:
18+
/// Construct and immediately subscribe to status changes.
1819
StatusChangeLogger(TreeNode* root_node);
20+
1921
virtual ~StatusChangeLogger() = default;
2022

2123
StatusChangeLogger(const StatusChangeLogger& other) = delete;
2224
StatusChangeLogger& operator=(const StatusChangeLogger& other) = delete;
2325

24-
StatusChangeLogger(StatusChangeLogger&& other) = default;
25-
StatusChangeLogger& operator=(StatusChangeLogger&& other) = default;
26+
StatusChangeLogger(StatusChangeLogger&& other) = delete;
27+
StatusChangeLogger& operator=(StatusChangeLogger&& other) = delete;
2628

2729
virtual void callback(BT::Duration timestamp, const TreeNode& node,
2830
NodeStatus prev_status, NodeStatus status) = 0;
@@ -55,6 +57,13 @@ class StatusChangeLogger
5557
show_transition_to_idle_ = enable;
5658
}
5759

60+
protected:
61+
/// Default constructor for deferred subscription. Call subscribeToTreeChanges() when ready.
62+
StatusChangeLogger() = default;
63+
64+
/// Subscribe to status changes. Call at end of constructor for deferred subscription.
65+
void subscribeToTreeChanges(TreeNode* root_node);
66+
5867
private:
5968
bool enabled_ = true;
6069
bool show_transition_to_idle_ = true;
@@ -67,23 +76,34 @@ class StatusChangeLogger
6776
//--------------------------------------------
6877

6978
inline StatusChangeLogger::StatusChangeLogger(TreeNode* root_node)
79+
{
80+
subscribeToTreeChanges(root_node);
81+
}
82+
83+
inline void StatusChangeLogger::subscribeToTreeChanges(TreeNode* root_node)
7084
{
7185
first_timestamp_ = std::chrono::high_resolution_clock::now();
7286

7387
auto subscribeCallback = [this](TimePoint timestamp, const TreeNode& node,
7488
NodeStatus prev, NodeStatus status) {
75-
std::unique_lock lk(callback_mutex_);
76-
if(enabled_ && (status != NodeStatus::IDLE || show_transition_to_idle_))
89+
// Copy state under lock, then release before calling user code
90+
// This prevents recursive mutex locking when multiple nodes change status
91+
bool should_callback = false;
92+
Duration adjusted_timestamp;
7793
{
78-
if(type_ == TimestampType::absolute)
79-
{
80-
this->callback(timestamp.time_since_epoch(), node, prev, status);
81-
}
82-
else
94+
std::unique_lock lk(callback_mutex_);
95+
if(enabled_ && (status != NodeStatus::IDLE || show_transition_to_idle_))
8396
{
84-
this->callback(timestamp - first_timestamp_, node, prev, status);
97+
should_callback = true;
98+
adjusted_timestamp = (type_ == TimestampType::absolute) ?
99+
timestamp.time_since_epoch() :
100+
(timestamp - first_timestamp_);
85101
}
86102
}
103+
if(should_callback)
104+
{
105+
this->callback(adjusted_timestamp, node, prev, status);
106+
}
87107
};
88108

89109
auto visitor = [this, subscribeCallback](TreeNode* node) {

include/behaviortree_cpp/loggers/bt_file_logger_v2.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
#pragma once
22
#include "behaviortree_cpp/loggers/abstract_logger.h"
33

4-
#include <array>
5-
#include <deque>
64
#include <filesystem>
7-
#include <fstream>
5+
#include <memory>
86

97
namespace BT
108
{
9+
1110
/**
1211
* @brief The FileLogger2 is a logger that saves the tree as
1312
* XML and all the transitions.
@@ -36,8 +35,8 @@ class FileLogger2 : public StatusChangeLogger
3635
FileLogger2(const FileLogger2& other) = delete;
3736
FileLogger2& operator=(const FileLogger2& other) = delete;
3837

39-
FileLogger2(FileLogger2&& other) = default;
40-
FileLogger2& operator=(FileLogger2&& other) = default;
38+
FileLogger2(FileLogger2&& other) = delete;
39+
FileLogger2& operator=(FileLogger2&& other) = delete;
4140

4241
virtual ~FileLogger2() override;
4342

@@ -58,8 +57,8 @@ class FileLogger2 : public StatusChangeLogger
5857
void flush() override;
5958

6059
private:
61-
struct PImpl;
62-
std::unique_ptr<PImpl> _p;
60+
struct Pimpl;
61+
std::unique_ptr<Pimpl> _p;
6362

6463
void writerLoop();
6564
};

include/behaviortree_cpp/loggers/bt_sqlite_logger.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class SqliteLogger : public StatusChangeLogger
100100

101101
std::thread writer_thread_;
102102
std::atomic_bool loop_ = true;
103+
std::atomic_bool writer_ready_ = false;
103104

104105
ExtraCallback extra_func_;
105106

include/behaviortree_cpp/tree_node.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,30 @@ struct TreeNodeManifest
4444
using PortsRemapping = std::unordered_map<std::string, std::string>;
4545
using NonPortAttributes = std::unordered_map<std::string, std::string>;
4646

47+
/**
48+
* @brief Pre-conditions that can be attached to any node via XML attributes.
49+
*
50+
* Pre-conditions are evaluated in the order defined by this enum (FAILURE_IF first,
51+
* then SUCCESS_IF, then SKIP_IF, then WHILE_TRUE).
52+
*
53+
* **Important**: FAILURE_IF, SUCCESS_IF, and SKIP_IF are evaluated **only once**
54+
* when the node transitions from IDLE (or SKIPPED) to another state.
55+
* They are NOT re-evaluated while the node is RUNNING.
56+
*
57+
* - `_failureIf="<script>"`: If true when node is IDLE, return FAILURE immediately (node's tick() is not called).
58+
* - `_successIf="<script>"`: If true when node is IDLE, return SUCCESS immediately (node's tick() is not called).
59+
* - `_skipIf="<script>"`: If true when node is IDLE, return SKIPPED immediately (node's tick() is not called).
60+
* - `_while="<script>"`: Checked both on IDLE and RUNNING states.
61+
*
62+
* If false when IDLE, return SKIPPED. If false when RUNNING, halt the node
63+
* and return SKIPPED. This is the only pre-condition that can interrupt
64+
* a running node.
65+
*
66+
* If you need a condition to be re-evaluated on every tick, use the
67+
* `<Precondition>` decorator node with `else="RUNNING"` instead of these attributes.
68+
*/
4769
enum class PreCond
4870
{
49-
// order of the enums also tell us the execution order
5071
FAILURE_IF = 0,
5172
SUCCESS_IF,
5273
SKIP_IF,

src/basic_types.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ std::string convertFromString<std::string>(StringView str)
122122
template <>
123123
int64_t convertFromString<int64_t>(StringView str)
124124
{
125-
long result = 0;
125+
int64_t result = 0;
126126
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
127127
std::ignore = ptr;
128128
if(ec != std::errc())
@@ -135,7 +135,7 @@ int64_t convertFromString<int64_t>(StringView str)
135135
template <>
136136
uint64_t convertFromString<uint64_t>(StringView str)
137137
{
138-
unsigned long result = 0;
138+
uint64_t result = 0;
139139
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
140140
std::ignore = ptr;
141141
if(ec != std::errc())

0 commit comments

Comments
 (0)