Skip to content

Commit 4b507d5

Browse files
facontidavideclaude
andcommitted
Add polymorphic shared_ptr port support (Issue #943)
Enable passing shared_ptr<Derived> to ports expecting shared_ptr<Base> while preserving ABI and API compatibility. Implementation: - Add PolymorphicCastRegistry class for runtime cast registration - Store registry in BehaviorTreeFactory via PImpl (ABI-safe) - Propagate registry to blackboards via shared_ptr (tree outlives factory) - Add polymorphic cast fallback in Blackboard::get() and TreeNode::getInputStamped() - Add port type validation with polymorphic awareness in XML parser API: - factory.registerPolymorphicCast<Derived, Base>() to register relationships - Supports transitive upcasts (Sphynx -> Cat -> Animal) - Compile-time port type checking still catches invalid connections Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b9bc25c commit 4b507d5

File tree

10 files changed

+1150
-6
lines changed

10 files changed

+1150
-6
lines changed

include/behaviortree_cpp/blackboard.h

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "behaviortree_cpp/contrib/json.hpp"
55
#include "behaviortree_cpp/exceptions.h"
66
#include "behaviortree_cpp/utils/locked_reference.hpp"
7+
#include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp"
78
#include "behaviortree_cpp/utils/safe_any.hpp"
89

910
#include <memory>
@@ -149,6 +150,25 @@ class Blackboard
149150

150151
const Blackboard* rootBlackboard() const;
151152

153+
/**
154+
* @brief Set the polymorphic cast registry for this blackboard.
155+
*
156+
* The registry enables polymorphic shared_ptr conversions during get().
157+
* This is typically set automatically when creating trees via BehaviorTreeFactory.
158+
*/
159+
void setPolymorphicCastRegistry(std::shared_ptr<PolymorphicCastRegistry> registry)
160+
{
161+
polymorphic_registry_ = std::move(registry);
162+
}
163+
164+
/**
165+
* @brief Get the polymorphic cast registry (may be null).
166+
*/
167+
[[nodiscard]] const PolymorphicCastRegistry* polymorphicCastRegistry() const
168+
{
169+
return polymorphic_registry_.get();
170+
}
171+
152172
private:
153173
mutable std::mutex storage_mutex_;
154174
mutable std::recursive_mutex entry_mutex_;
@@ -159,6 +179,9 @@ class Blackboard
159179
std::shared_ptr<Entry> createEntryImpl(const std::string& key, const TypeInfo& info);
160180

161181
bool autoremapping_ = false;
182+
183+
// Optional registry for polymorphic shared_ptr conversions
184+
std::shared_ptr<PolymorphicCastRegistry> polymorphic_registry_;
162185
};
163186

