From 49355fd6405c851b84ae1742a25e73474664c2f9 Mon Sep 17 00:00:00 2001 From: Ashton Larkin <42042756+adlarkin@users.noreply.github.com> Date: Thu, 21 Dec 2023 18:26:44 -0500 Subject: [PATCH 1/2] Add optional metadata to TreeNodeManifest --- include/behaviortree_cpp/basic_types.h | 40 +++++++++++++++++- include/behaviortree_cpp/bt_factory.h | 15 +++++-- include/behaviortree_cpp/tree_node.h | 2 + src/bt_factory.cpp | 8 ++-- src/xml_parsing.cpp | 22 +++++++++- tests/gtest_factory.cpp | 57 +++++++++++++++++++++++++- 6 files changed, 132 insertions(+), 12 deletions(-) diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index a86e31504..6cd356913 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include "behaviortree_cpp/utils/safe_any.hpp" @@ -450,6 +452,43 @@ struct has_static_method_description< { }; +/// Optional metadata for a TreeNodeManifest. +/// The metadata can represent an xml element with text: +/// Descriptive text +/// Or an xml element with an attribute: +/// +struct ManifestMetadata +{ + /// A pair containing the of a metadata's XML attribute. + using Attribute = std::pair; + + [[nodiscard]] bool representsText() const + { + return std::holds_alternative(text_or_attribute); + } + + [[nodiscard]] bool representsAttribute() const + { + return std::holds_alternative(text_or_attribute); + } + + std::string name; + std::variant text_or_attribute; +}; + +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) { @@ -467,4 +506,3 @@ using TimePoint = std::chrono::high_resolution_clock::time_point; using Duration = std::chrono::high_resolution_clock::duration; } // namespace BT - diff --git a/include/behaviortree_cpp/bt_factory.h b/include/behaviortree_cpp/bt_factory.h index 4c126b7e3..7072bd15d 100644 --- a/include/behaviortree_cpp/bt_factory.h +++ b/include/behaviortree_cpp/bt_factory.h @@ -44,13 +44,20 @@ template inline TreeNodeManifest CreateManifest(const std::string& ID, PortsList portlist = getProvidedPorts()) { - if constexpr( has_static_method_description::value) + if constexpr( has_static_method_description::value && has_static_method_metadata::value ) { - return {getType(), ID, portlist, T::description()}; + return {getType(), ID, portlist, T::description(), T::metadata()}; } - else { - return {getType(), ID, portlist, {}}; + else if constexpr( has_static_method_description::value && !has_static_method_metadata::value ) + { + return {getType(), ID, portlist, T::description(), {}}; + } + else if constexpr( !has_static_method_description::value && has_static_method_metadata::value ) + { + return {getType(), ID, portlist, {}, T::metadata()}; } + + return {getType(), ID, portlist, {}, {}}; } #ifdef BT_PLUGIN_EXPORT diff --git a/include/behaviortree_cpp/tree_node.h b/include/behaviortree_cpp/tree_node.h index 20246dbc4..7287afffe 100644 --- a/include/behaviortree_cpp/tree_node.h +++ b/include/behaviortree_cpp/tree_node.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "behaviortree_cpp/utils/signal.h" #include "behaviortree_cpp/basic_types.h" @@ -39,6 +40,7 @@ struct TreeNodeManifest std::string registration_ID; PortsList ports; std::string description; + std::vector metadata; }; using PortsRemapping = std::unordered_map; diff --git a/src/bt_factory.cpp b/src/bt_factory.cpp index e14ca8162..14967cb13 100644 --- a/src/bt_factory.cpp +++ b/src/bt_factory.cpp @@ -91,7 +91,7 @@ BehaviorTreeFactory::BehaviorTreeFactory(): registerNodeType>("Switch4"); registerNodeType>("Switch5"); registerNodeType>("Switch6"); - + registerNodeType>("LoopInt"); registerNodeType>("LoopBool"); registerNodeType>("LoopDouble"); @@ -157,7 +157,7 @@ void BehaviorTreeFactory::registerSimpleCondition( return std::make_unique(name, tick_functor, config); }; - TreeNodeManifest manifest = {NodeType::CONDITION, ID, std::move(ports), {}}; + TreeNodeManifest manifest = {NodeType::CONDITION, ID, std::move(ports), {}, {}}; registerBuilder(manifest, builder); } @@ -170,7 +170,7 @@ void BehaviorTreeFactory::registerSimpleAction( return std::make_unique(name, tick_functor, config); }; - TreeNodeManifest manifest = {NodeType::ACTION, ID, std::move(ports), {}}; + TreeNodeManifest manifest = {NodeType::ACTION, ID, std::move(ports), {}, {}}; registerBuilder(manifest, builder); } @@ -183,7 +183,7 @@ void BehaviorTreeFactory::registerSimpleDecorator( return std::make_unique(name, tick_functor, config); }; - TreeNodeManifest manifest = {NodeType::DECORATOR, ID, std::move(ports), {}}; + TreeNodeManifest manifest = {NodeType::DECORATOR, ID, std::move(ports), {}, {}}; registerBuilder(manifest, builder); } diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index 6fc77d9aa..dad3723df 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -83,7 +83,7 @@ struct XMLParser::PImpl std::map subtree_models; int suffix_count; - + explicit PImpl(const BehaviorTreeFactory& fact) : factory(fact), current_path(std::filesystem::current_path()), suffix_count(0) {} @@ -989,6 +989,24 @@ void addNodeModelToXML(const TreeNodeManifest& model, element->InsertEndChild(description_element); } + for (const auto& metadata : model.metadata) + { + auto metadata_element = doc.NewElement(metadata.name.c_str()); + + if (metadata.representsText()) + { + const auto element_text = std::get(metadata.text_or_attribute); + metadata_element->SetText(element_text.c_str()); + } + else + { + const auto [attribute_name, attribute_val] = std::get(metadata.text_or_attribute); + metadata_element->SetAttribute(attribute_name.c_str(), attribute_val.c_str()); + } + + element->InsertEndChild(metadata_element); + } + model_root->InsertEndChild(element); } @@ -997,7 +1015,7 @@ void addTreeToXML(const Tree& tree, XMLElement* rootXML, bool add_metadata, bool add_builtin_models) -{ +{ std::function addNode; addNode = [&](const TreeNode& node, XMLElement* parent_elem) diff --git a/tests/gtest_factory.cpp b/tests/gtest_factory.cpp index 3cfe3831f..6a3e96855 100644 --- a/tests/gtest_factory.cpp +++ b/tests/gtest_factory.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include "behaviortree_cpp/xml_parsing.h" #include "../sample_nodes/crossdoor_nodes.h" #include "../sample_nodes/dummy_nodes.h" @@ -409,7 +412,6 @@ class DescriptiveAction : public SyncActionNode TEST(BehaviorTreeFactory, DescriptionMethod) { - BehaviorTreeFactory factory; factory.registerNodeType("DescriptiveAction"); const auto& manifest = factory.manifests().at("DescriptiveAction"); @@ -420,3 +422,56 @@ TEST(BehaviorTreeFactory, DescriptionMethod) ASSERT_NE(xml.find( "THE DESCRIPTION"), std::string::npos); } + +std::vector makeTestMetadata() +{ + ManifestMetadata text_metadata; + text_metadata.name = "text_metadata"; + text_metadata.text_or_attribute = "text"; + + ManifestMetadata attribute_metadata; + attribute_metadata.name = "attribute_metadata"; + attribute_metadata.text_or_attribute = std::make_pair("attribute_name", "attribute_value"); + + return {text_metadata, attribute_metadata}; +} + +class ActionWithMetadata : public SyncActionNode +{ +public: + ActionWithMetadata(const std::string& name, const NodeConfig& config): + SyncActionNode(name, config) {} + + BT::NodeStatus tick() override { + return NodeStatus::SUCCESS; + } + + static PortsList providedPorts() { + return {}; + } + + static std::vector metadata() { + return makeTestMetadata(); + } +}; + +TEST(BehaviorTreeFactory, ManifestMethod) +{ + BehaviorTreeFactory factory; + factory.registerNodeType("ActionWithMetadata"); + const auto& manifest = factory.manifests().at("ActionWithMetadata"); + ASSERT_EQ(manifest.metadata.size(), 2u); + auto expected_metadata = makeTestMetadata(); + EXPECT_EQ(manifest.metadata[0].name, expected_metadata[0].name); + EXPECT_EQ(manifest.metadata[0].text_or_attribute, expected_metadata[0].text_or_attribute); + EXPECT_TRUE(manifest.metadata[0].representsText()); + EXPECT_EQ(manifest.metadata[1].name, expected_metadata[1].name); + EXPECT_EQ(manifest.metadata[1].text_or_attribute, expected_metadata[1].text_or_attribute); + EXPECT_TRUE(manifest.metadata[1].representsAttribute()); + + auto xml = writeTreeNodesModelXML(factory, false); + std::cout << xml << std::endl; + + ASSERT_NE(xml.find("text"), std::string::npos); + ASSERT_NE(xml.find(""), std::string::npos); +} From 96910f265786458ac2f1b33a9f3d1a3adaf26473 Mon Sep 17 00:00:00 2001 From: Ashton Larkin <42042756+adlarkin@users.noreply.github.com> Date: Fri, 22 Dec 2023 14:48:56 -0500 Subject: [PATCH 2/2] simplify metadata, address review feedback --- include/behaviortree_cpp/basic_types.h | 39 +------------ include/behaviortree_cpp/bt_factory.h | 23 +++----- include/behaviortree_cpp/tree_node.h | 4 +- src/bt_factory.cpp | 14 ++--- src/xml_parsing.cpp | 25 +++----- tests/gtest_factory.cpp | 80 +++++++++----------------- 6 files changed, 52 insertions(+), 133 deletions(-) diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index 6cd356913..4e01392db 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -439,43 +439,6 @@ struct has_static_method_providedPorts< { }; -template -struct has_static_method_description : std::false_type -{ -}; - -template -struct has_static_method_description< - T, typename std::enable_if< - std::is_same::value>::type> - : std::true_type -{ -}; - -/// Optional metadata for a TreeNodeManifest. -/// The metadata can represent an xml element with text: -/// Descriptive text -/// Or an xml element with an attribute: -/// -struct ManifestMetadata -{ - /// A pair containing the of a metadata's XML attribute. - using Attribute = std::pair; - - [[nodiscard]] bool representsText() const - { - return std::holds_alternative(text_or_attribute); - } - - [[nodiscard]] bool representsAttribute() const - { - return std::holds_alternative(text_or_attribute); - } - - std::string name; - std::variant text_or_attribute; -}; - template struct has_static_method_metadata : std::false_type { @@ -484,7 +447,7 @@ 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::is_same>>::value>::type> : std::true_type { }; diff --git a/include/behaviortree_cpp/bt_factory.h b/include/behaviortree_cpp/bt_factory.h index 7072bd15d..cbbecea00 100644 --- a/include/behaviortree_cpp/bt_factory.h +++ b/include/behaviortree_cpp/bt_factory.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "behaviortree_cpp/contrib/magic_enum.hpp" #include "behaviortree_cpp/behavior_tree.h" @@ -44,20 +46,11 @@ template inline TreeNodeManifest CreateManifest(const std::string& ID, PortsList portlist = getProvidedPorts()) { - if constexpr( has_static_method_description::value && has_static_method_metadata::value ) + if constexpr( has_static_method_metadata::value ) { - return {getType(), ID, portlist, T::description(), T::metadata()}; + return {getType(), ID, portlist, T::metadata()}; } - else if constexpr( has_static_method_description::value && !has_static_method_metadata::value ) - { - return {getType(), ID, portlist, T::description(), {}}; - } - else if constexpr( !has_static_method_description::value && has_static_method_metadata::value ) - { - return {getType(), ID, portlist, {}, T::metadata()}; - } - - return {getType(), ID, portlist, {}, {}}; + return {getType(), ID, portlist, {}}; } #ifdef BT_PLUGIN_EXPORT @@ -432,10 +425,10 @@ class BehaviorTreeFactory Tree createTree(const std::string& tree_name, Blackboard::Ptr blackboard = Blackboard::create()); - /// Add a description to a specific manifest. This description will be added + /// Add metadata to a specific manifest. This metadata will be added /// to with the function writeTreeNodesModelXML() - void addDescriptionToManifest(const std::string& node_id, - const std::string& description); + void addMetadataToManifest(const std::string& node_id, + const std::vector>& metadata); /** * @brief Add an Enum to the scripting language. diff --git a/include/behaviortree_cpp/tree_node.h b/include/behaviortree_cpp/tree_node.h index 7287afffe..788addd38 100644 --- a/include/behaviortree_cpp/tree_node.h +++ b/include/behaviortree_cpp/tree_node.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "behaviortree_cpp/utils/signal.h" @@ -39,8 +40,7 @@ struct TreeNodeManifest NodeType type; std::string registration_ID; PortsList ports; - std::string description; - std::vector metadata; + std::vector> metadata; }; using PortsRemapping = std::unordered_map; diff --git a/src/bt_factory.cpp b/src/bt_factory.cpp index 14967cb13..ec7a0b777 100644 --- a/src/bt_factory.cpp +++ b/src/bt_factory.cpp @@ -157,7 +157,7 @@ void BehaviorTreeFactory::registerSimpleCondition( return std::make_unique(name, tick_functor, config); }; - TreeNodeManifest manifest = {NodeType::CONDITION, ID, std::move(ports), {}, {}}; + TreeNodeManifest manifest = {NodeType::CONDITION, ID, std::move(ports), {}}; registerBuilder(manifest, builder); } @@ -170,7 +170,7 @@ void BehaviorTreeFactory::registerSimpleAction( return std::make_unique(name, tick_functor, config); }; - TreeNodeManifest manifest = {NodeType::ACTION, ID, std::move(ports), {}, {}}; + TreeNodeManifest manifest = {NodeType::ACTION, ID, std::move(ports), {}}; registerBuilder(manifest, builder); } @@ -183,7 +183,7 @@ void BehaviorTreeFactory::registerSimpleDecorator( return std::make_unique(name, tick_functor, config); }; - TreeNodeManifest manifest = {NodeType::DECORATOR, ID, std::move(ports), {}, {}}; + TreeNodeManifest manifest = {NodeType::DECORATOR, ID, std::move(ports), {}}; registerBuilder(manifest, builder); } @@ -435,15 +435,15 @@ Tree BehaviorTreeFactory::createTree(const std::string& tree_name, return tree; } -void BehaviorTreeFactory::addDescriptionToManifest(const std::string& node_id, - const std::string& description) +void BehaviorTreeFactory::addMetadataToManifest(const std::string& node_id, + const std::vector>& metadata) { auto it = _p->manifests.find(node_id); if (it == _p->manifests.end()) { - throw std::runtime_error("addDescriptionToManifest: wrong ID"); + throw std::runtime_error("addMetadataToManifest: wrong ID"); } - it->second.description = description; + it->second.metadata = metadata; } void BehaviorTreeFactory::registerScriptingEnum(StringView name, int value) diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index dad3723df..87435a4c0 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -982,29 +982,18 @@ void addNodeModelToXML(const TreeNodeManifest& model, element->InsertEndChild(port_element); } - if (!model.description.empty()) + if (!model.metadata.empty()) { - auto description_element = doc.NewElement("description"); - description_element->SetText(model.description.c_str()); - element->InsertEndChild(description_element); - } - - for (const auto& metadata : model.metadata) - { - auto metadata_element = doc.NewElement(metadata.name.c_str()); + auto metadata_root = doc.NewElement("MetadataFields"); - if (metadata.representsText()) - { - const auto element_text = std::get(metadata.text_or_attribute); - metadata_element->SetText(element_text.c_str()); - } - else + for (const auto& [name, value] : model.metadata) { - const auto [attribute_name, attribute_val] = std::get(metadata.text_or_attribute); - metadata_element->SetAttribute(attribute_name.c_str(), attribute_val.c_str()); + auto metadata_element = doc.NewElement("Metadata"); + metadata_element->SetAttribute(name.c_str(), value.c_str()); + metadata_root->InsertEndChild(metadata_element); } - element->InsertEndChild(metadata_element); + element->InsertEndChild(metadata_root); } model_root->InsertEndChild(element); diff --git a/tests/gtest_factory.cpp b/tests/gtest_factory.cpp index 6a3e96855..5dc7b9e9c 100644 --- a/tests/gtest_factory.cpp +++ b/tests/gtest_factory.cpp @@ -391,49 +391,12 @@ TEST(BehaviorTreeReload, ReloadSameTree) } } -class DescriptiveAction : public SyncActionNode +std::vector> makeTestMetadata() { -public: - DescriptiveAction(const std::string& name, const NodeConfig& config): - SyncActionNode(name, config) {} - - BT::NodeStatus tick() override { - return NodeStatus::SUCCESS; - } - - static PortsList providedPorts() { - return {}; - } - - static std::string description() { - return "THE DESCRIPTION"; - } -}; - -TEST(BehaviorTreeFactory, DescriptionMethod) -{ - BehaviorTreeFactory factory; - factory.registerNodeType("DescriptiveAction"); - const auto& manifest = factory.manifests().at("DescriptiveAction"); - ASSERT_EQ(manifest.description, "THE DESCRIPTION"); - - auto xml = writeTreeNodesModelXML(factory, false); - std::cout << xml << std::endl; - - ASSERT_NE(xml.find( "THE DESCRIPTION"), std::string::npos); -} - -std::vector makeTestMetadata() -{ - ManifestMetadata text_metadata; - text_metadata.name = "text_metadata"; - text_metadata.text_or_attribute = "text"; - - ManifestMetadata attribute_metadata; - attribute_metadata.name = "attribute_metadata"; - attribute_metadata.text_or_attribute = std::make_pair("attribute_name", "attribute_value"); - - return {text_metadata, attribute_metadata}; + return { + std::make_pair("foo", "hello"), + std::make_pair("bar", "42"), + }; } class ActionWithMetadata : public SyncActionNode @@ -450,28 +413,39 @@ class ActionWithMetadata : public SyncActionNode return {}; } - static std::vector metadata() { + static std::vector> metadata() { return makeTestMetadata(); } }; TEST(BehaviorTreeFactory, ManifestMethod) { + const char* expectedXML = R"( + + + + + + )"; + BehaviorTreeFactory factory; factory.registerNodeType("ActionWithMetadata"); const auto& manifest = factory.manifests().at("ActionWithMetadata"); - ASSERT_EQ(manifest.metadata.size(), 2u); - auto expected_metadata = makeTestMetadata(); - EXPECT_EQ(manifest.metadata[0].name, expected_metadata[0].name); - EXPECT_EQ(manifest.metadata[0].text_or_attribute, expected_metadata[0].text_or_attribute); - EXPECT_TRUE(manifest.metadata[0].representsText()); - EXPECT_EQ(manifest.metadata[1].name, expected_metadata[1].name); - EXPECT_EQ(manifest.metadata[1].text_or_attribute, expected_metadata[1].text_or_attribute); - EXPECT_TRUE(manifest.metadata[1].representsAttribute()); + EXPECT_EQ(manifest.metadata, makeTestMetadata()); auto xml = writeTreeNodesModelXML(factory, false); std::cout << xml << std::endl; - ASSERT_NE(xml.find("text"), std::string::npos); - ASSERT_NE(xml.find(""), std::string::npos); + EXPECT_NE(xml.find(expectedXML), std::string::npos); +} + +TEST(BehaviorTreeFactory, addMetadataToManifest) +{ + BehaviorTreeFactory factory; + factory.registerNodeType("SaySomething"); + const auto& initial_manifest = factory.manifests().at("SaySomething"); + EXPECT_TRUE(initial_manifest.metadata.empty()); + factory.addMetadataToManifest("SaySomething", makeTestMetadata()); + const auto& modified_manifest = factory.manifests().at("SaySomething"); + EXPECT_EQ(modified_manifest.metadata, makeTestMetadata()); }