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

BehaviorTree / BehaviorTree.CPP / 21671715805

04 Feb 2026 12:37PM UTC coverage: 80.192% (+0.4%) from 79.835%
21671715805

Pull #1107

github

web-flow
Merge 4b507d5bf into c7c0ea78a
Pull Request #1107: Add polymorphic shared_ptr port support (Issue #943)

193 of 213 new or added lines in 7 files covered. (90.61%)

2 existing lines in 1 file now uncovered.

4927 of 6144 relevant lines covered (80.19%)

20883.73 hits per line

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

83.61
/include/behaviortree_cpp/tree_node.h
1
/* Copyright (C) 2015-2018 Michele Colledanchise -  All Rights Reserved
2
*  Copyright (C) 2018-2025 Davide Faconti -  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
#pragma once
15

16
#include "behaviortree_cpp/basic_types.h"
17
#include "behaviortree_cpp/blackboard.h"
18
#include "behaviortree_cpp/scripting/script_parser.hpp"
19
#include "behaviortree_cpp/utils/signal.h"
20
#include "behaviortree_cpp/utils/strcat.hpp"
21
#include "behaviortree_cpp/utils/wakeup_signal.hpp"
22

23
#include <charconv>
24
#include <exception>
25
#include <map>
26
#include <utility>
27

28
#ifdef _MSC_VER
29
#pragma warning(disable : 4127)
30
#endif
31

32
namespace BT
33
{
34

35
/// This information is used mostly by the XMLParser.
36
struct TreeNodeManifest
37
{
38
  NodeType type;
39
  std::string registration_ID;
40
  PortsList ports;
41
  KeyValueVector metadata;
42
};
43

44
using PortsRemapping = std::unordered_map<std::string, std::string>;
45
using NonPortAttributes = std::unordered_map<std::string, std::string>;
46

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
 */
69
enum class PreCond
70
{
71
  FAILURE_IF = 0,
72
  SUCCESS_IF,
73
  SKIP_IF,
74
  WHILE_TRUE,
75
  COUNT_
76
};
77

78
static const std::array<std::string, 4> PreCondNames = {  //
79
  "_failureIf", "_successIf", "_skipIf", "_while"
80
};
81

82
enum class PostCond
83
{
84
  // order of the enums also tell us the execution order
85
  ON_HALTED = 0,
86
  ON_FAILURE,
87
  ON_SUCCESS,
88
  ALWAYS,
89
  COUNT_
90
};
91

92
static const std::array<std::string, 4> PostCondNames = {  //
93
  "_onHalted", "_onFailure", "_onSuccess", "_post"
94
};
95

96
template <>
97
[[nodiscard]] std::string toStr<BT::PostCond>(const BT::PostCond& cond);
98

99
template <>
100
[[nodiscard]] std::string toStr<BT::PreCond>(const BT::PreCond& cond);
101

102
using ScriptingEnumsRegistry = std::unordered_map<std::string, int>;
103

104
struct NodeConfig
105
{
106
  NodeConfig()
1,647✔
107
  {}
1,647✔
108

109
  // Pointer to the blackboard used by this node
110
  Blackboard::Ptr blackboard;
111
  // List of enums available for scripting
112
  std::shared_ptr<ScriptingEnumsRegistry> enums;
113
  // input ports
114
  PortsRemapping input_ports;
115
  // output ports
116
  PortsRemapping output_ports;
117

118
  // Any other attributes found in the xml that are not parsed as ports
119
  // or built-in identifier (e.g. anything with a leading '_')
120
  NonPortAttributes other_attributes;
121

122
  const TreeNodeManifest* manifest = nullptr;
123

124
  // Numeric unique identifier
125
  uint16_t uid = 0;
126
  // Unique human-readable name, that encapsulate the subtree
127
  // hierarchy, for instance, given 2 nested trees, it should be:
128
  //
129
  //   main_tree/nested_tree/my_action
130
  std::string path;
131

132
  std::map<PreCond, std::string> pre_conditions;
133
  std::map<PostCond, std::string> post_conditions;
134
};
135

136
// back compatibility
137
using NodeConfiguration = NodeConfig;
138

139
template <typename T>
140
inline constexpr bool hasNodeNameCtor()
141
{
142
  return std::is_constructible<T, const std::string&>::value;
143
}
144

145
template <typename T, typename... ExtraArgs>
146
inline constexpr bool hasNodeFullCtor()
147
{
148
  return std::is_constructible<T, const std::string&, const NodeConfig&,
149
                               ExtraArgs...>::value;
150
}
151

152
/// Abstract base class for Behavior Tree Nodes
153
class TreeNode
154
{
155
public:
156
  typedef std::shared_ptr<TreeNode> Ptr;
157

158
  /**
159
     * @brief TreeNode main constructor.
160
     *
161
     * @param name     name of the instance, not the type.
162
     * @param config   information about input/output ports. See NodeConfig
163
     *
164
     * Note: If your custom node has ports, the derived class must implement:
165
     *
166
     *     static PortsList providedPorts();
167
     */
168
  TreeNode(std::string name, NodeConfig config);
169

170
  TreeNode(const TreeNode& other) = delete;
171
  TreeNode& operator=(const TreeNode& other) = delete;
172

173
  TreeNode(TreeNode&& other) noexcept;
174
  TreeNode& operator=(TreeNode&& other) noexcept;
175

176
  virtual ~TreeNode();
177

178
  /// The method that should be used to invoke tick() and setStatus();
179
  virtual BT::NodeStatus executeTick();
180

181
  void haltNode();
182

183
  [[nodiscard]] bool isHalted() const;
184

185
  [[nodiscard]] NodeStatus status() const;
186

187
  /// Name of the instance, not the type
188
  [[nodiscard]] const std::string& name() const;
189

190
  /// Blocking function that will sleep until the setStatus() is called with
191
  /// either RUNNING, FAILURE or SUCCESS.
192
  [[nodiscard]] BT::NodeStatus waitValidStatus();
193

194
  virtual NodeType type() const = 0;
195

196
  using StatusChangeSignal = Signal<TimePoint, const TreeNode&, NodeStatus, NodeStatus>;
197
  using StatusChangeSubscriber = StatusChangeSignal::Subscriber;
198
  using StatusChangeCallback = StatusChangeSignal::CallableFunction;
199

200
  using PreTickCallback = std::function<NodeStatus(TreeNode&)>;
201
  using PostTickCallback = std::function<NodeStatus(TreeNode&, NodeStatus)>;
202
  using TickMonitorCallback =
203
      std::function<void(TreeNode&, NodeStatus, std::chrono::microseconds)>;
204

205
  /**
206
     * @brief subscribeToStatusChange is used to attach a callback to a status change.
207
     * When StatusChangeSubscriber goes out of scope (it is a shared_ptr) the callback
208
     * is unsubscribed automatically.
209
     *
210
     * @param callback The callback to be execute when status change.
211
     *
212
     * @return the subscriber handle.
213
     */
214
  [[nodiscard]] StatusChangeSubscriber
215
  subscribeToStatusChange(StatusChangeCallback callback);
216

217
  /** This method attaches to the TreeNode a callback with signature:
218
     *
219
     *     NodeStatus callback(TreeNode& node)
220
     *
221
     * This callback is executed BEFORE the tick() and, if it returns SUCCESS or FAILURE,
222
     * the actual tick() will NOT be executed and this result will be returned instead.
223
     *
224
     * This is useful to inject a "dummy" implementation of the TreeNode at run-time
225
     */
226
  void setPreTickFunction(PreTickCallback callback);
227

228
  /**
229
   * This method attaches to the TreeNode a callback with signature:
230
   *
231
   *     NodeStatus myCallback(TreeNode& node, NodeStatus status)
232
   *
233
   * This callback is executed AFTER the tick() and, if it returns SUCCESS or FAILURE,
234
   * the value returned by the actual tick() is overridden with this one.
235
   */
236
  void setPostTickFunction(PostTickCallback callback);
237

238
  /**
239
   * This method attaches to the TreeNode a callback with signature:
240
   *
241
   *     void myCallback(TreeNode& node, NodeStatus status, std::chrono::microseconds duration)
242
   *
243
   * This callback is executed AFTER the tick() and will inform the user about its status and
244
   * the execution time. Works only if the tick was not substituted by a pre-condition.
245
   */
246
  void setTickMonitorCallback(TickMonitorCallback callback);
247

248
  /// The unique identifier of this instance of treeNode.
249
  /// It is assigneld by the factory
250
  [[nodiscard]] uint16_t UID() const;
251

252
  /// Human readable identifier, that includes the hierarchy of Subtrees
253
  /// See tutorial 10 as an example.
254
  [[nodiscard]] const std::string& fullPath() const;
255

256
  /// registrationName is the ID used by BehaviorTreeFactory to create an instance.
257
  [[nodiscard]] const std::string& registrationName() const;
258

259
  /// Configuration passed at construction time. Can never change after the
260
  /// creation of the TreeNode instance.
261
  [[nodiscard]] const NodeConfig& config() const;
262

263
  /** Read an input port, which, in practice, is an entry in the blackboard.
264
   * If the blackboard contains a std::string and T is not a string,
265
   * convertFromString<T>() is used automatically to parse the text.
266
   *
267
   * @param key   the name of the port.
268
   * @param destination  reference to the object where the value should be stored
269
   * @return      false if an error occurs.
270
   */
271
  template <typename T>
272
  Result getInput(const std::string& key, T& destination) const;
273

274
  /**
275
   * @brief getInputStamped is similar to getInput(dey, destination),
276
   * but it returns also the Timestamp object, that can be used to check if
277
   * a value was updated and when.
278
   *
279
   * @param key   the name of the port.
280
   * @param destination  reference to the object where the value should be stored
281
   */
282
  template <typename T>
283
  [[nodiscard]] Expected<Timestamp> getInputStamped(const std::string& key,
284
                                                    T& destination) const;
285

286
  /** Same as bool getInput(const std::string& key, T& destination)
287
   * but using optional.
288
   *
289
   * @param key   the name of the port.
290
   */
291
  template <typename T>
292
  [[nodiscard]] Expected<T> getInput(const std::string& key) const
119✔
293
  {
294
    T out{};
119✔
295
    auto res = getInput(key, out);
119✔
296
    return (res) ? Expected<T>(out) : nonstd::make_unexpected(res.error());
238✔
297
  }
119✔
298

299
  /** Same as bool getInputStamped(const std::string& key, T& destination)
300
   * but return StampedValue<T>
301
   *
302
   * @param key   the name of the port.
303
   */
304
  template <typename T>
305
  [[nodiscard]] Expected<StampedValue<T>> getInputStamped(const std::string& key) const
306
  {
307
    StampedValue<T> out;
308
    if(auto res = getInputStamped(key, out.value))
309
    {
310
      out.stamp = *res;
311
      return out;
312
    }
313
    else
314
    {
315
      return nonstd::make_unexpected(res.error());
316
    }
317
  }
318

319
  /**
320
   * @brief setOutput modifies the content of an Output port
321
   * @param key    the name of the port.
322
   * @param value  new value
323
   * @return       valid Result, if successful.
324
   */
325
  template <typename T>
326
  Result setOutput(const std::string& key, const T& value);
327

328
  /**
329
   * @brief getLockedPortContent should be used when:
330
   *
331
   * - your port contains an object with reference semantic (usually a smart pointer)
332
   * - you want to modify the object we are pointing to.
333
   * - you are concerned about thread-safety.
334
   *
335
   * For example, if your port has type std::shared_ptr<Foo>,
336
   * the code below is NOT thread safe:
337
   *
338
   *    auto foo_ptr = getInput<std::shared_ptr<Foo>>("port_name");
339
   *    // modifying the content of foo_ptr is NOT thread-safe
340
   *
341
   * What you must do, instead, to guaranty thread-safety, is:
342
   *
343
   *    if(auto any_ref = getLockedPortContent("port_name")) {
344
   *      Any* any = any_ref.get();
345
   *      auto foo_ptr = any->cast<std::shared_ptr<Foo>>();
346
   *      // modifying the content of foo_ptr inside this scope IS thread-safe
347
   *    }
348
   *
349
   * It is important to destroy the object AnyPtrLocked, to release the lock.
350
   *
351
   * NOTE: this method doesn't work, if the port contains a static string, instead
352
   * of a blackboard pointer.
353
   *
354
   * @param key  the identifier of the port.
355
   * @return     empty AnyPtrLocked if the blackboard entry doesn't exist or the content
356
   *             of the port was a static string.
357
   */
358
  [[nodiscard]] AnyPtrLocked getLockedPortContent(const std::string& key);
359

360
  // function provided mostly for debugging purpose to see the raw value
361
  // in the port (no remapping and no conversion to a type)
362
  [[nodiscard]] StringView getRawPortValue(const std::string& key) const;
363

364
  /// Check a string and return true if it matches the pattern:  {...}
365
  [[nodiscard]] static bool isBlackboardPointer(StringView str,
366
                                                StringView* stripped_pointer = nullptr);
367

368
  [[nodiscard]] static StringView stripBlackboardPointer(StringView str);
369

370
  [[nodiscard]] static Expected<StringView> getRemappedKey(StringView port_name,
371
                                                           StringView remapped_port);
372

373
  /// Notify that the tree should be ticked again()
374
  void emitWakeUpSignal();
375

376
  [[nodiscard]] bool requiresWakeUp() const;
377

378
  /** Used to inject config into a node, even if it doesn't have the proper
379
     *  constructor
380
     */
381
  template <class DerivedT, typename... ExtraArgs>
382
  static std::unique_ptr<TreeNode> Instantiate(const std::string& name,
812✔
383
                                               const NodeConfig& config,
384
                                               ExtraArgs... args)
385
  {
386
    static_assert(hasNodeFullCtor<DerivedT, ExtraArgs...>() ||
387
                  hasNodeNameCtor<DerivedT>());
388

389
    if constexpr(hasNodeFullCtor<DerivedT, ExtraArgs...>())
390
    {
391
      return std::make_unique<DerivedT>(name, config, args...);
383✔
392
    }
393
    else if constexpr(hasNodeNameCtor<DerivedT>())
394
    {
395
      auto node_ptr = std::make_unique<DerivedT>(name, args...);
429✔
396
      node_ptr->config() = config;
429✔
397
      return node_ptr;
858✔
398
    }
429✔
399
  }
400

401
protected:
402
  friend class BehaviorTreeFactory;
403
  friend class DecoratorNode;
404
  friend class ControlNode;
405
  friend class Tree;
406

407
  [[nodiscard]] NodeConfig& config();
408

409
  /// Method to be implemented by the user
410
  virtual BT::NodeStatus tick() = 0;
411

412
  /// Set the status to IDLE
413
  void resetStatus();
414

415
  // Only BehaviorTreeFactory should call this
416
  void setRegistrationID(StringView ID);
417

418
  void setWakeUpInstance(std::shared_ptr<WakeUpSignal> instance);
419

420
  void modifyPortsRemapping(const PortsRemapping& new_remapping);
421

422
  /**
423
     * @brief setStatus changes the status of the node.
424
     * it will throw if you try to change the status to IDLE, because
425
     * your parent node should do that, not the user!
426
     */
427
  void setStatus(NodeStatus new_status);
428

429
  using PreScripts = std::array<ScriptFunction, size_t(PreCond::COUNT_)>;
430
  using PostScripts = std::array<ScriptFunction, size_t(PostCond::COUNT_)>;
431

432
  PreScripts& preConditionsScripts();
433
  PostScripts& postConditionsScripts();
434

435
  template <typename T>
436
  T parseString(const std::string& str) const;
437

438
private:
439
  struct PImpl;
440
  std::unique_ptr<PImpl> _p;
441

442
  Expected<NodeStatus> checkPreConditions();
443
  void checkPostConditions(NodeStatus status);
444

445
  /// The method used to interrupt the execution of a RUNNING node.
446
  /// Only Async nodes that may return RUNNING should implement it.
447
  virtual void halt() = 0;
448
};
449

450
//-------------------------------------------------------
451

452
template <typename T>
453
T TreeNode::parseString(const std::string& str) const
89✔
454
{
455
  if constexpr(std::is_enum_v<T> && !std::is_same_v<T, NodeStatus>)
456
  {
457
    // Check the ScriptingEnumsRegistry first, if available.
458
    if(config().enums)
4✔
459
    {
460
      auto it = config().enums->find(str);
4✔
461
      if(it != config().enums->end())
4✔
462
      {
463
        return static_cast<T>(it->second);
1✔
464
      }
465
    }
466
    // Try numeric conversion (e.g. "2" for an enum value).
467
    int tmp = 0;
3✔
468
    auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), tmp);
3✔
469
    if(ec == std::errc() && ptr == str.data() + str.size())
3✔
470
    {
471
      return static_cast<T>(tmp);
1✔
472
    }
473
    // Fall back to convertFromString<T>, which uses a user-provided
474
    // specialization if one exists. Issue #948.
475
    return convertFromString<T>(str);
2✔
476
  }
477
  return convertFromString<T>(str);
85✔
478
}
479

480
template <typename T>
481
inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
573,582✔
482
                                                     T& destination) const
483
{
484
  std::string port_value_str;
573,582✔
485

486
  auto input_port_it = config().input_ports.find(key);
573,582✔
487
  if(input_port_it != config().input_ports.end())
573,582✔
488
  {
489
    port_value_str = input_port_it->second;
1,576✔
490
  }
491
  else if(!config().manifest)
572,006✔
492
  {
493
    return nonstd::make_unexpected(StrCat("getInput() of node '", fullPath(),
572,004✔
494
                                          "' failed because the manifest is "
495
                                          "nullptr (WTF?) and the key: [",
496
                                          key, "] is missing"));
572,004✔
497
  }
498
  else
499
  {
500
    // maybe it is declared with a default value in the manifest
501
    auto port_manifest_it = config().manifest->ports.find(key);
2✔
502
    if(port_manifest_it == config().manifest->ports.end())
2✔
503
    {
504
      return nonstd::make_unexpected(StrCat("getInput() of node '", fullPath(),
×
505
                                            "' failed because the manifest doesn't "
506
                                            "contain the key: [",
507
                                            key, "]"));
×
508
    }
509
    const auto& port_info = port_manifest_it->second;
2✔
510
    // there is a default value
511
    if(port_info.defaultValue().empty())
2✔
512
    {
513
      return nonstd::make_unexpected(StrCat("getInput() of node '", fullPath(),
2✔
514
                                            "' failed because nor the manifest or the "
515
                                            "XML contain the key: [",
516
                                            key, "]"));
2✔
517
    }
518
    if(port_info.defaultValue().isString())
×
519
    {
520
      port_value_str = port_info.defaultValue().cast<std::string>();
×
521
    }
522
    else
523
    {
524
      destination = port_info.defaultValue().cast<T>();
×
525
      return Timestamp{};
×
526
    }
527
  }
528

529
  // Helper lambda to parse string using the stored converter if available,
530
  // otherwise fall back to convertFromString<T>. This fixes the plugin issue
531
  // where convertFromString<T> specializations are not visible across shared
532
  // library boundaries (issue #953).
533
  auto parseStringWithConverter = [this, &key](const std::string& str) -> T {
3,046✔
534
    if(config().manifest)
1,470✔
535
    {
536
      auto port_it = config().manifest->ports.find(key);
1,444✔
537
      if(port_it != config().manifest->ports.end())
1,444✔
538
      {
539
        const auto& converter = port_it->second.converter();
1,444✔
540
        if(converter)
1,444✔
541
        {
542
          return converter(str).template cast<T>();
1,381✔
543
        }
544
      }
545
    }
546
    // Fall back to parseString which calls convertFromString
547
    return parseString<T>(str);
89✔
548
  };
549

550
  auto blackboard_ptr = getRemappedKey(key, port_value_str);
1,576✔
551
  try
552
  {
553
    // pure string, not a blackboard key
554
    if(!blackboard_ptr)
1,576✔
555
    {
556
      try
557
      {
558
        destination = parseStringWithConverter(port_value_str);
1,464✔
559
      }
560
      catch(std::exception& ex)
×
561
      {
562
        return nonstd::make_unexpected(StrCat("getInput(): ", ex.what()));
×
563
      }
564
      return Timestamp{};
1,464✔
565
    }
566
    const auto& blackboard_key = blackboard_ptr.value();
112✔
567

568
    if(!config().blackboard)
112✔
569
    {
570
      return nonstd::make_unexpected("getInput(): trying to access "
×
571
                                     "an invalid Blackboard");
×
572
    }
573

574
    if(auto entry = config().blackboard->getEntry(std::string(blackboard_key)))
446✔
575
    {
576
      std::unique_lock lk(entry->entry_mutex);
110✔
577
      auto& any_value = entry->value;
110✔
578

579
      // support getInput<Any>()
580
      if constexpr(std::is_same_v<T, Any>)
581
      {
582
        destination = any_value;
583
        return Timestamp{ entry->sequence_id, entry->stamp };
584
      }
585

586
      if(!entry->value.empty())
110✔
587
      {
588
        if(!std::is_same_v<T, std::string> && any_value.isString())
59✔
589
        {
590
          destination = parseStringWithConverter(any_value.cast<std::string>());
6✔
591
        }
592
        else
593
        {
594
          auto result = any_value.tryCast<T>();
101✔
595
          if(result)
101✔
596
          {
597
            destination = result.value();
99✔
598
          }
599
          else if constexpr(is_shared_ptr<T>::value)
600
          {
601
            // Try polymorphic cast via registry (Issue #943)
602
            if(auto* registry = config().blackboard->polymorphicCastRegistry())
2✔
603
            {
604
              auto poly_result = any_value.tryCastWithRegistry<T>(*registry);
2✔
605
              if(poly_result)
2✔
606
              {
607
                destination = poly_result.value();
2✔
608
              }
609
              else
610
              {
NEW
611
                throw std::runtime_error(result.error());
×
612
              }
613
            }
2✔
614
            else
615
            {
NEW
616
              throw std::runtime_error(result.error());
×
617
            }
618
          }
619
          else
620
          {
NEW
621
            throw std::runtime_error(result.error());
×
622
          }
623
        }
101✔
624
        return Timestamp{ entry->sequence_id, entry->stamp };
107✔
625
      }
626
    }
110✔
627

628
    return nonstd::make_unexpected(StrCat("getInput() failed because it was unable to "
5✔
629
                                          "find the key [",
630
                                          key, "] remapped to [", blackboard_key, "]"));
5✔
631
  }
632
  catch(std::exception& err)
×
633
  {
634
    return nonstd::make_unexpected(err.what());
×
635
  }
636
}
573,582✔
637

