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

BehaviorTree / BehaviorTree.CPP / 21673172039

04 Feb 2026 01:24PM UTC coverage: 80.205% (+0.4%) from 79.835%
21673172039

Pull #1107

github

web-flow
Merge d29dc428e into facf5a0f4
Pull Request #1107: Add polymorphic shared_ptr port support (Issue #943)

200 of 220 new or added lines in 7 files covered. (90.91%)

2 existing lines in 1 file now uncovered.

4931 of 6148 relevant lines covered (80.2%)

21416.24 hits per line

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

91.22
/include/behaviortree_cpp/utils/polymorphic_cast_registry.hpp
1
/* Copyright (C) 2022-2025 Davide Faconti -  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
#pragma once
14

15
#include "behaviortree_cpp/contrib/any.hpp"
16

17
#include <algorithm>
18
#include <functional>
19
#include <map>
20
#include <memory>
21
#include <mutex>
22
#include <set>
23
#include <typeindex>
24

25
namespace BT
26
{
27

28
/**
29
 * @brief Registry for polymorphic shared_ptr cast relationships.
30
 *
31
 * This enables passing shared_ptr<Derived> to ports expecting shared_ptr<Base>
32
 * without breaking ABI compatibility. Users register inheritance relationships
33
 * at runtime, and the registry handles upcasting/downcasting transparently.
34
 *
35
 * This class is typically owned by BehaviorTreeFactory and passed to Blackboard
36
 * during tree creation. This avoids global state and makes testing easier.
37
 *
38
 * Usage with BehaviorTreeFactory:
39
 *   BehaviorTreeFactory factory;
40
 *   factory.registerPolymorphicCast<Cat, Animal>();
41
 *   factory.registerPolymorphicCast<Sphynx, Cat>();
42
 *   auto tree = factory.createTreeFromText(xml);
43
 */
