diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index 1795ac49a..18a3a931c 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -181,6 +181,11 @@ template <> template <> [[nodiscard]] std::vector convertFromString>(StringView str); +// Strings separated by the character ";" +template <> +[[nodiscard]] std::vector +convertFromString>(StringView str); + // This recognizes either 0/1, true/false, TRUE/FALSE template <> [[nodiscard]] bool convertFromString(StringView str); diff --git a/include/behaviortree_cpp/basic_types.h.orig b/include/behaviortree_cpp/basic_types.h.orig deleted file mode 100644 index 11c9baa7b..000000000 --- a/include/behaviortree_cpp/basic_types.h.orig +++ /dev/null @@ -1,770 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "behaviortree_cpp/utils/safe_any.hpp" -#include "behaviortree_cpp/exceptions.h" -#include "behaviortree_cpp/contrib/expected.hpp" - -namespace BT -{ -/// Enumerates the possible types of nodes -enum class NodeType -{ - UNDEFINED = 0, - ACTION, - CONDITION, - CONTROL, - DECORATOR, - SUBTREE -}; - -/// Enumerates the states every node can be in after execution during a particular -/// time step. -/// IMPORTANT: Your custom nodes should NEVER return IDLE. -enum class NodeStatus -{ - IDLE = 0, - RUNNING = 1, - SUCCESS = 2, - FAILURE = 3, - SKIPPED = 4, -}; - -inline bool isStatusActive(const NodeStatus& status) -{ - return status != NodeStatus::IDLE && status != NodeStatus::SKIPPED; -} - -inline bool isStatusCompleted(const NodeStatus& status) -{ - return status == NodeStatus::SUCCESS || status == NodeStatus::FAILURE; -} - -enum class PortDirection -{ - INPUT, - OUTPUT, - INOUT -}; - -using StringView = std::string_view; - -bool StartWith(StringView str, StringView prefix); - -// vector of key/value pairs -using KeyValueVector = std::vector>; - -<<<<<<< HEAD -struct AnyTypeAllowed -{ -}; - -/** - * convertFromString is used to convert a string into a custom type. - * - * This function is invoked under the hood by TreeNode::getInput(), but only when the - * input port contains a string. - * - * If you have a custom type, you need to implement the corresponding template specialization. - */ -template -[[nodiscard]] inline T convertFromString(StringView /*str*/) -{ - 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 <> -[[nodiscard]] std::string convertFromString(StringView str); - -template <> -[[nodiscard]] const char* convertFromString(StringView str); - -template <> -[[nodiscard]] int convertFromString(StringView str); - -template <> -[[nodiscard]] unsigned convertFromString(StringView str); - -template <> -[[nodiscard]] long convertFromString(StringView str); - -template <> -[[nodiscard]] unsigned long convertFromString(StringView str); - -template <> -[[nodiscard]] float convertFromString(StringView str); - -template <> -[[nodiscard]] double convertFromString(StringView str); - -// Integer numbers separated by the character ";" -template <> -[[nodiscard]] std::vector convertFromString>(StringView str); - -// Real numbers separated by the character ";" -template <> -[[nodiscard]] std::vector convertFromString>(StringView str); - -// This recognizes either 0/1, true/false, TRUE/FALSE -template <> -[[nodiscard]] bool convertFromString(StringView str); - -// Names with all capital letters -template <> -[[nodiscard]] NodeStatus convertFromString(StringView str); - -// Names with all capital letters -template <> -[[nodiscard]] NodeType convertFromString(StringView str); - -template <> -[[nodiscard]] PortDirection convertFromString(StringView str); - -using StringConverter = std::function; - -using StringConvertersMap = std::unordered_map; - -// helper function -template -[[nodiscard]] inline StringConverter GetAnyFromStringFunctor() -{ - if constexpr(std::is_constructible_v) - { - return [](StringView str) { return Any(str); }; - } - else if constexpr(std::is_same_v || std::is_enum_v) - { - return {}; - } - else - { - return [](StringView str) { return Any(convertFromString(str)); }; - } -} - -template <> -[[nodiscard]] inline StringConverter GetAnyFromStringFunctor() -{ - return {}; -} - -//------------------------------------------------------------------ - -template -constexpr bool IsConvertibleToString() -{ - return std::is_convertible_v || - std::is_convertible_v; -} - -template -[[nodiscard]] std::string toStr(const T& value) -{ - if constexpr(IsConvertibleToString()) - { - return static_cast(value); - } - else if constexpr(!std::is_arithmetic_v) - { - throw LogicError(StrCat("Function BT::toStr() not specialized for type [", - BT::demangle(typeid(T)), "]")); - } - else - { - return std::to_string(value); - } -} - -template <> -[[nodiscard]] std::string toStr(const bool& value); - -template <> -[[nodiscard]] std::string toStr(const std::string& value); - -template <> -[[nodiscard]] std::string toStr(const BT::NodeStatus& status); - -/** - * @brief toStr converts NodeStatus to string. Optionally colored. - */ -[[nodiscard]] std::string toStr(BT::NodeStatus status, bool colored); - -std::ostream& operator<<(std::ostream& os, const BT::NodeStatus& status); - -template <> -[[nodiscard]] std::string toStr(const BT::NodeType& type); - -std::ostream& operator<<(std::ostream& os, const BT::NodeType& type); - -template <> -[[nodiscard]] std::string toStr(const BT::PortDirection& direction); - -std::ostream& operator<<(std::ostream& os, const BT::PortDirection& type); - -// Small utility, unless you want to use -[[nodiscard]] std::vector splitString(const StringView& strToSplit, - char delimeter); - -template -using enable_if = typename std::enable_if::type*; - -template -using enable_if_not = typename std::enable_if::type*; - -======= ->>>>>>> 8f94d2c (json conversions) -/** Usage: given a function/method like this: - * - * Expected getAnswer(); - * - * User code can check result and error message like this: - * - * auto res = getAnswer(); - * if( res ) - * { - * std::cout << "answer was: " << res.value() << std::endl; - * } - * else{ - * std::cerr << "failed to get the answer: " << res.error() << std::endl; - * } - * - * */ -template -using Expected = nonstd::expected; - -struct AnyTypeAllowed -{ -}; - -/** - * @brief convertFromJSON will parse a json string and use JsonExporter - * to convert its content to a given type. It will work only if - * the type was previously registered. May throw if it fails. - * - * @param json_text a valid JSON string - * @param type you must specify the typeid() - * @return the object, wrapped in Any. - */ -[[nodiscard]] Any convertFromJSON(StringView json_text, std::type_index type); - -/// Same as the non template version, but with automatic casting -template -[[nodiscard]] inline T convertFromJSON(StringView str) -{ - return convertFromJSON(str, typeid(T)).cast(); -} - -/** - * convertFromString is used to convert a string into a custom type. - * - * This function is invoked under the hood by TreeNode::getInput(), but only when the - * input port contains a string. - * - * If you have a custom type, you need to implement the corresponding - * template specialization. - * - * If the string starts with the prefix "json:", it will - * fall back to convertFromJSON() - */ -template -[[nodiscard]] inline T convertFromString(StringView str) -{ - // if string starts with "json:{", try to parse it as json - if(StartWith(str, "json:")) - { - str.remove_prefix(5); - return convertFromJSON(str); - } - - 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 <> -[[nodiscard]] std::string convertFromString(StringView str); - -template <> -[[nodiscard]] const char* convertFromString(StringView str); - -template <> -[[nodiscard]] int convertFromString(StringView str); - -template <> -[[nodiscard]] unsigned convertFromString(StringView str); - -template <> -[[nodiscard]] long convertFromString(StringView str); - -template <> -[[nodiscard]] unsigned long convertFromString(StringView str); - -template <> -[[nodiscard]] float convertFromString(StringView str); - -template <> -[[nodiscard]] double convertFromString(StringView str); - -// Integer numbers separated by the character ";" -template <> -[[nodiscard]] std::vector convertFromString>(StringView str); - -// Real numbers separated by the character ";" -template <> -[[nodiscard]] std::vector convertFromString>(StringView str); - -// This recognizes either 0/1, true/false, TRUE/FALSE -template <> -[[nodiscard]] bool convertFromString(StringView str); - -// Names with all capital letters -template <> -[[nodiscard]] NodeStatus convertFromString(StringView str); - -// Names with all capital letters -template <> -[[nodiscard]] NodeType convertFromString(StringView str); - -template <> -[[nodiscard]] PortDirection convertFromString(StringView str); - -using StringConverter = std::function; - -using StringConvertersMap = std::unordered_map; - -// helper function -template -[[nodiscard]] inline StringConverter GetAnyFromStringFunctor() -{ - if constexpr(std::is_constructible_v) - { - return [](StringView str) { return Any(str); }; - } - else if constexpr(std::is_same_v || std::is_enum_v) - { - return {}; - } - else - { - return [](StringView str) { return Any(convertFromString(str)); }; - } -} - -template <> -[[nodiscard]] inline StringConverter GetAnyFromStringFunctor() -{ - return {}; -} - -//------------------------------------------------------------------ - -template -constexpr bool IsConvertibleToString() -{ - return std::is_convertible_v || - std::is_convertible_v; -} - -Expected toJsonString(const Any& value); - -/** - * @brief toStr is the reverse operation of convertFromString. - * - * If T is a custom type and there is no template specialization, - * it will try to fall back to toJsonString() - */ -template -[[nodiscard]] std::string toStr(const T& value) -{ - if constexpr(IsConvertibleToString()) - { - return static_cast(value); - } - else if constexpr(!std::is_arithmetic_v) - { - if(auto str = toJsonString(Any(value))) - { - return *str; - } - - throw LogicError(StrCat("Function BT::toStr() not specialized for type [", - BT::demangle(typeid(T)), "]")); - } - else - { - return std::to_string(value); - } -} - -template <> -[[nodiscard]] std::string toStr(const bool& value); - -template <> -[[nodiscard]] std::string toStr(const std::string& value); - -template <> -[[nodiscard]] std::string toStr(const BT::NodeStatus& status); - -/** - * @brief toStr converts NodeStatus to string. Optionally colored. - */ -[[nodiscard]] std::string toStr(BT::NodeStatus status, bool colored); - -std::ostream& operator<<(std::ostream& os, const BT::NodeStatus& status); - -template <> -[[nodiscard]] std::string toStr(const BT::NodeType& type); - -std::ostream& operator<<(std::ostream& os, const BT::NodeType& type); - -template <> -[[nodiscard]] std::string toStr(const BT::PortDirection& direction); - -std::ostream& operator<<(std::ostream& os, const BT::PortDirection& type); - -// Small utility, unless you want to use -[[nodiscard]] std::vector splitString(const StringView& strToSplit, - char delimeter); - -template -using enable_if = typename std::enable_if::type*; - -template -using enable_if_not = typename std::enable_if::type*; - -#ifdef USE_BTCPP3_OLD_NAMES -// note: we also use the name Optional instead of expected because it is more intuitive -// for users that are not up to date with "modern" C++ -template -using Optional = nonstd::expected; -#endif - -/** Usage: given a function/method like: - * - * Result DoSomething(); - * - * User code can check result and error message like this: - * - * auto res = DoSomething(); - * if( res ) - * { - * std::cout << "DoSomething() done " << std::endl; - * } - * else{ - * std::cerr << "DoSomething() failed with message: " << res.error() << std::endl; - * } - * - * */ -using Result = Expected; - -[[nodiscard]] bool IsAllowedPortName(StringView str); - -class TypeInfo -{ -public: - template - static TypeInfo Create() - { - return TypeInfo{ typeid(T), GetAnyFromStringFunctor() }; - } - - TypeInfo() : type_info_(typeid(AnyTypeAllowed)), type_str_("AnyTypeAllowed") - {} - - TypeInfo(std::type_index type_info, StringConverter conv) - : type_info_(type_info), converter_(conv), type_str_(BT::demangle(type_info)) - {} - - [[nodiscard]] const std::type_index& type() const; - - [[nodiscard]] const std::string& typeName() const; - - [[nodiscard]] Any parseString(const char* str) const; - - [[nodiscard]] Any parseString(const std::string& str) const; - - template - [[nodiscard]] Any parseString(const T&) const - { - // avoid compilation errors - return {}; - } - - [[nodiscard]] bool isStronglyTyped() const - { - return type_info_ != typeid(AnyTypeAllowed) && type_info_ != typeid(BT::Any); - } - - [[nodiscard]] const StringConverter& converter() const - { - return converter_; - } - -private: - std::type_index type_info_; - StringConverter converter_; - std::string type_str_; -}; - -class PortInfo : public TypeInfo -{ -public: - PortInfo(PortDirection direction = PortDirection::INOUT) - : TypeInfo(), direction_(direction) - {} - - PortInfo(PortDirection direction, std::type_index type_info, StringConverter conv) - : TypeInfo(type_info, conv), direction_(direction) - {} - - [[nodiscard]] PortDirection direction() const; - - void setDescription(StringView description); - - template - void setDefaultValue(const T& default_value) - { - default_value_ = Any(default_value); - try - { - default_value_str_ = BT::toStr(default_value); - } - catch(LogicError&) - {} - } - - [[nodiscard]] const std::string& description() const; - - [[nodiscard]] const Any& defaultValue() const; - - [[nodiscard]] const std::string& defaultValueString() const; - -private: - PortDirection direction_; - std::string description_; - Any default_value_; - std::string default_value_str_; -}; - -template -[[nodiscard]] std::pair CreatePort(PortDirection direction, - StringView name, - StringView description = {}) -{ - auto sname = static_cast(name); - if(!IsAllowedPortName(sname)) - { - throw RuntimeError("The name of a port must not be `name` or `ID` " - "and must start with an alphabetic character. " - "Underscore is reserved."); - } - - std::pair out; - - if(std::is_same::value) - { - out = { sname, PortInfo(direction) }; - } - else - { - out = { sname, PortInfo(direction, typeid(T), GetAnyFromStringFunctor()) }; - } - if(!description.empty()) - { - out.second.setDescription(description); - } - return out; -} - -//---------- -/** Syntactic sugar to invoke CreatePort(PortDirection::INPUT, ...) - * - * @param name the name of the port - * @param description optional human-readable description - */ -template -[[nodiscard]] inline std::pair -InputPort(StringView name, StringView description = {}) -{ - return CreatePort(PortDirection::INPUT, name, description); -} - -/** Syntactic sugar to invoke CreatePort(PortDirection::OUTPUT,...) - * - * @param name the name of the port - * @param description optional human-readable description - */ -template -[[nodiscard]] inline std::pair -OutputPort(StringView name, StringView description = {}) -{ - return CreatePort(PortDirection::OUTPUT, name, description); -} - -/** Syntactic sugar to invoke CreatePort(PortDirection::INOUT,...) - * - * @param name the name of the port - * @param description optional human-readable description - */ -template -[[nodiscard]] inline std::pair -BidirectionalPort(StringView name, StringView description = {}) -{ - return CreatePort(PortDirection::INOUT, name, description); -} -//---------- - -namespace details -{ - -template -[[nodiscard]] inline std::pair -PortWithDefault(PortDirection direction, StringView name, const DefaultT& default_value, - StringView description) -{ - static_assert(IsConvertibleToString() || std::is_convertible_v || - std::is_constructible_v, - "The default value must be either the same of the port or string"); - - auto out = CreatePort(direction, name, description); - - if constexpr(std::is_constructible_v) - { - out.second.setDefaultValue(T(default_value)); - } - else if constexpr(IsConvertibleToString()) - { - out.second.setDefaultValue(std::string(default_value)); - } - else - { - out.second.setDefaultValue(default_value); - } - return out; -} - -} // end namespace details - -/** Syntactic sugar to invoke CreatePort(PortDirection::INPUT,...) - * It also sets the PortInfo::defaultValue() - * - * @param name the name of the port - * @param default_value default value of the port, either type T of BlackboardKey - * @param description optional human-readable description - */ -template -[[nodiscard]] inline std::pair -InputPort(StringView name, const DefaultT& default_value, StringView description) -{ - return details::PortWithDefault(PortDirection::INPUT, name, default_value, - description); -} - -/** Syntactic sugar to invoke CreatePort(PortDirection::INOUT,...) - * It also sets the PortInfo::defaultValue() - * - * @param name the name of the port - * @param default_value default value of the port, either type T of BlackboardKey - * @param description optional human-readable description - */ -template -[[nodiscard]] inline std::pair -BidirectionalPort(StringView name, const DefaultT& default_value, StringView description) -{ - return details::PortWithDefault(PortDirection::INOUT, name, default_value, - description); -} - -/** Syntactic sugar to invoke CreatePort(PortDirection::OUTPUT,...) - * It also sets the PortInfo::defaultValue() - * - * @param name the name of the port - * @param default_value default blackboard entry where the output is written - * @param description optional human-readable description - */ -template -[[nodiscard]] inline std::pair OutputPort(StringView name, - StringView default_value, - StringView description) -{ - if(default_value.empty() || default_value.front() != '{' || default_value.back() != '}') - { - throw LogicError("Output port can only refer to blackboard entries, i.e. use the " - "syntax '{port_name}'"); - } - auto out = CreatePort(PortDirection::OUTPUT, name, description); - out.second.setDefaultValue(default_value); - return out; -} - -//---------- - -using PortsList = std::unordered_map; - -template -struct has_static_method_providedPorts : std::false_type -{ -}; - -template -struct has_static_method_providedPorts< - T, typename std::enable_if< - std::is_same::value>::type> - : std::true_type -{ -}; - -template -struct has_static_method_metadata : std::false_type -{ -}; - -template -struct has_static_method_metadata< - T, typename std::enable_if< - std::is_same::value>::type> - : std::true_type -{ -}; - -template -[[nodiscard]] inline PortsList -getProvidedPorts(enable_if> = nullptr) -{ - return T::providedPorts(); -} - -template -[[nodiscard]] inline PortsList -getProvidedPorts(enable_if_not> = nullptr) -{ - return {}; -} - -using TimePoint = std::chrono::high_resolution_clock::time_point; -using Duration = std::chrono::high_resolution_clock::duration; - -} // namespace BT diff --git a/src/basic_types.cpp b/src/basic_types.cpp index 1b296ddcf..8265365b8 100644 --- a/src/basic_types.cpp +++ b/src/basic_types.cpp @@ -236,6 +236,19 @@ std::vector convertFromString>(StringView str) return output; } +template <> +std::vector convertFromString>(StringView str) +{ + auto parts = splitString(str, ';'); + std::vector output; + output.reserve(parts.size()); + for(const StringView& part : parts) + { + output.push_back(convertFromString(part)); + } + return output; +} + template <> bool convertFromString(StringView str) { diff --git a/tests/gtest_ports.cpp b/tests/gtest_ports.cpp index 1d136c8ed..1beb652a2 100644 --- a/tests/gtest_ports.cpp +++ b/tests/gtest_ports.cpp @@ -206,11 +206,11 @@ TEST(PortTest, IllegalPorts) ASSERT_ANY_THROW(factory.registerNodeType("nope")); } -class ActionVectorIn : public SyncActionNode +class ActionVectorDoubleIn : public SyncActionNode { public: - ActionVectorIn(const std::string& name, const NodeConfig& config, - std::vector* states) + ActionVectorDoubleIn(const std::string& name, const NodeConfig& config, + std::vector* states) : SyncActionNode(name, config), states_(states) {} @@ -238,14 +238,14 @@ TEST(PortTest, SubtreeStringInput_Issue489) - + )"; std::vector states; BehaviorTreeFactory factory; - factory.registerNodeType("ActionVectorIn", &states); + factory.registerNodeType("ActionVectorDoubleIn", &states); factory.registerBehaviorTreeFromText(xml_txt); auto tree = factory.createTree("Main"); @@ -258,6 +258,55 @@ TEST(PortTest, SubtreeStringInput_Issue489) ASSERT_EQ(7, states[1]); } +class ActionVectorStringIn : public SyncActionNode +{ +public: + ActionVectorStringIn(const std::string& name, const NodeConfig& config, + std::vector* states) + : SyncActionNode(name, config), states_(states) + {} + + NodeStatus tick() override + { + getInput("states", *states_); + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() + { + return { BT::InputPort>("states") }; + } + +private: + std::vector* states_; +}; + +TEST(PortTest, SubtreeStringInput_StringVector) +{ + std::string xml_txt = R"( + + + + + )"; + + std::vector states; + + BehaviorTreeFactory factory; + factory.registerNodeType("ActionVectorStringIn", &states); + + factory.registerBehaviorTreeFromText(xml_txt); + auto tree = factory.createTree("Main"); + + NodeStatus status = tree.tickWhileRunning(); + + ASSERT_EQ(status, NodeStatus::SUCCESS); + ASSERT_EQ(3, states.size()); + ASSERT_EQ("hello", states[0]); + ASSERT_EQ("world", states[1]); + ASSERT_EQ("with spaces", states[2]); +} + struct Point2D { int x = 0;