638
template <typename T>
639
inline Result TreeNode::getInput(const std::string& key, T& destination) const
573,582✔
640
{
641
  auto res = getInputStamped(key, destination);
573,582✔
642
  if(!res)
573,582✔
643
  {
644
    return nonstd::make_unexpected(res.error());
572,011✔
645
  }
646
  return {};
1,571✔
647
}
573,582✔
648

649
template <typename T>
650
inline Result TreeNode::setOutput(const std::string& key, const T& value)
83✔
651
{
652
  if(!config().blackboard)
83✔
653
  {
654
    return nonstd::make_unexpected("setOutput() failed: trying to access a "
2✔
655
                                   "Blackboard(BB) entry, but BB is invalid");
1✔
656
  }
657

658
  auto remap_it = config().output_ports.find(key);
82✔
659
  if(remap_it == config().output_ports.end())
82✔
660
  {
661
    return nonstd::make_unexpected(StrCat("setOutput() failed: "
×
662
                                          "NodeConfig::output_ports "
663
                                          "does not contain the key: [",
664
                                          key, "]"));
×
665
  }
666
  StringView remapped_key = remap_it->second;
82✔
667
  if(remapped_key == "{=}" || remapped_key == "=")
82✔
668
  {
669
    config().blackboard->set(static_cast<std::string>(key), value);
2✔
670
    return {};
2✔
671
  }
672

673
  if(!isBlackboardPointer(remapped_key))
80✔
674
  {
675
    return nonstd::make_unexpected("setOutput requires a blackboard pointer. Use {}");
1✔
676
  }
677

678
  if constexpr(std::is_same_v<BT::Any, T>)
679
  {
680
    auto port_type = config().manifest->ports.at(key).type();
×
681
    if(port_type != typeid(BT::Any) && port_type != typeid(BT::AnyTypeAllowed))
×
682
    {
683
      throw LogicError("setOutput<Any> is not allowed, unless the port "
×
684
                       "was declared using OutputPort<Any>");
685
    }
686
  }
687

688
  remapped_key = stripBlackboardPointer(remapped_key);
79✔
689
  config().blackboard->set(static_cast<std::string>(remapped_key), value);
158✔
690

691
  return {};
79✔
692
}
693

694
// Utility function to fill the list of ports using T::providedPorts();
695
template <typename T>
696
inline void assignDefaultRemapping(NodeConfig& config)
5✔
697
{
698
  for(const auto& it : getProvidedPorts<T>())
7✔
699
  {
700
    const auto& port_name = it.first;
2✔
701
    const auto direction = it.second.direction();
2✔
702
    if(direction != PortDirection::OUTPUT)
2✔
703
    {
704
      // PortDirection::{INPUT,INOUT}
705
      config.input_ports[port_name] = "{=}";
1✔
706
    }
707
    if(direction != PortDirection::INPUT)
2✔
708
    {
709
      // PortDirection::{OUTPUT,INOUT}
710
      config.output_ports[port_name] = "{=}";
1✔
711
    }
712
  }
713
}
5✔
714

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