• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

BehaviorTree / BehaviorTree.CPP / 21626879657

03 Feb 2026 10:37AM UTC coverage: 69.604%. Remained the same
21626879657

push

github

facontidavide
Merge branch 'master' of github.com:BehaviorTree/BehaviorTree.CPP

5 of 10 new or added lines in 1 file covered. (50.0%)

172 existing lines in 3 files now uncovered.

3760 of 5402 relevant lines covered (69.6%)

24260.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.39
/src/tree_node.cpp
1
/* Copyright (C) 2015-2018 Michele Colledanchise -  All Rights Reserved
2
 * Copyright (C) 2018-2025 Davide Faconti, Eurecat -  All Rights Reserved
3
*
4
*   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
5
*   to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
6
*   and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
*   The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
*
9
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10
*   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
11
*   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
*/
13

14
#include "behaviortree_cpp/tree_node.h"
15

16
#include <array>
17
#include <cstring>
18

19
namespace BT
20
{
21

22
struct TreeNode::PImpl
23
{
24
  PImpl(std::string name, NodeConfig config)
961✔
25
    : name(std::move(name)), config(std::move(config))
961✔
26
  {}
961✔
27

28
  const std::string name;
29

30
  NodeStatus status = NodeStatus::IDLE;
31

32
  std::condition_variable state_condition_variable;
33

34
  mutable std::mutex state_mutex;
35

36
  StatusChangeSignal state_change_signal;
37

38
  NodeConfig config;
39

40
  std::string registration_ID;
41

42
  PreTickCallback pre_tick_callback;
43
  PostTickCallback post_tick_callback;
44
  TickMonitorCallback tick_monitor_callback;
45

46
  std::mutex callback_injection_mutex;
47

48
  std::shared_ptr<WakeUpSignal> wake_up;
49

50
  std::array<ScriptFunction, size_t(PreCond::COUNT_)> pre_parsed;
51
  std::array<ScriptFunction, size_t(PostCond::COUNT_)> post_parsed;
52
};
53

54
TreeNode::TreeNode(std::string name, NodeConfig config)
961✔
55
  : _p(new PImpl(std::move(name), std::move(config)))
961✔
56
{}
961✔
57

58
TreeNode::TreeNode(TreeNode&& other) noexcept : _p(std::move(other._p))
×
59
{}
×
60

61
TreeNode& TreeNode::operator=(TreeNode&& other) noexcept
×
62
{
63
  this->_p = std::move(other._p);
×
64
  return *this;
×
65
}
66

67
TreeNode::~TreeNode() = default;
961✔
68

69
NodeStatus TreeNode::executeTick()
603,722✔
70
{
71
  auto new_status = _p->status;
603,722✔
72
  PreTickCallback pre_tick;
603,722✔
73
  PostTickCallback post_tick;
603,722✔
74
  TickMonitorCallback monitor_tick;
603,722✔
75
  {
76
    const std::scoped_lock lk(_p->callback_injection_mutex);
603,722✔
77
    pre_tick = _p->pre_tick_callback;
603,722✔
78
    post_tick = _p->post_tick_callback;
603,722✔
79
    monitor_tick = _p->tick_monitor_callback;
603,722✔
80
  }
603,722✔
81

82
  // a pre-condition may return the new status.
83
  // In this case it override the actual tick()
84
  if(auto precond = checkPreConditions())
603,722✔
85
  {
86
    new_status = precond.value();
71✔
87
  }
88
  else
89
  {
90
    // injected pre-callback
91
    bool substituted = false;
603,651✔
92
    if(pre_tick && !isStatusCompleted(_p->status))
603,651✔
93
    {
94
      auto override_status = pre_tick(*this);
22✔
95
      if(isStatusCompleted(override_status))
22✔
96
      {
97
        // don't execute the actual tick()
98
        substituted = true;
22✔
99
        new_status = override_status;
22✔
100
      }
101
    }
102

103
    // Call the ACTUAL tick
104
    if(!substituted)
603,651✔
105
    {
106
      using namespace std::chrono;
107
      const auto t1 = steady_clock::now();
603,629✔
108
      new_status = tick();
603,629✔
109
      const auto t2 = steady_clock::now();
603,605✔
110
      if(monitor_tick)
603,605✔
111
      {
112
        monitor_tick(*this, new_status, duration_cast<microseconds>(t2 - t1));
×
113
      }
114
    }
115
  }
603,718✔
116

117
  // injected post callback
118
  if(isStatusCompleted(new_status))
603,698✔
119
  {
120
    checkPostConditions(new_status);
600,215✔
121
  }
122

123
  if(post_tick)
603,698✔
124
  {
125
    auto override_status = post_tick(*this, new_status);
×
126
    if(isStatusCompleted(override_status))
×
127
    {
128
      new_status = override_status;
×
129
    }
130
  }
131

132
  // preserve the IDLE state if skipped, but communicate SKIPPED to parent
133
  if(new_status != NodeStatus::SKIPPED)
603,698✔
134
  {
135
    setStatus(new_status);
603,618✔
136
  }
137
  return new_status;
603,698✔
138
}
603,758✔
139

140
void TreeNode::haltNode()
891✔
141
{
142
  halt();
891✔
143

144
  const auto& parse_executor = _p->post_parsed[size_t(PostCond::ON_HALTED)];
891✔
145
  if(parse_executor)
891✔
146
  {
147
    Ast::Environment env = { config().blackboard, config().enums };
6✔
148
    parse_executor(env);
6✔
149
  }
6✔
150
}
891✔
151

152
void TreeNode::setStatus(NodeStatus new_status)
608,932✔
153
{
154
  if(new_status == NodeStatus::IDLE)
608,932✔
155
  {
156
    throw RuntimeError("Node [", name(),
×
157
                       "]: you are not allowed to set manually the status to IDLE. "
158
                       "If you know what you are doing (?) use resetStatus() instead.");
×
159
  }
160

161
  NodeStatus prev_status = NodeStatus::IDLE;
608,932✔
162
  {
163
    const std::unique_lock<std::mutex> UniqueLock(_p->state_mutex);
608,932✔
164
    prev_status = _p->status;
608,932✔
165
    _p->status = new_status;
608,932✔
166
  }
608,932✔
167
  if(prev_status != new_status)
608,932✔
168
  {
169
    _p->state_condition_variable.notify_all();
602,008✔
170
    _p->state_change_signal.notify(std::chrono::high_resolution_clock::now(), *this,
602,008✔
171
                                   prev_status, new_status);
172
  }
173
}
608,932✔
174

175
TreeNode::PreScripts& TreeNode::preConditionsScripts()
701✔
176
{
177
  return _p->pre_parsed;
701✔
178
}
179

180
TreeNode::PostScripts& TreeNode::postConditionsScripts()
701✔
181
{
182
  return _p->post_parsed;
701✔
183
}
184

185
Expected<NodeStatus> TreeNode::checkPreConditions()
603,722✔
186
{
187
  Ast::Environment env = { config().blackboard, config().enums };
603,722✔
188

189
  // Check pre-conditions in order: FAILURE_IF, SUCCESS_IF, SKIP_IF, WHILE_TRUE.
190
  // IMPORTANT: _failureIf, _successIf, and _skipIf are evaluated ONLY when the
191
  // node is IDLE or SKIPPED. They are NOT re-evaluated while the node is RUNNING.
192
  for(size_t index = 0; index < size_t(PreCond::COUNT_); index++)
3,018,471✔
193
  {
194
    const auto& parse_executor = _p->pre_parsed[index];
2,414,820✔
195
    if(!parse_executor)
2,414,820✔
196
    {
197
      continue;
2,414,561✔
198
    }
199

200
    const auto preID = static_cast<PreCond>(index);
259✔
201

202
    // _failureIf, _successIf, _skipIf: only checked when IDLE or SKIPPED
203
    // _while: checked here AND also when RUNNING (see below)
204
    if(_p->status == NodeStatus::IDLE || _p->status == NodeStatus::SKIPPED)
259✔
205
    {
206
      // what to do if the condition is true
207
      if(parse_executor(env).cast<bool>())
114✔
208
      {
209
        if(preID == PreCond::FAILURE_IF)
67✔
210
        {
211
          return NodeStatus::FAILURE;
3✔
212
        }
213
        if(preID == PreCond::SUCCESS_IF)
64✔
214
        {
215
          return NodeStatus::SUCCESS;
1✔
216
        }
217
        if(preID == PreCond::SKIP_IF)
63✔
218
        {
219
          return NodeStatus::SKIPPED;
57✔
220
        }
221
      }
222
      // if the conditions is false
223
      else if(preID == PreCond::WHILE_TRUE)
47✔
224
      {
225
        return NodeStatus::SKIPPED;
5✔
226
      }
227
    }
228
    else if(_p->status == NodeStatus::RUNNING && preID == PreCond::WHILE_TRUE)
145✔
229
    {
230
      // _while is the ONLY precondition checked while RUNNING.
231
      // If the condition becomes false, halt the node and return SKIPPED.
232
      if(!parse_executor(env).cast<bool>())
143✔
233
      {
234
        haltNode();
5✔
235
        return NodeStatus::SKIPPED;
5✔
236
      }
237
    }
238
  }
239
  return nonstd::make_unexpected("");  // no precondition
603,651✔
240
}
603,722✔
241

242
void TreeNode::checkPostConditions(NodeStatus status)
600,215✔
243
{
244
  auto ExecuteScript = [this](const PostCond& cond) {
1,200,430✔
245
    const auto& parse_executor = _p->post_parsed[size_t(cond)];
1,200,430✔
246
    if(parse_executor)
1,200,430✔
247
    {
248
      Ast::Environment env = { config().blackboard, config().enums };
17✔
249
      parse_executor(env);
17✔
250
    }
17✔
251
  };
1,200,430✔
252

253
  if(status == NodeStatus::SUCCESS)
600,215✔
254
  {
255
    ExecuteScript(PostCond::ON_SUCCESS);
767✔
256
  }
257
  else if(status == NodeStatus::FAILURE)
599,448✔
258
  {
259
    ExecuteScript(PostCond::ON_FAILURE);
599,448✔
260
  }
261
  ExecuteScript(PostCond::ALWAYS);
600,215✔
262
}
600,215✔
263

264
void TreeNode::resetStatus()
601,755✔
265
{
266
  NodeStatus prev_status = NodeStatus::IDLE;
601,755✔
267
  {
268
    const std::unique_lock<std::mutex> lock(_p->state_mutex);
601,755✔
269
    prev_status = _p->status;
601,755✔
270
    _p->status = NodeStatus::IDLE;
601,755✔
271
  }
601,755✔
272

273
  if(prev_status != NodeStatus::IDLE)
601,755✔
274
  {
275
    _p->state_condition_variable.notify_all();
599,664✔
276
    _p->state_change_signal.notify(std::chrono::high_resolution_clock::now(), *this,
599,664✔
277
                                   prev_status, NodeStatus::IDLE);
278
  }
279
}
601,755✔
280

281
NodeStatus TreeNode::status() const
1,208,446✔
282
{
283
  const std::lock_guard<std::mutex> lock(_p->state_mutex);
1,208,446✔
284
  return _p->status;
2,416,892✔
285
}
1,208,446✔
286

287
NodeStatus TreeNode::waitValidStatus()
×
288
{
289
  std::unique_lock<std::mutex> lock(_p->state_mutex);
×
290

291
  while(isHalted())
×
292
  {
UNCOV
293
    _p->state_condition_variable.wait(lock);
×
294
  }
UNCOV
295
  return _p->status;
×
UNCOV
296
}
×
297

298
const std::string& TreeNode::name() const
82✔
299
{
300
  return _p->name;
82✔
301
}
302

UNCOV
303
bool TreeNode::isHalted() const
×
304
{
UNCOV
305
  return _p->status == NodeStatus::IDLE;
×
306
}
307

308
TreeNode::StatusChangeSubscriber
309
TreeNode::subscribeToStatusChange(TreeNode::StatusChangeCallback callback)
43✔
310
{
311
  return _p->state_change_signal.subscribe(std::move(callback));
43✔
312
}
313

314
void TreeNode::setPreTickFunction(PreTickCallback callback)
2✔
315
{
316
  const std::unique_lock lk(_p->callback_injection_mutex);
2✔
317
  _p->pre_tick_callback = std::move(callback);
2✔
318
}
2✔
319

320
void TreeNode::setPostTickFunction(PostTickCallback callback)
×
321
{
322
  const std::unique_lock lk(_p->callback_injection_mutex);
×
UNCOV
323
  _p->post_tick_callback = std::move(callback);
×
324
}
×
325

326
void TreeNode::setTickMonitorCallback(TickMonitorCallback callback)
×
327
{
UNCOV
328
  const std::unique_lock lk(_p->callback_injection_mutex);
×
UNCOV
329
  _p->tick_monitor_callback = std::move(callback);
×
UNCOV
330
}
×
331

332
uint16_t TreeNode::UID() const
343✔
333
{
334
  return _p->config.uid;
343✔
335
}
336

337
const std::string& TreeNode::fullPath() const
598,088✔
338
{
339
  return _p->config.path;
598,088✔
340
}
341

342
const std::string& TreeNode::registrationName() const
12✔
343
{
344
  return _p->registration_ID;
12✔
345
}
346

347
const NodeConfig& TreeNode::config() const
1,797,330✔
348
{
349
  return _p->config;
1,797,330✔
350
}
351

352
NodeConfig& TreeNode::config()
1,210,350✔
353
{
354
  return _p->config;
1,210,350✔
355
}
356

357
StringView TreeNode::getRawPortValue(const std::string& key) const
11✔
358
{
359
  auto remap_it = _p->config.input_ports.find(key);
11✔
360
  if(remap_it == _p->config.input_ports.end())
11✔
361
  {
UNCOV
362
    remap_it = _p->config.output_ports.find(key);
×
UNCOV
363
    if(remap_it == _p->config.output_ports.end())
×
364
    {
UNCOV
365
      throw std::logic_error(StrCat("[", key, "] not found"));
×
366
    }
367
  }
368
  return remap_it->second;
22✔
369
}
370

371
bool TreeNode::isBlackboardPointer(StringView str, StringView* stripped_pointer)
2,047✔
372
{
373
  if(str.size() < 3)
2,047✔
374
  {
375
    return false;
1,146✔
376
  }
377
  // strip leading and following spaces
378
  size_t front_index = 0;
901✔
379
  size_t last_index = str.size() - 1;
901✔
380
  while(str[front_index] == ' ' && front_index <= last_index)
1,072✔
381
  {
382
    front_index++;
171✔
383
  }
384
  while(str[last_index] == ' ' && front_index <= last_index)
1,092✔
385
  {
386
    last_index--;
191✔
387
  }
388
  const auto size = (last_index - front_index) + 1;
901✔
389
  auto valid = size >= 3 && str[front_index] == '{' && str[last_index] == '}';
901✔
390
  if(valid && stripped_pointer != nullptr)
901✔
391
  {
392
    *stripped_pointer = StringView(&str[front_index + 1], size - 2);
292✔
393
  }
394
  return valid;
901✔
395
}
396

397
StringView TreeNode::stripBlackboardPointer(StringView str)
66✔
398
{
399
  StringView out;
66✔
400
  if(isBlackboardPointer(str, &out))
66✔
401
  {
402
    return out;
66✔
403
  }
UNCOV
404
  return {};
×
405
}
406

407
Expected<StringView> TreeNode::getRemappedKey(StringView port_name,
1,871✔
408
                                              StringView remapped_port)
409
{
410
  if(remapped_port == "{=}" || remapped_port == "=")
1,871✔
411
  {
412
    return { port_name };
4✔
413
  }
414
  StringView stripped;
1,867✔
415
  if(isBlackboardPointer(remapped_port, &stripped))
1,867✔
416
  {
417
    return { stripped };
219✔
418
  }
419
  return nonstd::make_unexpected("Not a blackboard pointer");
1,648✔
420
}
421

422
void TreeNode::emitWakeUpSignal()
247✔
423
{
424
  if(_p->wake_up)
247✔
425
  {
426
    _p->wake_up->emitSignal();
183✔
427
  }
428
}
247✔
429

430
bool TreeNode::requiresWakeUp() const
598,183✔
431
{
432
  return bool(_p->wake_up);
598,183✔
433
}
434

435
void TreeNode::setRegistrationID(StringView ID)
1,219✔
436
{
437
  _p->registration_ID.assign(ID.data(), ID.size());
1,219✔
438
}
1,219✔
439

440
void TreeNode::setWakeUpInstance(std::shared_ptr<WakeUpSignal> instance)
671✔
441
{
442
  _p->wake_up = instance;
671✔
443
}
671✔
444

445
void TreeNode::modifyPortsRemapping(const PortsRemapping& new_remapping)
×
446
{
UNCOV
447
  for(const auto& new_it : new_remapping)
×
448
  {
UNCOV
449
    auto it = _p->config.input_ports.find(new_it.first);
×
450
    if(it != _p->config.input_ports.end())
×
451
    {
UNCOV
452
      it->second = new_it.second;
×
453
    }
UNCOV
454
    it = _p->config.output_ports.find(new_it.first);
×
UNCOV
455
    if(it != _p->config.output_ports.end())
×
456
    {
UNCOV
457
      it->second = new_it.second;
×
458
    }
459
  }
UNCOV
460
}
×
461

462
template <>
463
std::string toStr<PreCond>(const PreCond& cond)
2,816✔
464
{
465
  if(cond < PreCond::COUNT_)
2,816✔
466
  {
467
    return BT::PreCondNames[static_cast<size_t>(cond)];
2,816✔
468
  }
UNCOV
469
  return "Undefined";
×
470
}
471

472
template <>
473
std::string toStr<PostCond>(const PostCond& cond)
2,816✔
474
{
475
  if(cond < BT::PostCond::COUNT_)
2,816✔
476
  {
477
    return BT::PostCondNames[static_cast<size_t>(cond)];
2,816✔
478
  }
UNCOV
479
  return "Undefined";
×
480
}
481

482
AnyPtrLocked BT::TreeNode::getLockedPortContent(const std::string& key)
9✔
483
{
484
  if(auto remapped_key = getRemappedKey(key, getRawPortValue(key)))
9✔
485
  {
486
    const auto bb_key = std::string(*remapped_key);
9✔
487
    auto result = _p->config.blackboard->getAnyLocked(bb_key);
9✔
488
    if(!result && _p->config.manifest != nullptr)
9✔
489
    {
490
      // Entry doesn't exist yet. Create it using the port's type info
491
      // from the manifest so that getLockedPortContent works even when
492
      // the port is not explicitly declared in XML. Issue #942.
493
      auto port_it = _p->config.manifest->ports.find(key);
1✔
494
      if(port_it != _p->config.manifest->ports.end())
1✔
495
      {
496
        _p->config.blackboard->createEntry(bb_key, port_it->second);
1✔
497
        result = _p->config.blackboard->getAnyLocked(bb_key);
1✔
498
      }
499
    }
500
    return result;
9✔
501
  }
18✔
UNCOV
502
  return {};
×
503
}
504

505
}  // namespace BT
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc