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

BehaviorTree / BehaviorTree.CPP / 21668366960

04 Feb 2026 10:43AM UTC coverage: 79.549% (-0.04%) from 79.593%
21668366960

Pull #1105

github

web-flow
Merge 5e085e561 into f4eed9c0d
Pull Request #1105: Fix #880: createTreeFromText now finds previously registered subtrees

53 of 68 new or added lines in 1 file covered. (77.94%)

6 existing lines in 2 files now uncovered.

4695 of 5902 relevant lines covered (79.55%)

10379.53 hits per line

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

75.98
/src/bt_factory.cpp
1
/*  Copyright (C) 2018-2025 Davide Faconti, Eurecat -  All Rights Reserved
2
*
3
*   Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
4
*   to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
5
*   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:
6
*   The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
*
8
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9
*   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,
10
*   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.
11
*/
12

13
#include "behaviortree_cpp/bt_factory.h"
14

15
#include "tinyxml2.h"
16

17
#include "behaviortree_cpp/utils/shared_library.h"
18
#include "behaviortree_cpp/utils/wildcards.hpp"
19
#include "behaviortree_cpp/xml_parsing.h"
20

21
#include <filesystem>
22

23
namespace BT
24
{
25

26
// Extract the main tree ID from an XML root element.
27
// Checks main_tree_to_execute attribute first, then falls back to the
28
// single BehaviorTree ID if only one is defined.
29
static std::string detectMainTreeId(const tinyxml2::XMLElement* xml_root)
222✔
30
{
31
  if(auto attr = xml_root->Attribute("main_tree_to_execute"))
222✔
32
  {
33
    return attr;
56✔
34
  }
35
  int bt_count = 0;
194✔
36
  std::string single_id;
194✔
37
  for(auto* bt = xml_root->FirstChildElement("BehaviorTree"); bt != nullptr;
391✔
38
      bt = bt->NextSiblingElement("BehaviorTree"))
197✔
39
  {
40
    bt_count++;
197✔
41
    if(auto* id = bt->Attribute("ID"))
197✔
42
    {
43
      single_id = id;
85✔
44
    }
45
  }
46
  if(bt_count == 1 && !single_id.empty())
194✔
47
  {
48
    return single_id;
81✔
49
  }
50
  return {};
113✔
51
}
194✔
52

UNCOV
53
bool WildcardMatch(std::string const& str, StringView filter)
×
54
{
55
  return wildcards_match(str, { filter.data(), filter.size() });
×
56
}
57

58
struct BehaviorTreeFactory::PImpl
59
{
60
  std::unordered_map<std::string, NodeBuilder> builders;
61
  std::unordered_map<std::string, TreeNodeManifest> manifests;
62
  std::set<std::string> builtin_IDs;
63
  std::unordered_map<std::string, Any> behavior_tree_definitions;
64
  std::shared_ptr<std::unordered_map<std::string, int>> scripting_enums;
65
  std::shared_ptr<BT::Parser> parser;
66
  std::unordered_map<std::string, SubstitutionRule> substitution_rules;
67
};
68

69
BehaviorTreeFactory::BehaviorTreeFactory() : _p(new PImpl)
288✔
70
{
71
  _p->parser = std::make_shared<XMLParser>(*this);
288✔
72
  registerNodeType<FallbackNode>("Fallback");
576✔
73
  registerNodeType<FallbackNode>("AsyncFallback", true);
576✔
74
  registerNodeType<SequenceNode>("Sequence");
576✔
75
  registerNodeType<SequenceNode>("AsyncSequence", true);
576✔
76
  registerNodeType<SequenceWithMemory>("SequenceWithMemory");
576✔
77

78
#ifdef USE_BTCPP3_OLD_NAMES
79
  registerNodeType<SequenceWithMemory>("SequenceStar");  // backward compatibility
80
#endif
81

82
  registerNodeType<ParallelNode>("Parallel");
576✔
83
  registerNodeType<ParallelAllNode>("ParallelAll");
576✔
84
  registerNodeType<ReactiveSequence>("ReactiveSequence");
576✔
85
  registerNodeType<ReactiveFallback>("ReactiveFallback");
576✔
86
  registerNodeType<IfThenElseNode>("IfThenElse");
576✔
87
  registerNodeType<WhileDoElseNode>("WhileDoElse");
576✔
88

89
  registerNodeType<InverterNode>("Inverter");
576✔
90

91
  registerNodeType<RetryNode>("RetryUntilSuccessful");
576✔
92
  registerNodeType<KeepRunningUntilFailureNode>("KeepRunningUntilFailure");
576✔
93
  registerNodeType<RepeatNode>("Repeat");
576✔
94
  registerNodeType<TimeoutNode>("Timeout");
576✔
95
  registerNodeType<DelayNode>("Delay");
576✔
96
  registerNodeType<RunOnceNode>("RunOnce");
576✔
97

98
  registerNodeType<ForceSuccessNode>("ForceSuccess");
576✔
99
  registerNodeType<ForceFailureNode>("ForceFailure");
576✔
100

101
  registerNodeType<AlwaysSuccessNode>("AlwaysSuccess");
576✔
102
  registerNodeType<AlwaysFailureNode>("AlwaysFailure");
576✔
103
  registerNodeType<ScriptNode>("Script");
576✔
104
  registerNodeType<ScriptCondition>("ScriptCondition");
576✔
105
  registerNodeType<SetBlackboardNode>("SetBlackboard");
576✔
106
  registerNodeType<SleepNode>("Sleep");
576✔
107
  registerNodeType<UnsetBlackboardNode>("UnsetBlackboard");
576✔
108

109
  registerNodeType<SubTreeNode>("SubTree");
576✔
110

111
  registerNodeType<PreconditionNode>("Precondition");
576✔
112

113
  registerNodeType<SwitchNode<2>>("Switch2");
576✔
114
  registerNodeType<SwitchNode<3>>("Switch3");
576✔
115
  registerNodeType<SwitchNode<4>>("Switch4");
576✔
116
  registerNodeType<SwitchNode<5>>("Switch5");
576✔
117
  registerNodeType<SwitchNode<6>>("Switch6");
576✔
118

119
  registerNodeType<LoopNode<int>>("LoopInt");
576✔
120
  registerNodeType<LoopNode<bool>>("LoopBool");
576✔
121
  registerNodeType<LoopNode<double>>("LoopDouble");
576✔
122
  registerNodeType<LoopNode<std::string>>("LoopString");
576✔
123

124
  registerNodeType<EntryUpdatedAction>("WasEntryUpdated");
576✔
125
  registerNodeType<EntryUpdatedDecorator>("SkipUnlessUpdated", NodeStatus::SKIPPED);
576✔
126
  registerNodeType<EntryUpdatedDecorator>("WaitValueUpdate", NodeStatus::RUNNING);
288✔
127

128
  for(const auto& it : _p->builders)
12,096✔
129
  {
130
    _p->builtin_IDs.insert(it.first);
11,808✔
131
  }
132

133
  _p->scripting_enums = std::make_shared<std::unordered_map<std::string, int>>();
288✔
134
}
288✔
135

136
BehaviorTreeFactory::~BehaviorTreeFactory() = default;
288✔
137

138
BehaviorTreeFactory::BehaviorTreeFactory(BehaviorTreeFactory&& other) noexcept = default;
×
139
BehaviorTreeFactory&
140
BehaviorTreeFactory::operator=(BehaviorTreeFactory&& other) noexcept = default;
1✔
141

142
bool BehaviorTreeFactory::unregisterBuilder(const std::string& ID)
×
143
{
144
  if(builtinNodes().count(ID) != 0)
×
145
  {
146
    throw LogicError("You can not remove the builtin registration ID [", ID, "]");
×
147
  }
148
  auto it = _p->builders.find(ID);
×
149
  if(it == _p->builders.end())
×
150
  {
151
    return false;
×
152
  }
153
  _p->builders.erase(ID);
×
154
  _p->manifests.erase(ID);
×
155
  return true;
×
156
}
157

158
void BehaviorTreeFactory::registerBuilder(const TreeNodeManifest& manifest,
12,122✔
159
                                          const NodeBuilder& builder)
160
{
161
  auto it = _p->builders.find(manifest.registration_ID);
12,122✔
162
  if(it != _p->builders.end())
12,122✔
163
  {
164
    throw BehaviorTreeException("ID [", manifest.registration_ID, "] already registered");
×
165
  }
166

167
  _p->builders.insert({ manifest.registration_ID, builder });
12,122✔
168
  _p->manifests.insert({ manifest.registration_ID, manifest });
12,122✔
169
}
12,122✔
170

171
void BehaviorTreeFactory::registerSimpleCondition(
11✔
172
    const std::string& ID, const SimpleConditionNode::TickFunctor& tick_functor,
173
    PortsList ports)
174
{
175
  const NodeBuilder builder = [tick_functor, ID](const std::string& name,
22✔
176
                                                 const NodeConfig& config) {
177
    return std::make_unique<SimpleConditionNode>(name, tick_functor, config);
10✔
178
  };
11✔
179

180
  const TreeNodeManifest manifest = { NodeType::CONDITION, ID, std::move(ports), {} };
11✔
181
  registerBuilder(manifest, builder);
11✔
182
}
11✔
183

184
void BehaviorTreeFactory::registerSimpleAction(
185✔
185
    const std::string& ID, const SimpleActionNode::TickFunctor& tick_functor,
186
    PortsList ports)
187
{
188
  const NodeBuilder builder = [tick_functor, ID](const std::string& name,
370✔
189
                                                 const NodeConfig& config) {
190
    return std::make_unique<SimpleActionNode>(name, tick_functor, config);
123✔
191
  };
185✔
192

193
  const TreeNodeManifest manifest = { NodeType::ACTION, ID, std::move(ports), {} };
185✔
194
  registerBuilder(manifest, builder);
185✔
195
}
185✔
196

197
void BehaviorTreeFactory::registerSimpleDecorator(
×
198
    const std::string& ID, const SimpleDecoratorNode::TickFunctor& tick_functor,
199
    PortsList ports)
200
{
201
  const NodeBuilder builder = [tick_functor, ID](const std::string& name,
×
202
                                                 const NodeConfig& config) {
203
    return std::make_unique<SimpleDecoratorNode>(name, tick_functor, config);
×
204
  };
×
205

206
  const TreeNodeManifest manifest = { NodeType::DECORATOR, ID, std::move(ports), {} };
×
207
  registerBuilder(manifest, builder);
×
208
}
×
209

210
void BehaviorTreeFactory::registerFromPlugin(const std::string& file_path)
3✔
211
{
212
  BT::SharedLibrary loader;
3✔
213
  loader.load(file_path);
3✔
214
  using Func = void (*)(BehaviorTreeFactory&);
215

216
  if(loader.hasSymbol(PLUGIN_SYMBOL))
6✔
217
  {
218
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
219
    auto* func = reinterpret_cast<Func>(loader.getSymbol(PLUGIN_SYMBOL));
3✔
220
    func(*this);
3✔
221
  }
222
  else
223
  {
224
    std::cout << "ERROR loading library [" << file_path << "]: can't find symbol ["
225
              << PLUGIN_SYMBOL << "]" << std::endl;
×
226
  }
227
}
3✔
228

229
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
230
void BehaviorTreeFactory::registerFromROSPlugins()
×
231
{
232
  throw RuntimeError("Using attribute [ros_pkg] in <include>, but this library was "
233
                     "compiled without ROS support. Recompile the BehaviorTree.CPP "
234
                     "using catkin");
×
235
}
236

237
void BehaviorTreeFactory::registerBehaviorTreeFromFile(
×
238
    const std::filesystem::path& filename)
239
{
240
  _p->parser->loadFromFile(filename);
×
241
}
×
242

243
void BehaviorTreeFactory::registerBehaviorTreeFromText(const std::string& xml_text)
50✔
244
{
245
  _p->parser->loadFromText(xml_text);
50✔
246
}
49✔
247

248
std::vector<std::string> BehaviorTreeFactory::registeredBehaviorTrees() const
1✔
249
{
250
  return _p->parser->registeredBehaviorTrees();
1✔
251
}
252

253
void BehaviorTreeFactory::clearRegisteredBehaviorTrees()
×
254
{
255
  _p->parser->clearInternalState();
×
256
}
×
257

258
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
259
std::unique_ptr<TreeNode> BehaviorTreeFactory::instantiateTreeNode(
926✔
260
    const std::string& name, const std::string& ID, const NodeConfig& config) const
261
{
262
  auto idNotFound = [this, ID] {
×
263
    std::cerr << ID << " not included in this list:" << std::endl;
×
264
    for(const auto& builder_it : _p->builders)
×
265
    {
266
      std::cerr << builder_it.first << std::endl;
×
267
    }
268
    throw RuntimeError("BehaviorTreeFactory: ID [", ID, "] not registered");
×
269
  };
926✔
270

271
  auto it_manifest = _p->manifests.find(ID);
926✔
272
  if(it_manifest == _p->manifests.end())
926✔
273
  {
274
    idNotFound();
×
275
  }
276

277
  std::unique_ptr<TreeNode> node;
926✔
278

279
  bool substituted = false;
926✔
280
  for(const auto& [filter, rule] : _p->substitution_rules)
936✔
281
  {
282
    if(filter == name || filter == ID || wildcards_match(config.path, filter))
16✔
283
    {
284
      // first case: the rule is simply a string with the name of the
285
      // node to create instead
286
      if(const auto* const substituted_ID = std::get_if<std::string>(&rule))
6✔
287
      {
288
        auto it_builder = _p->builders.find(*substituted_ID);
4✔
289
        if(it_builder != _p->builders.end())
4✔
290
        {
291
          auto& builder = it_builder->second;
4✔
292
          node = builder(name, config);
4✔
293
        }
294
        else
295
        {
296
          throw RuntimeError("Substituted Node ID [", *substituted_ID, "] not found");
×
297
        }
298
        substituted = true;
4✔
299
        break;
4✔
300
      }
301

302
      if(const auto* const test_config = std::get_if<TestNodeConfig>(&rule))
2✔
303
      {
304
        node = std::make_unique<TestNode>(name, config,
4✔
305
                                          std::make_shared<TestNodeConfig>(*test_config));
6✔
306
        substituted = true;
2✔
307
        break;
2✔
308
      }
309

310
      if(const auto* const test_config =
×
311
             std::get_if<std::shared_ptr<TestNodeConfig>>(&rule))
×
312
      {
313
        node = std::make_unique<TestNode>(name, config, *test_config);
×
314
        substituted = true;
×
315
        break;
×
316
      }
317
      throw LogicError("Substitution rule is not a string or a TestNodeConfig");
×
318
    }
319
  }
320

321
  // No substitution rule applied: default behavior
322
  if(!substituted)
926✔
323
  {
324
    auto it_builder = _p->builders.find(ID);
920✔
325
    if(it_builder == _p->builders.end())
920✔
326
    {
327
      idNotFound();
×
328
    }
329
    auto& builder = it_builder->second;
920✔
330
    node = builder(name, config);
920✔
331
  }
332

333
  node->setRegistrationID(ID);
926✔
334
  node->config().enums = _p->scripting_enums;
926✔
335

336
  auto AssignConditions = [](auto& conditions, auto& executors) {
1,852✔
337
    for(const auto& [cond_id, script] : conditions)
1,906✔
338
    {
339
      if(auto executor = ParseScript(script))
108✔
340
      {
341
        executors[size_t(cond_id)] = executor.value();
54✔
342
      }
343
      else
344
      {
345
        throw LogicError("Error in the script \"", script, "\"\n", executor.error());
×
346
      }
347
    }
348
  };
1,852✔
349
  AssignConditions(config.pre_conditions, node->preConditionsScripts());
926✔
350
  AssignConditions(config.post_conditions, node->postConditionsScripts());
926✔
351

352
  return node;
1,852✔
353
}
926✔
354

355
const std::unordered_map<std::string, NodeBuilder>& BehaviorTreeFactory::builders() const
853✔
356
{
357
  return _p->builders;
853✔
358
}
359

360
const std::unordered_map<std::string, TreeNodeManifest>&
361
BehaviorTreeFactory::manifests() const
2,393✔
362
{
363
  return _p->manifests;
2,393✔
364
}
365

366
const std::set<std::string>& BehaviorTreeFactory::builtinNodes() const
126✔
367
{
368
  return _p->builtin_IDs;
126✔
369
}
370

371
Tree BehaviorTreeFactory::createTreeFromText(const std::string& text,
218✔
372
                                             Blackboard::Ptr blackboard)
373
{
374
  // Determine the main tree from the XML text before loading into the
375
  // shared parser, which may already contain trees from previous
376
  // registerBehaviorTreeFrom*() or createTreeFrom*() calls.
377
  tinyxml2::XMLDocument doc;
218✔
378
  doc.Parse(text.c_str(), text.size());
218✔
379
  std::string main_tree_ID;
218✔
380
  if(auto* root = doc.RootElement())
218✔
381
  {
382
    main_tree_ID = detectMainTreeId(root);
216✔
383
  }
384

385
  // When the main tree couldn't be determined from the raw XML
386
  // (e.g. <BehaviorTree> without an ID), snapshot registered trees
387
  // before loading so we can diff afterwards.
388
  std::set<std::string> before_set;
218✔
389
  if(main_tree_ID.empty())
218✔
390
  {
391
    auto before = _p->parser->registeredBehaviorTrees();
115✔
392
    before_set.insert(before.begin(), before.end());
115✔
393
  }
115✔
394

395
  _p->parser->loadFromText(text);
218✔
396

397
  // Try to identify the newly added tree by diffing.
398
  std::string resolved_ID = main_tree_ID;
197✔
399
  if(resolved_ID.empty())
197✔
400
  {
401
    auto after = _p->parser->registeredBehaviorTrees();
110✔
402
    std::string single_new_tree;
110✔
403
    int new_count = 0;
110✔
404
    for(const auto& name : after)
223✔
405
    {
406
      if(before_set.count(name) == 0)
113✔
407
      {
408
        single_new_tree = name;
110✔
409
        new_count++;
110✔
410
      }
411
    }
412
    if(new_count == 1)
110✔
413
    {
414
      resolved_ID = single_new_tree;
110✔
415
    }
416
  }
110✔
417

418
  Tree tree;
197✔
419
  if(resolved_ID.empty())
197✔
420
  {
NEW
421
    tree = _p->parser->instantiateTree(blackboard);
×
422
  }
423
  else
424
  {
425
    tree = _p->parser->instantiateTree(blackboard, resolved_ID);
210✔
426
  }
427
  tree.manifests = this->manifests();
184✔
428
  tree.remapManifestPointers();
184✔
429
  return tree;
368✔
430
}
312✔
431

432
Tree BehaviorTreeFactory::createTreeFromFile(const std::filesystem::path& file_path,
6✔
433
                                             Blackboard::Ptr blackboard)
434
{
435
  // Determine the main tree from the file before loading into the
436
  // shared parser, which may already contain trees from previous
437
  // registerBehaviorTreeFrom*() or createTreeFrom*() calls.
438
  tinyxml2::XMLDocument doc;
6✔
439
  doc.LoadFile(file_path.string().c_str());
6✔
440
  std::string main_tree_ID;
6✔
441
  if(auto* root = doc.RootElement())
6✔
442
  {
443
    main_tree_ID = detectMainTreeId(root);
6✔
444
  }
445

446
  // When the main tree couldn't be determined from the raw XML
447
  // (e.g. <BehaviorTree> without an ID), snapshot registered trees
448
  // before loading so we can diff afterwards.
449
  std::set<std::string> before_set;
6✔
450
  if(main_tree_ID.empty())
6✔
451
  {
NEW
452
    auto before = _p->parser->registeredBehaviorTrees();
×
NEW
453
    before_set.insert(before.begin(), before.end());
×
NEW
454
  }
×
455

456
  _p->parser->loadFromFile(file_path);
6✔
457

458
  // Try to identify the newly added tree by diffing.
459
  std::string resolved_ID = main_tree_ID;
6✔
460
  if(resolved_ID.empty())
6✔
461
  {
NEW
462
    auto after = _p->parser->registeredBehaviorTrees();
×
NEW
463
    std::string single_new_tree;
×
NEW
464
    int new_count = 0;
×
NEW
465
    for(const auto& name : after)
×
466
    {
NEW
467
      if(before_set.count(name) == 0)
×
468
      {
NEW
469
        single_new_tree = name;
×
NEW
470
        new_count++;
×
471
      }
472
    }
NEW
473
    if(new_count == 1)
×
474
    {
NEW
475
      resolved_ID = single_new_tree;
×
476
    }
NEW
477
  }
×
478

479
  Tree tree;
6✔
480
  if(resolved_ID.empty())
6✔
481
  {
NEW
482
    tree = _p->parser->instantiateTree(blackboard);
×
483
  }
484
  else
485
  {
486
    tree = _p->parser->instantiateTree(blackboard, resolved_ID);
6✔
487
  }
488
  tree.manifests = this->manifests();
6✔
489
  tree.remapManifestPointers();
6✔
490
  return tree;
12✔
491
}
6✔
492

493
Tree BehaviorTreeFactory::createTree(const std::string& tree_name,
43✔
494
                                     Blackboard::Ptr blackboard)
495
{
496
  auto tree = _p->parser->instantiateTree(blackboard, tree_name);
45✔
497
  tree.manifests = this->manifests();
41✔
498
  tree.remapManifestPointers();
41✔
499
  return tree;
41✔
500
}
×
501

502
void BehaviorTreeFactory::addMetadataToManifest(const std::string& node_id,
1✔
503
                                                const KeyValueVector& metadata)
504
{
505
  auto it = _p->manifests.find(node_id);
1✔
506
  if(it == _p->manifests.end())
1✔
507
  {
508
    throw std::runtime_error("addMetadataToManifest: wrong ID");
×
509
  }
510
  it->second.metadata = metadata;
1✔
511
}
1✔
512

513
void BehaviorTreeFactory::registerScriptingEnum(StringView name, int value)
16✔
514
{
515
  const auto str = std::string(name);
16✔
516
  auto it = _p->scripting_enums->find(str);
16✔
517
  if(it == _p->scripting_enums->end())
16✔
518
  {
519
    _p->scripting_enums->insert({ str, value });
16✔
520
  }
521
  else
522
  {
523
    if(it->second != value)
×
524
    {
525
      throw LogicError(
526
          StrCat("Registering the enum [", name, "] twice with different values, first ",
×
527
                 std::to_string(it->second), " and later ", std::to_string(value)));
×
528
    }
529
  }
530
}
16✔
531

532
void BehaviorTreeFactory::clearSubstitutionRules()
×
533
{
534
  _p->substitution_rules.clear();
×
535
}
×
536

537
void BehaviorTreeFactory::addSubstitutionRule(StringView filter, SubstitutionRule rule)
11✔
538
{
539
  _p->substitution_rules[std::string(filter)] = std::move(rule);
22✔
540
}
11✔
541

542
void BehaviorTreeFactory::loadSubstitutionRuleFromJSON(const std::string& json_text)
5✔
543
{
544
  auto const json = nlohmann::json::parse(json_text);
5✔
545

546
  std::unordered_map<std::string, TestNodeConfig> configs;
5✔
547

548
  // TestNodeConfigs is optional: users may only have string-based
549
  // substitution rules that map to already-registered node types.
550
  if(json.contains("TestNodeConfigs"))
5✔
551
  {
552
    auto test_configs = json.at("TestNodeConfigs");
3✔
553
    for(auto const& [name, test_config] : test_configs.items())
7✔
554
    {
555
      auto& config = configs[name];
2✔
556

557
      auto return_status = test_config.at("return_status").get<std::string>();
2✔
558
      config.return_status = convertFromString<NodeStatus>(return_status);
2✔
559
      if(test_config.contains("async_delay"))
2✔
560
      {
561
        config.async_delay =
1✔
562
            std::chrono::milliseconds(test_config["async_delay"].get<int>());
1✔
563
      }
564
      if(test_config.contains("post_script"))
2✔
565
      {
566
        config.post_script = test_config["post_script"].get<std::string>();
1✔
567
      }
568
      if(test_config.contains("success_script"))
2✔
569
      {
570
        config.success_script = test_config["success_script"].get<std::string>();
×
571
      }
572
      if(test_config.contains("failure_script"))
2✔
573
      {
574
        config.failure_script = test_config["failure_script"].get<std::string>();
×
575
      }
576
    }
5✔
577
  }
3✔
578

579
  auto substitutions = json.at("SubstitutionRules");
5✔
580
  for(auto const& [node_name, test] : substitutions.items())
19✔
581
  {
582
    auto test_name = test.get<std::string>();
7✔
583
    auto it = configs.find(test_name);
7✔
584
    if(it == configs.end())
7✔
585
    {
586
      addSubstitutionRule(node_name, test_name);
5✔
587
    }
588
    else
589
    {
590
      addSubstitutionRule(node_name, it->second);
2✔
591
    }
592
  }
12✔
593
}
5✔
594

595
const std::unordered_map<std::string, BehaviorTreeFactory::SubstitutionRule>&
596
BehaviorTreeFactory::substitutionRules() const
3✔
597
{
598
  return _p->substitution_rules;
3✔
599
}
600

601
Tree::Tree() = default;
452✔
602

603
void Tree::remapManifestPointers()
231✔
604
{
605
  for(auto& subtree : subtrees)
530✔
606
  {
607
    for(auto& node : subtree->nodes)
1,195✔
608
    {
609
      const auto* old_manifest = node->config().manifest;
896✔
610
      if(old_manifest != nullptr)
896✔
611
      {
612
        auto it = manifests.find(old_manifest->registration_ID);
827✔
613
        if(it != manifests.end())
827✔
614
        {
615
          node->config().manifest = &(it->second);
827✔
616
        }
617
      }
618
    }
619
  }
620
}
231✔
621

622
void Tree::initialize()
231✔
623
{
624
  wake_up_ = std::make_shared<WakeUpSignal>();
231✔
625
  for(auto& subtree : subtrees)
530✔
626
  {
627
    for(auto& node : subtree->nodes)
1,195✔
628
    {
629
      node->setWakeUpInstance(wake_up_);
896✔
630
    }
631
  }
632
}
231✔
633

634
// NOLINTNEXTLINE(readability-make-member-function-const)
635
void Tree::haltTree()
465✔
636
{
637
  if(rootNode() == nullptr)
465✔
638
  {
639
    return;
213✔
640
  }
641
  // the halt should propagate to all the node if the nodes
642
  // have been implemented correctly
643
  rootNode()->haltNode();
252✔
644

645
  //but, just in case.... this should be no-op
646
  auto visitor = [](BT::TreeNode* node) { node->haltNode(); };
966✔
647
  BT::applyRecursiveVisitor(rootNode(), visitor);
252✔
648

649
  rootNode()->resetStatus();
252✔
650
}
651

652
TreeNode* Tree::rootNode() const
2,970✔
653
{
654
  if(subtrees.empty())
2,970✔
655
  {
656
    return nullptr;
208✔
657
  }
658
  auto& subtree_nodes = subtrees.front()->nodes;
2,762✔
659
  return subtree_nodes.empty() ? nullptr : subtree_nodes.front().get();
2,762✔
660
}
661

662
bool Tree::sleep(std::chrono::system_clock::duration timeout)
276✔
663
{
664
  return wake_up_->waitFor(
552✔
665
      std::chrono::duration_cast<std::chrono::milliseconds>(timeout));
552✔
666
}
667

668
void Tree::emitWakeUpSignal()
×
669
{
670
  wake_up_->emitSignal();
×
671
}
×
672

673
Tree::~Tree()
452✔
674
{
675
  haltTree();
452✔
676
}
452✔
677

678
NodeStatus Tree::tickExactlyOnce()
300✔
679
{
680
  return tickRoot(EXACTLY_ONCE, std::chrono::milliseconds(0));
300✔
681
}
682

683
NodeStatus Tree::tickOnce()
133✔
684
{
685
  return tickRoot(ONCE_UNLESS_WOKEN_UP, std::chrono::milliseconds(0));
133✔
686
}
687

688
NodeStatus Tree::tickWhileRunning(std::chrono::milliseconds sleep_time)
190✔
689
{
690
  return tickRoot(WHILE_RUNNING, sleep_time);
190✔
691
}
692

693
Blackboard::Ptr Tree::rootBlackboard()
53✔
694
{
695
  if(!subtrees.empty())
53✔
696
  {
697
    return subtrees.front()->blackboard;
53✔
698
  }
699
  return {};
×
700
}
701

702
void Tree::applyVisitor(const std::function<void(const TreeNode*)>& visitor) const
×
703
{
704
  BT::applyRecursiveVisitor(static_cast<const TreeNode*>(rootNode()), visitor);
×
705
}
×
706

707
// NOLINTNEXTLINE(readability-make-member-function-const)
708
void Tree::applyVisitor(const std::function<void(TreeNode*)>& visitor)
3✔
709
{
710
  BT::applyRecursiveVisitor(rootNode(), visitor);
3✔
711
}
3✔
712

713
uint16_t Tree::getUID()
929✔
714
{
715
  auto uid = ++uid_counter_;
929✔
716
  return uid;
929✔
717
}
718

719
NodeStatus Tree::tickRoot(TickOption opt, std::chrono::milliseconds sleep_time)
623✔
720
{
721
  NodeStatus status = NodeStatus::IDLE;
623✔
722

723
  if(!wake_up_)
623✔
724
  {
725
    initialize();
×
726
  }
727

728
  if(rootNode() == nullptr)
623✔
729
  {
730
    throw RuntimeError("Empty Tree");
×
731
  }
732

733
  while(status == NodeStatus::IDLE ||
2,078✔
734
        (opt == TickOption::WHILE_RUNNING && status == NodeStatus::RUNNING))
409✔
735
  {
736
    status = rootNode()->executeTick();
850✔
737

738
    // Inner loop. The previous tick might have triggered the wake-up
739
    // in this case, unless TickOption::EXACTLY_ONCE, we tick again
740
    while(opt != TickOption::EXACTLY_ONCE && status == NodeStatus::RUNNING &&
1,190✔
741
          wake_up_->waitFor(std::chrono::milliseconds(0)))
1,190✔
742
    {
743
      status = rootNode()->executeTick();
14✔
744
    }
745

746
    if(isStatusCompleted(status))
832✔
747
    {
748
      rootNode()->resetStatus();
211✔
749
    }
750
    if(status == NodeStatus::RUNNING && sleep_time.count() > 0)
832✔
751
    {
752
      sleep(std::chrono::milliseconds(sleep_time));
227✔
753
    }
754
  }
755

756
  return status;
605✔
757
}
758

759
void BlackboardRestore(const std::vector<Blackboard::Ptr>& backup, Tree& tree)
1✔
760
{
761
  assert(backup.size() == tree.subtrees.size());
1✔
762
  for(size_t i = 0; i < tree.subtrees.size(); i++)
3✔
763
  {
764
    backup[i]->cloneInto(*(tree.subtrees[i]->blackboard));
2✔
765
  }
766
}
1✔
767

768
std::vector<Blackboard::Ptr> BlackboardBackup(const Tree& tree)
1✔
769
{
770
  std::vector<Blackboard::Ptr> bb;
1✔
771
  bb.reserve(tree.subtrees.size());
1✔
772
  for(const auto& sub : tree.subtrees)
3✔
773
  {
774
    bb.push_back(BT::Blackboard::create());
2✔
775
    sub->blackboard->cloneInto(*bb.back());
2✔
776
  }
777
  return bb;
1✔
778
}
×
779

780
nlohmann::json ExportTreeToJSON(const Tree& tree)
×
781
{
782
  nlohmann::json out;
×
783
  for(const auto& subtree : tree.subtrees)
×
784
  {
785
    auto sub_name = subtree->instance_name;
×
786
    if(sub_name.empty())
×
787
    {
788
      sub_name = subtree->tree_ID;
×
789
    }
790
    out[sub_name] = ExportBlackboardToJSON(*subtree->blackboard);
×
791
  }
×
792
  return out;
×
793
}
×
794

795
void ImportTreeFromJSON(const nlohmann::json& json, Tree& tree)
×
796
{
797
  if(json.size() != tree.subtrees.size())
×
798
  {
799
    throw std::runtime_error("Number of blackboards don't match:");
×
800
  }
801

802
  size_t index = 0;
×
803
  for(const auto& [key, array] : json.items())
×
804
  {
805
    auto& subtree = tree.subtrees.at(index++);
×
806
    ImportBlackboardFromJSON(array, *subtree->blackboard);
×
807
  }
×
808
}
×
809

810
}  // 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