44
class PolymorphicCastRegistry
45
{
46
public:
47
  using CastFunction = std::function<linb::any(const linb::any&)>;
48

49
  PolymorphicCastRegistry() = default;
301✔
50
  ~PolymorphicCastRegistry() = default;
301✔
51

52
  // Non-copyable, non-movable (contains mutex)
53
  PolymorphicCastRegistry(const PolymorphicCastRegistry&) = delete;
54
  PolymorphicCastRegistry& operator=(const PolymorphicCastRegistry&) = delete;
55
  PolymorphicCastRegistry(PolymorphicCastRegistry&&) = delete;
56
  PolymorphicCastRegistry& operator=(PolymorphicCastRegistry&&) = delete;
57

58
  /**
59
   * @brief Register a Derived -> Base inheritance relationship.
60
   *
61
   * This enables:
62
   * - Upcasting: shared_ptr<Derived> can be retrieved as shared_ptr<Base>
63
   * - Downcasting: shared_ptr<Base> can be retrieved as shared_ptr<Derived>
64
   *   (via dynamic_pointer_cast, may return nullptr if types don't match)
65
   *
66
   * @tparam Derived The derived class (must inherit from Base)
67
   * @tparam Base The base class (must be polymorphic - have virtual functions)
68
   */
69
  template <typename Derived, typename Base>
70
  void registerCast()
15✔
71
  {
72
    static_assert(std::is_base_of_v<Base, Derived>, "Derived must inherit from Base");
73
    static_assert(std::is_polymorphic_v<Base>, "Base must be polymorphic (have virtual "
74
                                               "functions)");
75

76
    std::lock_guard<std::mutex> lock(mutex_);
15✔
77

78
    // Register upcast: Derived -> Base
79
    auto upcast_key = std::make_pair(std::type_index(typeid(std::shared_ptr<Derived>)),
15✔
80
                                     std::type_index(typeid(std::shared_ptr<Base>)));
15✔
81

82
    upcasts_[upcast_key] = [](const linb::any& from) -> linb::any {
26✔
83
      auto ptr = linb::any_cast<std::shared_ptr<Derived>>(from);
11✔
84
      return std::static_pointer_cast<Base>(ptr);
22✔
85
    };
11✔
86

87
    // Register downcast: Base -> Derived (uses dynamic_pointer_cast)
88
    auto downcast_key = std::make_pair(std::type_index(typeid(std::shared_ptr<Base>)),
15✔
89
                                       std::type_index(typeid(std::shared_ptr<Derived>)));
15✔
90

91
    downcasts_[downcast_key] = [](const linb::any& from) -> linb::any {
20✔
92
      auto ptr = linb::any_cast<std::shared_ptr<Base>>(from);
5✔
93
      auto derived = std::dynamic_pointer_cast<Derived>(ptr);
5✔
94
      if(!derived)
5✔
95
      {
96
        throw std::bad_cast();
4✔
97
      }
98
      return derived;
2✔
99
    };
9✔
100

101
    // Track inheritance relationship for port compatibility checks
102
    base_types_[std::type_index(typeid(std::shared_ptr<Derived>))].insert(
30✔
103
        std::type_index(typeid(std::shared_ptr<Base>)));
15✔
104
  }
15✔
105

106
  /**
107
   * @brief Check if from_type can be converted to to_type.
108
   *
109
   * Returns true if:
110
   * - from_type == to_type
111
   * - from_type is a registered derived type of to_type (upcast)
112
   * - to_type is a registered derived type of from_type (downcast)
113
   */
114
  [[nodiscard]] bool isConvertible(std::type_index from_type,
115
                                   std::type_index to_type) const
116
  {
117
    if(from_type == to_type)
118
    {
119
      return true;
120
    }
121

122
    std::lock_guard<std::mutex> lock(mutex_);
123

124
    // Check direct upcast
125
    auto upcast_key = std::make_pair(from_type, to_type);
126
    if(upcasts_.find(upcast_key) != upcasts_.end())
127
    {
128
      return true;
129
    }
130

131
    // Check transitive upcast (e.g., Sphynx -> Cat -> Animal)
132
    if(canUpcastTransitive(from_type, to_type))
133
    {
134
      return true;
135
    }
136

137
    // Check downcast
138
    auto downcast_key = std::make_pair(from_type, to_type);
139
    if(downcasts_.find(downcast_key) != downcasts_.end())
140
    {
141
      return true;
142
    }
143

144
    return false;
145
  }
146

147
  /**
148
   * @brief Check if from_type can be UPCAST to to_type (not downcast).
149
   *
150
   * This is stricter than isConvertible - only allows going from
151
   * derived to base, not the reverse.
152
   */
153
  [[nodiscard]] bool canUpcast(std::type_index from_type, std::type_index to_type) const
8✔
154
  {
155
    if(from_type == to_type)
8✔
156
    {
NEW
157
      return true;
×
158
    }
159

160
    std::lock_guard<std::mutex> lock(mutex_);
8✔
161
    return canUpcastTransitive(from_type, to_type);
8✔
162
  }
8✔
163

164
  /**
165
   * @brief Attempt to cast the value to the target type.
166
   *
167
   * @param from The source any containing a shared_ptr
168
   * @param from_type The type_index of the stored type
169
   * @param to_type The target type_index
170
   * @return The casted any, or empty any if cast fails
171
   */
172
  [[nodiscard]] linb::any tryCast(const linb::any& from, std::type_index from_type,
15✔
173
                                  std::type_index to_type) const
174
  {
175
    if(from_type == to_type)
15✔
176
    {
NEW
177
      return from;
×
178
    }
179

180
    std::lock_guard<std::mutex> lock(mutex_);
15✔
181

182
    // Try direct upcast
183
    auto upcast_key = std::make_pair(from_type, to_type);
15✔
184
    auto upcast_it = upcasts_.find(upcast_key);
15✔
185
    if(upcast_it != upcasts_.end())
15✔
186
    {
187
      try
188
      {
189
        return upcast_it->second(from);
5✔
190
      }
NEW
191
      catch(...)
×
192
      {
NEW
193
        return linb::any{};
×
NEW
194
      }
×
195
    }
196

197
    // Try transitive upcast
198
    auto transitive_result = tryTransitiveUpcast(from, from_type, to_type);
10✔
199
    if(!transitive_result.empty())
10✔
200
    {
201
      return transitive_result;
3✔
202
    }
203

204
    // Try direct downcast
205
    auto downcast_key = std::make_pair(from_type, to_type);
7✔
206
    auto downcast_it = downcasts_.find(downcast_key);
7✔
207
    if(downcast_it != downcasts_.end())
7✔
208
    {
209
      try
210
      {
211
        return downcast_it->second(from);
4✔
212
      }
213
      catch(...)
3✔
214
      {
215
        return linb::any{};
3✔
216
      }
3✔
217
    }
218

219
    // Try transitive downcast
220
    auto transitive_down_result = tryTransitiveDowncast(from, from_type, to_type);
3✔
221
    if(!transitive_down_result.empty())
3✔
222
    {
NEW
223
      return transitive_down_result;
×
224
    }
225

226
    return linb::any{};
3✔
227
  }
15✔
228

229
  /**
230
   * @brief Get all registered base types for a given type.
231
   */
232
  [[nodiscard]] std::set<std::type_index> getBaseTypes(std::type_index type) const
233
  {
234
    std::lock_guard<std::mutex> lock(mutex_);
235
    auto it = base_types_.find(type);
236
    if(it != base_types_.end())
237
    {
238
      return it->second;
239
    }
240
    return {};
241
  }
242

243
  /**
244
   * @brief Clear all registrations (mainly for testing).
245
   */
246
  void clear()
247
  {
248
    std::lock_guard<std::mutex> lock(mutex_);
249
    upcasts_.clear();
250
    downcasts_.clear();
251
    base_types_.clear();
252
  }
253

254
private:
255
  // Check if we can upcast from_type to to_type through a chain of registered casts
256
  [[nodiscard]] bool canUpcastTransitive(std::type_index from_type,
8✔
257
                                         std::type_index to_type) const
258
  {
259
    // BFS to find a path from from_type to to_type
260
    std::set<std::type_index> visited;
8✔
261
    std::vector<std::type_index> queue;
8✔
262
    queue.push_back(from_type);
8✔
263

264
    while(!queue.empty())
15✔
265
    {
266
      auto current = queue.back();
9✔
267
      queue.pop_back();
9✔
268

269
      if(visited.count(current) != 0)
9✔
270
      {
271
        continue;
6✔
272
      }
273
      visited.insert(current);
9✔
274

275
      auto it = base_types_.find(current);
9✔
276
      if(it == base_types_.end())
9✔
277
      {
278
        continue;
6✔
279
      }
280

281
      for(const auto& base : it->second)
4✔
282
      {
283
        if(base == to_type)
3✔
284
        {
285
          return true;
2✔
286
        }
287
        queue.push_back(base);
1✔
288
      }
289
    }
290
    return false;
6✔
291
  }
8✔
292

293
  // Try to perform a transitive upcast
294
  [[nodiscard]] linb::any tryTransitiveUpcast(const linb::any& from,
10✔
295
                                              std::type_index from_type,
296
                                              std::type_index to_type) const
297
  {
298
    // Find path from from_type to to_type using BFS
299
    // Note: std::type_index has no default constructor, so we can't use operator[]
300
    std::map<std::type_index, std::type_index> parent;
10✔
301
    std::vector<std::type_index> queue;
10✔
302
    queue.push_back(from_type);
10✔
303
    parent.insert({ from_type, from_type });
10✔
304

305
    while(!queue.empty())
25✔
306
    {
307
      auto current = queue.back();
18✔
308
      queue.pop_back();
18✔
309

310
      auto it = base_types_.find(current);
18✔
311
      if(it == base_types_.end())
18✔
312
      {
313
        continue;
7✔
314
      }
315

316
      for(const auto& base : it->second)
19✔
317
      {
318
        if(parent.find(base) != parent.end())
11✔
319
        {
NEW
320
          continue;
×
321
        }
322
        parent.insert({ base, current });
11✔
323
        if(base == to_type)
11✔
324
        {
325
          // Found path, reconstruct and apply casts
326
          std::vector<std::type_index> path;
3✔
327
          auto node = to_type;
3✔
328
          while(node != from_type)
9✔
329
          {
330
            path.push_back(node);
6✔
331
            node = parent.at(node);
6✔
332
          }
333
          path.push_back(from_type);
3✔
334
          std::reverse(path.begin(), path.end());
3✔
335

336
          // Apply casts along the path
337
          linb::any current_value = from;
3✔
338
          for(size_t i = 0; i + 1 < path.size(); ++i)
9✔
339
          {
340
            auto cast_key = std::make_pair(path[i], path[i + 1]);
6✔
341
            auto cast_it = upcasts_.find(cast_key);
6✔
342
            if(cast_it == upcasts_.end())
6✔
343
            {
NEW
344
              return linb::any{};
×
345
            }
346
            try
347
            {
348
              current_value = cast_it->second(current_value);
6✔
349
            }
NEW
350
            catch(...)
×
351
            {
NEW
352
              return linb::any{};
×
NEW
353
            }
×
354
          }
355
          return current_value;
3✔
356
        }
3✔
357
        queue.push_back(base);
8✔
358
      }
359
    }
360
    return linb::any{};
7✔
361
  }
10✔
362

363
  // Try to perform a transitive downcast
364
  [[nodiscard]] linb::any tryTransitiveDowncast(const linb::any& from,
3✔
365
                                                std::type_index from_type,
366
                                                std::type_index to_type) const
367
  {
368
    // For downcast, we need to find if to_type has from_type as an ancestor
369
    // Then apply downcasts in reverse order
370
    // Note: std::type_index has no default constructor, so we can't use operator[]
371
    std::map<std::type_index, std::type_index> parent;
3✔
372
    std::vector<std::type_index> queue;
3✔
373
    queue.push_back(to_type);
3✔
374
    parent.insert({ to_type, to_type });
3✔
375

376
    while(!queue.empty())
8✔
377
    {
378
      auto current = queue.back();
6✔
379
      queue.pop_back();
6✔
380

381
      auto it = base_types_.find(current);
6✔
382
      if(it == base_types_.end())
6✔
383
      {
384
        continue;
2✔
385
      }
386

387
      for(const auto& base : it->second)
7✔
388
      {
389
        if(parent.find(base) != parent.end())
4✔
390
        {
NEW
391
          continue;
×
392
        }
393
        parent.insert({ base, current });
4✔
394
        if(base == from_type)
4✔
395
        {
396
          // Found path, reconstruct and apply downcasts
397
          std::vector<std::type_index> path;
1✔
398
          auto node = from_type;
1✔
399
          while(node != to_type)
3✔
400
          {
401
            path.push_back(node);
2✔
402
            node = parent.at(node);
2✔
403
          }
404
          path.push_back(to_type);
1✔
405

406
          // Apply downcasts along the path
407
          linb::any current_value = from;
1✔
408
          for(size_t i = 0; i + 1 < path.size(); ++i)
1✔
409
          {
410
            auto cast_key = std::make_pair(path[i], path[i + 1]);
1✔
411
            auto cast_it = downcasts_.find(cast_key);
1✔
412
            if(cast_it == downcasts_.end())
1✔
413
            {
414
              return linb::any{};
1✔
415
            }
416
            try
417
            {
418
              current_value = cast_it->second(current_value);
1✔
419
            }
420
            catch(...)
1✔
421
            {
422
              return linb::any{};
1✔
423
            }
1✔
424
          }
NEW
425
          return current_value;
×
426
        }
1✔
427
        queue.push_back(base);
3✔
428
      }
429
    }
430
    return linb::any{};
2✔
431
  }
3✔
432

433
  mutable std::mutex mutex_;
434
  std::map<std::pair<std::type_index, std::type_index>, CastFunction> upcasts_;
435
  std::map<std::pair<std::type_index, std::type_index>, CastFunction> downcasts_;
436
  std::map<std::type_index, std::set<std::type_index>> base_types_;
437
};
438

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