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

BehaviorTree / BehaviorTree.CPP / 21669518654

04 Feb 2026 11:22AM UTC coverage: 79.715% (-0.03%) from 79.742%
21669518654

push

github

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

50 of 50 new or added lines in 1 file covered. (100.0%)

80 existing lines in 2 files now uncovered.

4692 of 5886 relevant lines covered (79.71%)

22434.13 hits per line

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

78.72
/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
#include <functional>
23

24
namespace BT
25
{
26
namespace
27
{
28

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

56
// Load XML into parser and resolve which tree to instantiate.
57
// Returns the resolved tree ID (may be empty if parser should use default).
58
std::string loadXmlAndResolveTreeId(Parser* parser, const std::string& main_tree_ID,
224✔
59
                                    const std::function<void()>& load_func)
60
{
61
  // When the main tree couldn't be determined from the raw XML
62
  // (e.g. <BehaviorTree> without an ID), snapshot registered trees
63
  // before loading so we can diff afterwards.
64
  std::set<std::string> before_set;
224✔
65
  if(main_tree_ID.empty())
224✔
66
  {
67
    const auto before = parser->registeredBehaviorTrees();
115✔
68
    before_set.insert(before.begin(), before.end());
115✔
69
  }
115✔
70

71
  load_func();
224✔
72

73
  // Try to identify the newly added tree by diffing.
74
  if(main_tree_ID.empty())
203✔
75
  {
76
    const auto after = parser->registeredBehaviorTrees();
110✔
77
    std::string single_new_tree;
110✔
78
    int new_count = 0;
110✔
79
    for(const auto& name : after)
223✔
80
    {
81
      if(before_set.count(name) == 0)
113✔
82
      {
83
        single_new_tree = name;
110✔
84
        new_count++;
110✔
85
      }
86
    }
87
    if(new_count == 1)
110✔
88
    {
89
      return single_new_tree;
110✔
90
    }
91
  }
220✔
92
  return main_tree_ID;
93✔
93
}
224✔
94

95
}  // namespace
96

UNCOV
97
bool WildcardMatch(std::string const& str, StringView filter)
×
98
{
UNCOV
99
  return wildcards_match(str, { filter.data(), filter.size() });
×
100
}
101

102
struct BehaviorTreeFactory::PImpl
103
{
104
  std::unordered_map<std::string, NodeBuilder> builders;
105
  std::unordered_map<std::string, TreeNodeManifest> manifests;
106
  std::set<std::string> builtin_IDs;
107
  std::unordered_map<std::string, Any> behavior_tree_definitions;
108
  std::shared_ptr<std::unordered_map<std::string, int>> scripting_enums;
109
  std::shared_ptr<BT::Parser> parser;
110
  std::unordered_map<std::string, SubstitutionRule> substitution_rules;
111
};
112

113
BehaviorTreeFactory::BehaviorTreeFactory() : _p(new PImpl)
288✔
114
{
115
  _p->parser = std::make_shared<XMLParser>(*this);
288✔
116
  registerNodeType<FallbackNode>("Fallback");
576✔
117
  registerNodeType<FallbackNode>("AsyncFallback", true);
576✔
118
  registerNodeType<SequenceNode>("Sequence");
576✔
119
  registerNodeType<SequenceNode>("AsyncSequence", true);
576✔
120
  registerNodeType<SequenceWithMemory>("SequenceWithMemory");
576✔
121

122
#ifdef USE_BTCPP3_OLD_NAMES
123
  registerNodeType<SequenceWithMemory>("SequenceStar");  // backward compatibility
124
#endif
125

126
  registerNodeType<ParallelNode>("Parallel");
576✔
127
  registerNodeType<ParallelAllNode>("ParallelAll");
576✔
128
  registerNodeType<ReactiveSequence>("ReactiveSequence");
576✔
129
  registerNodeType<ReactiveFallback>("ReactiveFallback");
576✔
130
  registerNodeType<IfThenElseNode>("IfThenElse");
576✔
131
  registerNodeType<WhileDoElseNode>("WhileDoElse");
576✔
132

133
  registerNodeType<InverterNode>("Inverter");
576✔
134

135
  registerNodeType<RetryNode>("RetryUntilSuccessful");
576✔
136
  registerNodeType<KeepRunningUntilFailureNode>("KeepRunningUntilFailure");
576✔
137
  registerNodeType<RepeatNode>("Repeat");
576✔
138
  registerNodeType<TimeoutNode>("Timeout");
576✔
139
  registerNodeType<DelayNode>("Delay");
576✔
140
  registerNodeType<RunOnceNode>("RunOnce");
576✔
141

142
  registerNodeType<ForceSuccessNode>("ForceSuccess");
576✔
143
  registerNodeType<ForceFailureNode>("ForceFailure");
576✔
144

145
  registerNodeType<AlwaysSuccessNode>("AlwaysSuccess");
576✔
146
  registerNodeType<AlwaysFailureNode>("AlwaysFailure");
576✔
147
  registerNodeType<ScriptNode>("Script");
576✔
148
  registerNodeType<ScriptCondition>("ScriptCondition");
576✔
149
  registerNodeType<SetBlackboardNode>("SetBlackboard");
576✔
150
  registerNodeType<SleepNode>("Sleep");
576✔
151
  registerNodeType<UnsetBlackboardNode>("UnsetBlackboard");
576✔
152

153
  registerNodeType<SubTreeNode>("SubTree");
576✔
154

155
  registerNodeType<PreconditionNode>("Precondition");
576✔
156

157
  registerNodeType<SwitchNode<2>>("Switch2");
576✔
158
  registerNodeType<SwitchNode<3>>("Switch3");
576✔
159
  registerNodeType<SwitchNode<4>>("Switch4");
576✔
160
  registerNodeType<SwitchNode<5>>("Switch5");
576✔
161
  registerNodeType<SwitchNode<6>>("Switch6");
576✔
162

163
  registerNodeType<LoopNode<int>>("LoopInt");
576✔
164
  registerNodeType<LoopNode<bool>>("LoopBool");
576✔
165
  registerNodeType<LoopNode<double>>("LoopDouble");
576✔
166
  registerNodeType<LoopNode<std::string>>("LoopString");
576✔
167

168
  registerNodeType<EntryUpdatedAction>("WasEntryUpdated");
576✔
169
  registerNodeType<EntryUpdatedDecorator>("SkipUnlessUpdated", NodeStatus::SKIPPED);
576✔
170
  registerNodeType<EntryUpdatedDecorator>("WaitValueUpdate", NodeStatus::RUNNING);
288✔
171

172
  for(const auto& it : _p->builders)
12,096✔
173
  {
174
    _p->builtin_IDs.insert(it.first);
11,808✔
175
  }
176

177
  _p->scripting_enums = std::make_shared<std::unordered_map<std::string, int>>();
288✔
178
}
288✔
179

180
BehaviorTreeFactory::~BehaviorTreeFactory() = default;
288✔
181

UNCOV
182
BehaviorTreeFactory::BehaviorTreeFactory(BehaviorTreeFactory&& other) noexcept = default;
×
183
BehaviorTreeFactory&
184
BehaviorTreeFactory::operator=(BehaviorTreeFactory&& other) noexcept = default;
1✔
185

UNCOV
186
bool BehaviorTreeFactory::unregisterBuilder(const std::string& ID)
×
187
{
UNCOV
188
  if(builtinNodes().count(ID) != 0)
×
189
  {
UNCOV
190
    throw LogicError("You can not remove the builtin registration ID [", ID, "]");
×
191
  }
UNCOV
192
  auto it = _p->builders.find(ID);
×
UNCOV
193
  if(it == _p->builders.end())
×
194
  {
UNCOV
195
    return false;
×
196
  }
UNCOV
197
  _p->builders.erase(ID);
×
UNCOV
198
  _p->manifests.erase(ID);
×
UNCOV
199
  return true;
×
200
}
201

202
void BehaviorTreeFactory::registerBuilder(const TreeNodeManifest& manifest,
12,122✔
203
                                          const NodeBuilder& builder)
204
{
205
  auto it = _p->builders.find(manifest.registration_ID);
12,122✔
206
  if(it != _p->builders.end())
12,122✔
207
  {
UNCOV
208
    throw BehaviorTreeException("ID [", manifest.registration_ID, "] already registered");
×
209
  }
210

211
  _p->builders.insert({ manifest.registration_ID, builder });
12,122✔
212
  _p->manifests.insert({ manifest.registration_ID, manifest });
12,122✔
213
}
12,122✔
214

215
void BehaviorTreeFactory::registerSimpleCondition(
11✔
216
    const std::string& ID, const SimpleConditionNode::TickFunctor& tick_functor,
217
    PortsList ports)
218
{
219
  const NodeBuilder builder = [tick_functor, ID](const std::string& name,
22✔
220
                                                 const NodeConfig& config) {
221
    return std::make_unique<SimpleConditionNode>(name, tick_functor, config);
10✔
222
  };
11✔
223

224
  const TreeNodeManifest manifest = { NodeType::CONDITION, ID, std::move(ports), {} };
11✔
225
  registerBuilder(manifest, builder);
11✔
226
}
11✔
227

228
void BehaviorTreeFactory::registerSimpleAction(
185✔
229
    const std::string& ID, const SimpleActionNode::TickFunctor& tick_functor,
230
    PortsList ports)
231
{
232
  const NodeBuilder builder = [tick_functor, ID](const std::string& name,
370✔
233
                                                 const NodeConfig& config) {
234
    return std::make_unique<SimpleActionNode>(name, tick_functor, config);
123✔
235
  };
185✔
236

237
  const TreeNodeManifest manifest = { NodeType::ACTION, ID, std::move(ports), {} };
185✔
238
  registerBuilder(manifest, builder);
185✔
239
}
185✔
240

UNCOV
241
void BehaviorTreeFactory::registerSimpleDecorator(
×
242
    const std::string& ID, const SimpleDecoratorNode::TickFunctor& tick_functor,
243
    PortsList ports)
244
{
UNCOV
245
  const NodeBuilder builder = [tick_functor, ID](const std::string& name,
×
246
                                                 const NodeConfig& config) {
UNCOV
247
    return std::make_unique<SimpleDecoratorNode>(name, tick_functor, config);
×
UNCOV
248
  };
×
249

UNCOV
250
  const TreeNodeManifest manifest = { NodeType::DECORATOR, ID, std::move(ports), {} };
×
UNCOV
251
  registerBuilder(manifest, builder);
×
UNCOV
252
}
×
253

254
void BehaviorTreeFactory::registerFromPlugin(const std::string& file_path)
3✔
255
{
256
  BT::SharedLibrary loader;
3✔
257
  loader.load(file_path);
3✔
258
  using Func = void (*)(BehaviorTreeFactory&);
259

260
  if(loader.hasSymbol(PLUGIN_SYMBOL))
6✔
261
  {
262
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
263
    auto* func = reinterpret_cast<Func>(loader.getSymbol(PLUGIN_SYMBOL));
3✔
264
    func(*this);
3✔
265
  }
266
  else
267
  {
268
    std::cout << "ERROR loading library [" << file_path << "]: can't find symbol ["
UNCOV
269
              << PLUGIN_SYMBOL << "]" << std::endl;
×
270
  }
271
}
3✔
272

273
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
UNCOV
274
void BehaviorTreeFactory::registerFromROSPlugins()
×
275
{
276
  throw RuntimeError("Using attribute [ros_pkg] in <include>, but this library was "
277
                     "compiled without ROS support. Recompile the BehaviorTree.CPP "
UNCOV
278
                     "using catkin");
×
279
}
280

281
void BehaviorTreeFactory::registerBehaviorTreeFromFile(
×
282
    const std::filesystem::path& filename)
283
{
UNCOV
284
  _p->parser->loadFromFile(filename);
×
UNCOV
285
}
×
286

287
void BehaviorTreeFactory::registerBehaviorTreeFromText(const std::string& xml_text)
50✔
288
{
289
  _p->parser->loadFromText(xml_text);
50✔
290
}
49✔
291

292
std::vector<std::string> BehaviorTreeFactory::registeredBehaviorTrees() const
1✔
293
{
294
  return _p->parser->registeredBehaviorTrees();
1✔
295
}
296

UNCOV
297
void BehaviorTreeFactory::clearRegisteredBehaviorTrees()
×
298
{
UNCOV
299
  _p->parser->clearInternalState();
×
UNCOV
300
}
×
301

302
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
303
std::unique_ptr<TreeNode> BehaviorTreeFactory::instantiateTreeNode(
926✔
304
    const std::string& name, const std::string& ID, const NodeConfig& config) const
305
{
UNCOV
306
  auto idNotFound = [this, ID] {
×
UNCOV
307
    std::cerr << ID << " not included in this list:" << std::endl;
×
UNCOV
308
    for(const auto& builder_it : _p->builders)
×
309
    {
UNCOV
310
      std::cerr << builder_it.first << std::endl;
×
311
    }
UNCOV
312
    throw RuntimeError("BehaviorTreeFactory: ID [", ID, "] not registered");
×
313
  };
926✔
314

315
  auto it_manifest = _p->manifests.find(ID);
926✔
316
  if(it_manifest == _p->manifests.end())
926✔
317
  {
318
    idNotFound();
×
319
  }
320

321
  std::unique_ptr<TreeNode> node;
926✔
322

323
  bool substituted = false;
926✔
324
  for(const auto& [filter, rule] : _p->substitution_rules)
936✔
325
  {
326
    if(filter == name || filter == ID || wildcards_match(config.path, filter))
16✔
327
    {
328
      // first case: the rule is simply a string with the name of the
329
      // node to create instead
330
      if(const auto* const substituted_ID = std::get_if<std::string>(&rule))
6✔
331
      {
332
        auto it_builder = _p->builders.find(*substituted_ID);
4✔
333
        if(it_builder != _p->builders.end())
4✔
334
        {
335
          auto& builder = it_builder->second;
4✔
336
          node = builder(name, config);
4✔
337
        }
338
        else
339
        {
UNCOV
340
          throw RuntimeError("Substituted Node ID [", *substituted_ID, "] not found");
×
341
        }
342
        substituted = true;
4✔
343
        break;
4✔
344
      }
345

346
      if(const auto* const test_config = std::get_if<TestNodeConfig>(&rule))
2✔
347
      {
348
        node = std::make_unique<TestNode>(name, config,
4✔
349
                                          std::make_shared<TestNodeConfig>(*test_config));
6✔
350
        substituted = true;
2✔
351
        break;
2✔
352
      }
353

354
      if(const auto* const test_config =
×
UNCOV
355
             std::get_if<std::shared_ptr<TestNodeConfig>>(&rule))
×
356
      {
357
        node = std::make_unique<TestNode>(name, config, *test_config);
×
358
        substituted = true;
×
UNCOV
359
        break;
×
360
      }
UNCOV
361
      throw LogicError("Substitution rule is not a string or a TestNodeConfig");
×
362
    }
363
  }
364

365
  // No substitution rule applied: default behavior
366
  if(!substituted)
926✔
367
  {
368
    auto it_builder = _p->builders.find(ID);
920✔
369
    if(it_builder == _p->builders.end())
920✔
370
    {
UNCOV
371
      idNotFound();
×
372
    }
373
    auto& builder = it_builder->second;
920✔
374
    node = builder(name, config);
920✔
375
  }
376

377
  node->setRegistrationID(ID);
926✔
378
  node->config().enums = _p->scripting_enums;
926✔
379

380
  auto AssignConditions = [](auto& conditions, auto& executors) {
1,852✔
381
    for(const auto& [cond_id, script] : conditions)
1,906✔
382
    {
383
      if(auto executor = ParseScript(script))
108✔
384
      {
385
        executors[size_t(cond_id)] = executor.value();
54✔
386
      }
387
      else
388
      {
UNCOV
389
        throw LogicError("Error in the script \"", script, "\"\n", executor.error());
×
390
      }
391
    }
392
  };
1,852✔
393
  AssignConditions(config.pre_conditions, node->preConditionsScripts());
926✔
394
  AssignConditions(config.post_conditions, node->postConditionsScripts());
926✔
395

396
  return node;
1,852✔
397
}
926✔
398

399
const std::unordered_map<std::string, NodeBuilder>& BehaviorTreeFactory::builders() const
853✔
400
{
401
  return _p->builders;
853✔
402
}
403

404
const std::unordered_map<std::string, TreeNodeManifest>&
405
BehaviorTreeFactory::manifests() const
2,393✔
406
{
407
  return _p->manifests;
2,393✔
408
}
409

410
const std::set<std::string>& BehaviorTreeFactory::builtinNodes() const
126✔
411
{
412
  return _p->builtin_IDs;
126✔
413
}
414

415
Tree BehaviorTreeFactory::createTreeFromText(const std::string& text,
218✔
416
                                             Blackboard::Ptr blackboard)
417
{
418
  // Determine the main tree from the XML before loading into the shared parser.
419
  tinyxml2::XMLDocument doc;
218✔
420
  doc.Parse(text.c_str(), text.size());
218✔
421
  std::string main_tree_ID;
218✔
422
  if(const auto* root = doc.RootElement())
218✔
423
  {
424
    main_tree_ID = detectMainTreeId(root);
216✔
425
  }
426

427
  const std::string resolved_ID = loadXmlAndResolveTreeId(
428
      _p->parser.get(), main_tree_ID, [&] { _p->parser->loadFromText(text); });
457✔
429

430
  Tree tree = resolved_ID.empty() ? _p->parser->instantiateTree(blackboard) :
197✔
431
                                    _p->parser->instantiateTree(blackboard, resolved_ID);
223✔
432
  tree.manifests = this->manifests();
184✔
433
  tree.remapManifestPointers();
184✔
434
  return tree;
368✔
435
}
265✔
436

437
Tree BehaviorTreeFactory::createTreeFromFile(const std::filesystem::path& file_path,
6✔
438
                                             Blackboard::Ptr blackboard)
439
{
440
  // Determine the main tree from the XML before loading into the shared parser.
441
  tinyxml2::XMLDocument doc;
6✔
442
  doc.LoadFile(file_path.string().c_str());
6✔
443
  std::string main_tree_ID;
6✔
444
  if(const auto* root = doc.RootElement())
6✔
445
  {
446
    main_tree_ID = detectMainTreeId(root);
6✔
447
  }
448

449
  const std::string resolved_ID = loadXmlAndResolveTreeId(
450
      _p->parser.get(), main_tree_ID, [&] { _p->parser->loadFromFile(file_path); });
12✔
451

452
  Tree tree = resolved_ID.empty() ? _p->parser->instantiateTree(blackboard) :
6✔
453
                                    _p->parser->instantiateTree(blackboard, resolved_ID);
6✔
454
  tree.manifests = this->manifests();
6✔
455
  tree.remapManifestPointers();
6✔
456
  return tree;
12✔
457
}
6✔
458

459
Tree BehaviorTreeFactory::createTree(const std::string& tree_name,
43✔
460
                                     Blackboard::Ptr blackboard)
461
{
462
  auto tree = _p->parser->instantiateTree(blackboard, tree_name);
45✔
463
  tree.manifests = this->manifests();
41✔
464
  tree.remapManifestPointers();
41✔
465
  return tree;
41✔
UNCOV
466
}
×
467

468
void BehaviorTreeFactory::addMetadataToManifest(const std::string& node_id,
1✔
469
                                                const KeyValueVector& metadata)
470
{
471
  auto it = _p->manifests.find(node_id);
1✔
472
  if(it == _p->manifests.end())
1✔
473
  {
UNCOV
474
    throw std::runtime_error("addMetadataToManifest: wrong ID");
×
475
  }
476
  it->second.metadata = metadata;
1✔
477
}
1✔
478

479
void BehaviorTreeFactory::registerScriptingEnum(StringView name, int value)
16✔
480
{
481
  const auto str = std::string(name);
16✔
482
  auto it = _p->scripting_enums->find(str);
16✔
483
  if(it == _p->scripting_enums->end())
16✔
484
  {
485
    _p->scripting_enums->insert({ str, value });
16✔
486
  }
487
  else
488
  {
UNCOV
489
    if(it->second != value)
×
490
    {
491
      throw LogicError(
UNCOV
492
          StrCat("Registering the enum [", name, "] twice with different values, first ",
×
UNCOV
493
                 std::to_string(it->second), " and later ", std::to_string(value)));
×
494
    }
495
  }
496
}
16✔
497

UNCOV
498
void BehaviorTreeFactory::clearSubstitutionRules()
×
499
{
UNCOV
500
  _p->substitution_rules.clear();
×
UNCOV
501
}
×
502

503
void BehaviorTreeFactory::addSubstitutionRule(StringView filter, SubstitutionRule rule)
11✔
504
{
505
  _p->substitution_rules[std::string(filter)] = std::move(rule);
22✔
506
}
11✔
507

508
void BehaviorTreeFactory::loadSubstitutionRuleFromJSON(const std::string& json_text)
5✔
509
{
510
  auto const json = nlohmann::json::parse(json_text);
5✔
511

512
  std::unordered_map<std::string, TestNodeConfig> configs;
5✔
513

514
  // TestNodeConfigs is optional: users may only have string-based
515
  // substitution rules that map to already-registered node types.
516
  if(json.contains("TestNodeConfigs"))
5✔
517
  {
518
    auto test_configs = json.at("TestNodeConfigs");
3✔
519
    for(auto const& [name, test_config] : test_configs.items())
7✔
520
    {
521
      auto& config = configs[name];
2✔
522

523
      auto return_status = test_config.at("return_status").get<std::string>();
2✔
524
      config.return_status = convertFromString<NodeStatus>(return_status);
2✔
525
      if(test_config.contains("async_delay"))
2✔
526
      {
527
        config.async_delay =
1✔
528
            std::chrono::milliseconds(test_config["async_delay"].get<int>());
1✔
529
      }
530
      if(test_config.contains("post_script"))
2✔
531
      {
532
        config.post_script = test_config["post_script"].get<std::string>();
1✔
533
      }
534
      if(test_config.contains("success_script"))
2✔
535
      {
UNCOV
536
        config.success_script = test_config["success_script"].get<std::string>();
×
537
      }
538
      if(test_config.contains("failure_script"))
2✔
539
      {
UNCOV
540
        config.failure_script = test_config["failure_script"].get<std::string>();
×
541
      }
542
    }
5✔
543
  }
3✔
544

545
  auto substitutions = json.at("SubstitutionRules");
5✔
546
  for(auto const& [node_name, test] : substitutions.items())
19✔
547
  {
548
    auto test_name = test.get<std::string>();
7✔
549
    auto it = configs.find(test_name);
7✔
550
    if(it == configs.end())
7✔
551
    {
552
      addSubstitutionRule(node_name, test_name);
5✔
553
    }
554
    else
555
    {
556
      addSubstitutionRule(node_name, it->second);
2✔
557
    }
558
  }
12✔
559
}
5✔
560

561
const std::unordered_map<std::string, BehaviorTreeFactory::SubstitutionRule>&
562
BehaviorTreeFactory::substitutionRules() const
3✔
563
{
564
  return _p->substitution_rules;
3✔
565
}
566

567
Tree::Tree() = default;
249✔
568

569
void Tree::remapManifestPointers()
231✔
570
{
571
  for(auto& subtree : subtrees)
530✔
572
  {
573
    for(auto& node : subtree->nodes)
1,195✔
574
    {
575
      const auto* old_manifest = node->config().manifest;
896✔
576
      if(old_manifest != nullptr)
896✔
577
      {
578
        auto it = manifests.find(old_manifest->registration_ID);
827✔
579
        if(it != manifests.end())
827✔
580
        {
581
          node->config().manifest = &(it->second);
827✔
582
        }
583
      }
584
    }
585
  }
586
}
231✔
587

588
void Tree::initialize()
231✔
589
{
590
  wake_up_ = std::make_shared<WakeUpSignal>();
231✔
591
  for(auto& subtree : subtrees)
530✔
592
  {
593
    for(auto& node : subtree->nodes)
1,195✔
594
    {
595
      node->setWakeUpInstance(wake_up_);
896✔
596
    }
597
  }
598
}
231✔
599

600
// NOLINTNEXTLINE(readability-make-member-function-const)
601
void Tree::haltTree()
262✔
602
{
603
  if(rootNode() == nullptr)
262✔
604
  {
605
    return;
10✔
606
  }
607
  // the halt should propagate to all the node if the nodes
608
  // have been implemented correctly
609
  rootNode()->haltNode();
252✔
610

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

615
  rootNode()->resetStatus();
252✔
616
}
617

618
TreeNode* Tree::rootNode() const
2,778✔
619
{
620
  if(subtrees.empty())
2,778✔
621
  {
622
    return nullptr;
5✔
623
  }
624
  auto& subtree_nodes = subtrees.front()->nodes;
2,773✔
625
  return subtree_nodes.empty() ? nullptr : subtree_nodes.front().get();
2,773✔
626
}
627

628
bool Tree::sleep(std::chrono::system_clock::duration timeout)
276✔
629
{
630
  return wake_up_->waitFor(
552✔
631
      std::chrono::duration_cast<std::chrono::milliseconds>(timeout));
552✔
632
}
633

UNCOV
634
void Tree::emitWakeUpSignal()
×
635
{
UNCOV
636
  wake_up_->emitSignal();
×
UNCOV
637
}
×
638

639
Tree::~Tree()
249✔
640
{
641
  haltTree();
249✔
642
}
249✔
643

644
NodeStatus Tree::tickExactlyOnce()
305✔
645
{
646
  return tickRoot(EXACTLY_ONCE, std::chrono::milliseconds(0));
305✔
647
}
648

649
NodeStatus Tree::tickOnce()
133✔
650
{
651
  return tickRoot(ONCE_UNLESS_WOKEN_UP, std::chrono::milliseconds(0));
133✔
652
}
653

654
NodeStatus Tree::tickWhileRunning(std::chrono::milliseconds sleep_time)
190✔
655
{
656
  return tickRoot(WHILE_RUNNING, sleep_time);
190✔
657
}
658

659
Blackboard::Ptr Tree::rootBlackboard()
53✔
660
{
661
  if(!subtrees.empty())
53✔
662
  {
663
    return subtrees.front()->blackboard;
53✔
664
  }
UNCOV
665
  return {};
×
666
}
667

UNCOV
668
void Tree::applyVisitor(const std::function<void(const TreeNode*)>& visitor) const
×
669
{
UNCOV
670
  BT::applyRecursiveVisitor(static_cast<const TreeNode*>(rootNode()), visitor);
×
UNCOV
671
}
×
672

673
// NOLINTNEXTLINE(readability-make-member-function-const)
674
void Tree::applyVisitor(const std::function<void(TreeNode*)>& visitor)
3✔
675
{
676
  BT::applyRecursiveVisitor(rootNode(), visitor);
3✔
677
}
3✔
678

679
uint16_t Tree::getUID()
929✔
680
{
681
  auto uid = ++uid_counter_;
929✔
682
  return uid;
929✔
683
}
684

685
NodeStatus Tree::tickRoot(TickOption opt, std::chrono::milliseconds sleep_time)
628✔
686
{
687
  NodeStatus status = NodeStatus::IDLE;
628✔
688

689
  if(!wake_up_)
628✔
690
  {
UNCOV
691
    initialize();
×
692
  }
693

694
  if(rootNode() == nullptr)
628✔
695
  {
UNCOV
696
    throw RuntimeError("Empty Tree");
×
697
  }
698

699
  while(status == NodeStatus::IDLE ||
2,093✔
700
        (opt == TickOption::WHILE_RUNNING && status == NodeStatus::RUNNING))
409✔
701
  {
702
    status = rootNode()->executeTick();
855✔
703

704
    // Inner loop. The previous tick might have triggered the wake-up
705
    // in this case, unless TickOption::EXACTLY_ONCE, we tick again
706
    while(opt != TickOption::EXACTLY_ONCE && status == NodeStatus::RUNNING &&
1,197✔
707
          wake_up_->waitFor(std::chrono::milliseconds(0)))
1,197✔
708
    {
709
      status = rootNode()->executeTick();
15✔
710
    }
711

712
    if(isStatusCompleted(status))
837✔
713
    {
714
      rootNode()->resetStatus();
211✔
715
    }
716
    if(status == NodeStatus::RUNNING && sleep_time.count() > 0)
837✔
717
    {
718
      sleep(std::chrono::milliseconds(sleep_time));
227✔
719
    }
720
  }
721

722
  return status;
610✔
723
}
724

725
void BlackboardRestore(const std::vector<Blackboard::Ptr>& backup, Tree& tree)
1✔
726
{
727
  assert(backup.size() == tree.subtrees.size());
1✔
728
  for(size_t i = 0; i < tree.subtrees.size(); i++)
3✔
729
  {
730
    backup[i]->cloneInto(*(tree.subtrees[i]->blackboard));
2✔
731
  }
732
}
1✔
733

734
std::vector<Blackboard::Ptr> BlackboardBackup(const Tree& tree)
1✔
735
{
736
  std::vector<Blackboard::Ptr> bb;
1✔
737
  bb.reserve(tree.subtrees.size());
1✔
738
  for(const auto& sub : tree.subtrees)
3✔
739
  {
740
    bb.push_back(BT::Blackboard::create());
2✔
741
    sub->blackboard->cloneInto(*bb.back());
2✔
742
  }
743
  return bb;
1✔
UNCOV
744
}
×
745

746
nlohmann::json ExportTreeToJSON(const Tree& tree)
×
747
{
748
  nlohmann::json out;
×
749
  for(const auto& subtree : tree.subtrees)
×
750
  {
UNCOV
751
    auto sub_name = subtree->instance_name;
×
UNCOV
752
    if(sub_name.empty())
×
753
    {
UNCOV
754
      sub_name = subtree->tree_ID;
×
755
    }
UNCOV
756
    out[sub_name] = ExportBlackboardToJSON(*subtree->blackboard);
×
UNCOV
757
  }
×
UNCOV
758
  return out;
×
UNCOV
759
}
×
760

UNCOV
761
void ImportTreeFromJSON(const nlohmann::json& json, Tree& tree)
×
762
{
UNCOV
763
  if(json.size() != tree.subtrees.size())
×
764
  {
UNCOV
765
    throw std::runtime_error("Number of blackboards don't match:");
×
766
  }
767

UNCOV
768
  size_t index = 0;
×
769
  for(const auto& [key, array] : json.items())
×
770
  {
UNCOV
771
    auto& subtree = tree.subtrees.at(index++);
×
UNCOV
772
    ImportBlackboardFromJSON(array, *subtree->blackboard);
×
UNCOV
773
  }
×
774
}
×
775

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