diff --git a/include/behaviortree_cpp/tree_node.h b/include/behaviortree_cpp/tree_node.h index d91e36e15..d95d4e017 100644 --- a/include/behaviortree_cpp/tree_node.h +++ b/include/behaviortree_cpp/tree_node.h @@ -20,6 +20,7 @@ #include "behaviortree_cpp/utils/strcat.hpp" #include "behaviortree_cpp/utils/wakeup_signal.hpp" +#include #include #include #include @@ -432,17 +433,25 @@ T TreeNode::parseString(const std::string& str) const { if constexpr(std::is_enum_v && !std::is_same_v) { - auto it = config().enums->find(str); - // conversion available - if(it != config().enums->end()) + // Check the ScriptingEnumsRegistry first, if available. + if(config().enums) { - return static_cast(it->second); + auto it = config().enums->find(str); + if(it != config().enums->end()) + { + return static_cast(it->second); + } } - else + // Try numeric conversion (e.g. "2" for an enum value). + int tmp = 0; + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), tmp); + if(ec == std::errc() && ptr == str.data() + str.size()) { - // hopefully str contains a number that can be parsed. May throw - return static_cast(convertFromString(str)); + return static_cast(tmp); } + // Fall back to convertFromString, which uses a user-provided + // specialization if one exists. Issue #948. + return convertFromString(str); } return convertFromString(str); } diff --git a/tests/gtest_enums.cpp b/tests/gtest_enums.cpp index c15f50ece..379434622 100644 --- a/tests/gtest_enums.cpp +++ b/tests/gtest_enums.cpp @@ -240,3 +240,68 @@ TEST(Enums, SubtreeRemapping) ASSERT_EQ(status, NodeStatus::SUCCESS); ASSERT_EQ(tree.rootBlackboard()->get("fault_status"), LOW_BATTERY); } + +// Issue #948: enums with a convertFromString specialization should be parsed +// correctly via getInput, without requiring ScriptingEnumsRegistry. +class ActionWithNodeType : public SyncActionNode +{ +public: + ActionWithNodeType(const std::string& name, const NodeConfig& config) + : SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + auto res = getInput("type"); + if(!res) + { + throw RuntimeError("getInput failed: " + res.error()); + } + parsed_type = res.value(); + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { InputPort("type") }; + } + + NodeType parsed_type = NodeType::UNDEFINED; +}; + +TEST(Enums, ParseEnumWithConvertFromString_Issue948) +{ + std::string xml_txt = R"( + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("ActionWithNodeType"); + // Deliberately NOT registering NodeType in ScriptingEnumsRegistry. + // convertFromString exists and should be used as fallback. + + auto tree = factory.createTreeFromText(xml_txt); + NodeStatus status = tree.tickWhileRunning(); + ASSERT_EQ(status, NodeStatus::SUCCESS); + + for(const auto& node : tree.subtrees.front()->nodes) + { + if(auto typed = dynamic_cast(node.get())) + { + if(typed->name() == "test_action") + { + ASSERT_EQ(NodeType::ACTION, typed->parsed_type); + } + else if(typed->name() == "test_control") + { + ASSERT_EQ(NodeType::CONTROL, typed->parsed_type); + } + } + } +}