164187
/**
@@ -188,7 +211,28 @@ inline T Blackboard::get(const std::string& key) const
188211
throw RuntimeError("Blackboard::get() error. Entry [", key,
189212
"] hasn't been initialized, yet");
190213
}
191-
return any_ref.get()->cast<T>();
214+
// Try direct cast first
215+
auto result = any->tryCast<T>();
216+
if(result)
217+
{
218+
return result.value();
219+
}
220+
221+
// For shared_ptr types, try polymorphic cast via registry (Issue #943)
222+
if constexpr(is_shared_ptr<T>::value)
223+
{
224+
if(polymorphic_registry_)
225+
{
226+
auto poly_result = any->tryCastWithRegistry<T>(*polymorphic_registry_);
227+
if(poly_result)
228+
{
229+
return poly_result.value();
230+
}
231+
}
232+
}
233+
234+
// If all casts failed, throw the original error
235+
throw std::runtime_error(result.error());
192236
}
193237
throw RuntimeError("Blackboard::get() error. Missing key [", key, "]");
194238
}
@@ -325,12 +369,34 @@ inline bool Blackboard::get(const std::string& key, T& value) const
325369
{
326370
if(auto any_ref = getAnyLocked(key))
327371
{
328-
if(any_ref.get()->empty())
372+
const auto& any = any_ref.get();
373+
if(any->empty())
329374
{
330375
return false;
331376
}
332-
value = any_ref.get()->cast<T>();
333-
return true;
377+
// Try direct cast first
378+
auto result = any->tryCast<T>();
379+
if(result)
380+
{
381+
value = result.value();
382+
return true;
383+
}
384+
385+
// For shared_ptr types, try polymorphic cast via registry (Issue #943)
386+
if constexpr(is_shared_ptr<T>::value)
387+
{
388+
if(polymorphic_registry_)
389+
{
390+
auto poly_result = any->tryCastWithRegistry<T>(*polymorphic_registry_);
391+
if(poly_result)
392+
{
393+
value = poly_result.value();
394+
return true;
395+
}
396+
}
397+
}
398+
// Cast failed
399+
throw std::runtime_error(result.error());
334400
}
335401
return false;
336402
}

include/behaviortree_cpp/bt_factory.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "behaviortree_cpp/behavior_tree.h"
1818
#include "behaviortree_cpp/contrib/json.hpp"
1919
#include "behaviortree_cpp/contrib/magic_enum.hpp"
20+
#include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp"
2021

2122
#include <filesystem>
2223
#include <functional>
@@ -529,6 +530,45 @@ class BehaviorTreeFactory
529530
[[nodiscard]] const std::unordered_map<std::string, SubstitutionRule>&
530531
substitutionRules() const;
531532

533+
/**
534+
* @brief Register a polymorphic cast relationship between Derived and Base types.
535+
*
536+
* This enables passing shared_ptr<Derived> to ports expecting shared_ptr<Base>
537+
* without type mismatch errors. The relationship is automatically applied
538+
* to all trees created from this factory.
539+
*
540+
* Example:
541+
* factory.registerPolymorphicCast<Cat, Animal>();
542+
* factory.registerPolymorphicCast<Sphynx, Cat>();
543+
*
544+
* @tparam Derived The derived class (must inherit from Base)
545+
* @tparam Base The base class (must be polymorphic)
546+
*/
547+
template <typename Derived, typename Base>
548+
void registerPolymorphicCast()
549+
{
550+
polymorphicCastRegistry().registerCast<Derived, Base>();
551+
}
552+
553+
/**
554+
* @brief Access the polymorphic cast registry.
555+
*
556+
* The registry is shared with all trees created from this factory,
557+
* allowing trees to outlive the factory while maintaining access
558+
* to polymorphic cast relationships.
559+
*/
560+
[[nodiscard]] PolymorphicCastRegistry& polymorphicCastRegistry();
561+
[[nodiscard]] const PolymorphicCastRegistry& polymorphicCastRegistry() const;
562+
563+
/**
564+
* @brief Get a shared pointer to the polymorphic cast registry.
565+
*
566+
* This allows trees and blackboards to hold a reference to the registry
567+
* that outlives the factory.
568+
*/
569+
[[nodiscard]] std::shared_ptr<PolymorphicCastRegistry>
570+
polymorphicCastRegistryPtr() const;
571+
532572
private:
533573
struct PImpl;
534574
std::unique_ptr<PImpl> _p;

include/behaviortree_cpp/tree_node.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,35 @@ inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
591591
}
592592
else
593593
{
594-
destination = any_value.cast<T>();
594+
auto result = any_value.tryCast<T>();
595+
if(result)
596+
{
597+
destination = result.value();
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())
603+
{
604+
auto poly_result = any_value.tryCastWithRegistry<T>(*registry);
605+
if(poly_result)
606+
{
607+
destination = poly_result.value();
608+
}
609+
else
610+
{
611+
throw std::runtime_error(result.error());
612+
}
613+
}
614+
else
615+
{
616+
throw std::runtime_error(result.error());
617+
}
618+
}
619+
else
620+
{
621+
throw std::runtime_error(result.error());
622+
}
595623
}
596624
return Timestamp{ entry->sequence_id, entry->stamp };
597625
}

0 commit comments

Comments
 (0)