diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index 706e0c61d..6e7c0ca6c 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -72,7 +72,15 @@ using StringView = std::string_view; template inline T convertFromString(StringView /*str*/) { - static_assert(true, "This template specialization of convertFromString doesn't exist"); + auto type_name = BT::demangle(typeid(T)); + + std::cerr << "You (maybe indirectly) called BT::convertFromString() for type [" + << type_name << "], but I can't find the template specialization.\n" + << std::endl; + + throw LogicError(std::string("You didn't implement the template specialization of " + "convertFromString for this type: ") + + type_name); } template <> diff --git a/include/behaviortree_cpp/tree_node.h b/include/behaviortree_cpp/tree_node.h index d2211dc02..20cff4d3c 100644 --- a/include/behaviortree_cpp/tree_node.h +++ b/include/behaviortree_cpp/tree_node.h @@ -302,8 +302,6 @@ class TreeNode std::array pre_parsed_; std::array post_parsed_; - std::shared_ptr scripting_enums_; - Expected checkPreConditions(); void checkPostConditions(NodeStatus status); @@ -316,6 +314,28 @@ class TreeNode template inline Result TreeNode::getInput(const std::string& key, T& destination) const { + // address the special case where T is an enum + auto ParseString = [this](const std::string& str) -> T + { + if constexpr (std::is_enum_v && !std::is_same_v) + { + auto it = config_.enums->find(str); + // conversion available + if( it != config_.enums->end() ) + { + return static_cast(it->second); + } + else { + // hopefully str contains a number that can be parsed. May throw + return static_cast(convertFromString(str)); + } + } + else { + return convertFromString(str); + } + }; + + auto remap_it = config_.input_ports.find(key); if (remap_it == config_.input_ports.end()) { @@ -327,18 +347,17 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const auto remapped_res = getRemappedKey(key, remap_it->second); try { + // pure string, not a blackboard key if (!remapped_res) { - destination = convertFromString(remap_it->second); + destination = ParseString(remap_it->second); return {}; } const auto& remapped_key = remapped_res.value(); if (!config_.blackboard) { - return nonstd::make_unexpected("getInput() trying to access a Blackboard(BB) " - "entry, " - "but BB is invalid"); + return nonstd::make_unexpected("getInput(): trying to access an invalid Blackboard"); } std::unique_lock entry_lock(config_.blackboard->entryMutex()); @@ -348,7 +367,7 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const if (!std::is_same_v && val->type() == typeid(std::string)) { - destination = convertFromString(val->cast()); + destination = ParseString(val->cast()); } else { diff --git a/src/basic_types.cpp b/src/basic_types.cpp index 569a69024..25f2fbb1a 100644 --- a/src/basic_types.cpp +++ b/src/basic_types.cpp @@ -107,7 +107,11 @@ template <> int convertFromString(StringView str) { int result = 0; - std::from_chars(str.data(), str.data() + str.size(), result); + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + if(ec != std::errc()) + { + throw RuntimeError(StrCat("Can't convert string [", str, "] to int")); + } return result; } @@ -115,7 +119,11 @@ template <> long convertFromString(StringView str) { long result = 0; - std::from_chars(str.data(), str.data() + str.size(), result); + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + if(ec != std::errc()) + { + throw RuntimeError(StrCat("Can't convert string [", str, "] to long")); + } return result; } @@ -123,7 +131,11 @@ template <> unsigned convertFromString(StringView str) { unsigned result = 0; - std::from_chars(str.data(), str.data() + str.size(), result); + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + if(ec != std::errc()) + { + throw RuntimeError(StrCat("Can't convert string [", str, "] to unsigned")); + } return result; } @@ -131,7 +143,11 @@ template <> unsigned long convertFromString(StringView str) { unsigned long result = 0; - std::from_chars(str.data(), str.data() + str.size(), result); + auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); + if(ec != std::errc()) + { + throw RuntimeError(StrCat("Can't convert string [", str, "] to unsigned long")); + } return result; } diff --git a/tests/gtest_ports.cpp b/tests/gtest_ports.cpp index ea969847c..2b2b5d196 100644 --- a/tests/gtest_ports.cpp +++ b/tests/gtest_ports.cpp @@ -197,6 +197,7 @@ class ActionVectorIn : public SyncActionNode { return {BT::InputPort>("states")}; } +private: std::vector* states_; }; @@ -230,3 +231,63 @@ TEST(PortTest, SubtreeStringInput_Issue489) ASSERT_EQ(7, states[1]); } +enum class Color +{ + Red = 0, + Blue = 1, + Green = 2, + Undefined +}; + +class ActionEnum : public SyncActionNode +{ +public: + ActionEnum(const std::string& name, const NodeConfig& config) : + SyncActionNode(name, config) + {} + + NodeStatus tick() override + { + getInput("color", color); + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return {BT::InputPort("color")}; + } + + Color color = Color::Undefined; +}; + +TEST(PortTest, StrintToEnum) +{ + std::string xml_txt = R"( + + + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerNodeType("ActionEnum"); + factory.registerScriptingEnums(); + + auto tree = factory.createTreeFromText(xml_txt); + + NodeStatus status = tree.tickWhileRunning(); + + ASSERT_EQ(status, NodeStatus::SUCCESS); + + auto first_node = dynamic_cast(tree.subtrees.front()->nodes[1].get()); + auto second_node = dynamic_cast(tree.subtrees.front()->nodes[2].get()); + + ASSERT_EQ(Color::Blue, first_node->color); + ASSERT_EQ(Color::Green, second_node->color); +} + + +