Skip to content

Commit 869a50e

Browse files
dsobekdyackzan
authored andcommitted
Fix subtree ports
1 parent 6c88f35 commit 869a50e

File tree

4 files changed

+134
-90
lines changed

4 files changed

+134
-90
lines changed

include/behaviortree_cpp/tree_node.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ struct NodeConfig
9191
PortsRemapping input_ports;
9292
// output ports
9393
PortsRemapping output_ports;
94+
// If missing port fields are automatically remapped (only relevant for subtrees).
95+
bool auto_remapped = false;
9496

9597
// Any other attributes found in the xml that are not parsed as ports
9698
// or built-in identifier (e.g. anything with a leading '_')
@@ -130,7 +132,8 @@ inline constexpr bool hasNodeFullCtor()
130132
class TreeNode
131133
{
132134
public:
133-
typedef std::shared_ptr<TreeNode> Ptr;
135+
using Ptr = std::shared_ptr<TreeNode>;
136+
using ConstPtr = std::shared_ptr<const TreeNode>;
134137

135138
/**
136139
* @brief TreeNode main constructor.

src/xml_parsing.cpp

Lines changed: 102 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020
#include <typeindex>
2121
#include "behaviortree_cpp/basic_types.h"
22+
#include "behaviortree_cpp/utils/strcat.hpp"
2223

2324
#if defined(_MSVC_LANG) && !defined(__clang__)
2425
#define __bt_cplusplus (_MSC_VER == 1900 ? 201103L : _MSVC_LANG)
@@ -85,7 +86,7 @@ auto StrEqual = [](const char* str1, const char* str2) -> bool {
8586

8687
struct SubtreeModel
8788
{
88-
std::unordered_map<std::string, BT::PortInfo> ports;
89+
PortsList ports;
8990
};
9091

9192
struct XMLParser::PImpl
@@ -638,6 +639,7 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
638639
const auto element_name = element->Name();
639640
const auto element_ID = element->Attribute("ID");
640641

642+
// TODO: Pull out this node type logic
641643
auto node_type = convertFromString<NodeType>(element_name);
642644
// name used by the factory
643645
std::string type_ID;
@@ -682,16 +684,20 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
682684

683685
PortsRemapping port_remap;
684686
NonPortAttributes other_attributes;
687+
// Only relevant for subtrees
688+
bool do_autoremap = false;
685689

690+
// Parse ports and validate them where we can.
686691
for(const XMLAttribute* att = element->FirstAttribute(); att; att = att->Next())
687692
{
688693
const std::string port_name = att->Name();
689-
const std::string port_value = att->Value();
694+
std::string port_value = att->Value();
690695
if(IsAllowedPortName(port_name))
691696
{
692-
const std::string port_name = att->Name();
693-
const std::string port_value = att->Value();
694-
697+
if(port_value == "{=}")
698+
{
699+
port_value = StrCat("{", port_name, "}");
700+
}
695701
if(manifest)
696702
{
697703
auto port_model_it = manifest->ports.find(port_name);
@@ -703,32 +709,34 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
703709
") but not in the providedPorts() of its "
704710
"registered node type."));
705711
}
706-
else
712+
713+
const auto& port_model = port_model_it->second;
714+
bool is_blacbkboard = port_value.size() >= 3 && port_value.front() == '{' &&
715+
port_value.back() == '}';
716+
// let's test already if conversion is possible
717+
if(!is_blacbkboard && port_model.converter() && port_model.isStronglyTyped())
707718
{
708-
const auto& port_model = port_model_it->second;
709-
bool is_blacbkboard = port_value.size() >= 3 && port_value.front() == '{' &&
710-
port_value.back() == '}';
711-
// let's test already if conversion is possible
712-
if(!is_blacbkboard && port_model.converter() && port_model.isStronglyTyped())
719+
// This may throw
720+
try
713721
{
714-
// This may throw
715-
try
716-
{
717-
port_model.converter()(port_value);
718-
}
719-
catch(std::exception& ex)
720-
{
721-
auto msg = StrCat("The port with name \"", port_name, "\" and value \"",
722-
port_value, "\" can not be converted to ",
723-
port_model.typeName());
724-
throw LogicError(msg);
725-
}
722+
port_model.converter()(port_value);
723+
}
724+
catch(std::exception& ex)
725+
{
726+
auto msg =
727+
StrCat("The port with name \"", port_name, "\" and value \"", port_value,
728+
"\" can not be converted to ", port_model.typeName());
729+
throw LogicError(msg);
726730
}
727731
}
728732
}
729733

730734
port_remap[port_name] = port_value;
731735
}
736+
else if(node_type == NodeType::SUBTREE && port_name == "_autoremap")
737+
{
738+
do_autoremap = convertFromString<bool>(port_value);
739+
}
732740
else if(!IsReservedAttribute(port_name))
733741
{
734742
other_attributes[port_name] = port_value;
@@ -740,6 +748,7 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
740748
config.path = prefix_path + instance_name;
741749
config.uid = output_tree.getUID();
742750
config.manifest = manifest;
751+
config.auto_remapped = do_autoremap;
743752

744753
if(type_ID == instance_name)
745754
{
@@ -769,9 +778,62 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
769778
//---------------------------------------------
770779
TreeNode::Ptr new_node;
771780

781+
// TODO: in order to set the config at this point, we need the subtree model, which is parsed after this function call in recursivelyCreateSubtree
772782
if(node_type == NodeType::SUBTREE)
773783
{
774-
config.input_ports = port_remap;
784+
// check if this subtree has a model. If it does,
785+
// we want to check if all the mandatory ports were remapped and
786+
// add default ones, if necessary.
787+
auto subtree_model_it = subtree_models.find(type_ID);
788+
if(subtree_model_it != subtree_models.end())
789+
{
790+
const PortsList& subtree_model_ports = subtree_model_it->second.ports;
791+
// check if:
792+
// - remapping contains mandatory ports
793+
// - if any of these has default value
794+
for(const auto& [port_name, port_info] : subtree_model_ports)
795+
{
796+
auto it = port_remap.find(port_name);
797+
// don't override existing remapping
798+
if(it == port_remap.end() && !do_autoremap)
799+
{
800+
// remapping is not explicitly defined in the XML: use the model
801+
if(port_info.defaultValueString().empty())
802+
{
803+
auto msg = StrCat("In the <TreeNodesModel> the <Subtree ID=\"", type_ID,
804+
"\"> is defining a mandatory port called [", port_name,
805+
"], but you are not remapping it");
806+
throw RuntimeError(msg);
807+
}
808+
port_remap.insert({ port_name, port_info.defaultValueString() });
809+
}
810+
}
811+
}
812+
// populate the node config
813+
for(const auto& [port_name, port_value] : port_remap)
814+
{
815+
PortDirection direction = PortDirection::INPUT;
816+
if(subtree_model_it != subtree_models.end())
817+
{
818+
const PortsList& subtree_model_ports = subtree_model_it->second.ports;
819+
if(const auto& it = subtree_model_ports.find(port_name);
820+
it != subtree_model_ports.end())
821+
{
822+
direction = it->second.direction();
823+
}
824+
}
825+
826+
// Include the ports in the node config
827+
if(direction == PortDirection::INPUT || direction == PortDirection::INOUT)
828+
{
829+
config.input_ports[port_name] = port_value;
830+
}
831+
if(direction == PortDirection::OUTPUT || direction == PortDirection::INOUT)
832+
{
833+
config.output_ports[port_name] = port_value;
834+
}
835+
}
836+
775837
new_node =
776838
factory.instantiateTreeNode(instance_name, toStr(NodeType::SUBTREE), config);
777839
auto subtree_node = dynamic_cast<SubTreeNode*>(new_node.get());
@@ -927,7 +989,8 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(const std::string& tree_ID,
927989
recursiveStep = [&](TreeNode::Ptr parent_node, Tree::Subtree::Ptr subtree,
928990
std::string prefix, const XMLElement* element) {
929991
// create the node
930-
auto node = createNodeFromXML(element, blackboard, parent_node, prefix, output_tree);
992+
TreeNode::Ptr node =
993+
createNodeFromXML(element, blackboard, parent_node, prefix, output_tree);
931994
subtree->nodes.push_back(node);
932995

933996
// common case: iterate through all children
@@ -941,78 +1004,31 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(const std::string& tree_ID,
9411004
}
9421005
else // special case: SubTreeNode
9431006
{
944-
auto new_bb = Blackboard::create(blackboard);
9451007
const std::string subtree_ID = element->Attribute("ID");
946-
std::unordered_map<std::string, std::string> subtree_remapping;
947-
bool do_autoremap = false;
1008+
TreeNode::ConstPtr const_node = node;
9481009

949-
for(auto attr = element->FirstAttribute(); attr != nullptr; attr = attr->Next())
950-
{
951-
std::string attr_name = attr->Name();
952-
std::string attr_value = attr->Value();
953-
if(attr_value == "{=}")
954-
{
955-
attr_value = StrCat("{", attr_name, "}");
956-
}
957-
958-
if(attr_name == "_autoremap")
959-
{
960-
do_autoremap = convertFromString<bool>(attr_value);
961-
new_bb->enableAutoRemapping(do_autoremap);
962-
continue;
963-
}
964-
if(!IsAllowedPortName(attr->Name()))
965-
{
966-
continue;
967-
}
968-
subtree_remapping.insert({ attr_name, attr_value });
969-
}
970-
// check if this subtree has a model. If it does,
971-
// we want to check if all the mandatory ports were remapped and
972-
// add default ones, if necessary
973-
auto subtree_model_it = subtree_models.find(subtree_ID);
974-
if(subtree_model_it != subtree_models.end())
975-
{
976-
const auto& subtree_model_ports = subtree_model_it->second.ports;
977-
// check if:
978-
// - remapping contains mondatory ports
979-
// - if any of these has default value
980-
for(const auto& [port_name, port_info] : subtree_model_ports)
981-
{
982-
auto it = subtree_remapping.find(port_name);
983-
// don't override existing remapping
984-
if(it == subtree_remapping.end() && !do_autoremap)
985-
{
986-
// remapping is not explicitly defined in the XML: use the model
987-
if(port_info.defaultValueString().empty())
988-
{
989-
auto msg = StrCat("In the <TreeNodesModel> the <Subtree ID=\"", subtree_ID,
990-
"\"> is defining a mandatory port called [", port_name,
991-
"], but you are not remapping it");
992-
throw RuntimeError(msg);
993-
}
994-
else
995-
{
996-
subtree_remapping.insert({ port_name, port_info.defaultValueString() });
997-
}
998-
}
999-
}
1000-
}
1001-
1002-
for(const auto& [attr_name, attr_value] : subtree_remapping)
1010+
auto new_bb = Blackboard::create(blackboard);
1011+
const bool do_autoremap = const_node->config().auto_remapped;
1012+
new_bb->enableAutoRemapping(do_autoremap);
1013+
1014+
// Populate the subtree's blackboard with it's port values.
1015+
PortsRemapping subtree_remapping = const_node->config().input_ports;
1016+
const PortsRemapping output_ports = const_node->config().output_ports;
1017+
subtree_remapping.insert(output_ports.begin(), output_ports.end());
1018+
for(const auto& [port_name, port_value] : subtree_remapping)
10031019
{
1004-
if(TreeNode::isBlackboardPointer(attr_value))
1020+
if(TreeNode::isBlackboardPointer(port_value))
10051021
{
10061022
// do remapping
1007-
StringView port_name = TreeNode::stripBlackboardPointer(attr_value);
1008-
new_bb->addSubtreeRemapping(attr_name, port_name);
1023+
StringView pointer_name = TreeNode::stripBlackboardPointer(port_value);
1024+
new_bb->addSubtreeRemapping(port_name, pointer_name);
10091025
}
10101026
else
10111027
{
10121028
// constant string: just set that constant value into the BB
10131029
// IMPORTANT: this must not be autoremapped!!!
10141030
new_bb->enableAutoRemapping(false);
1015-
new_bb->set(attr_name, static_cast<std::string>(attr_value));
1031+
new_bb->set(port_name, static_cast<std::string>(port_value));
10161032
new_bb->enableAutoRemapping(do_autoremap);
10171033
}
10181034
}

tests/CMakeLists.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ set(BT_TESTS
3636

3737
if(ament_cmake_FOUND)
3838

39-
find_package(ament_cmake_gtest REQUIRED)
39+
find_package(ament_cmake_gmock REQUIRED)
4040

41-
ament_add_gtest(behaviortree_cpp_test ${BT_TESTS})
42-
target_link_libraries(behaviortree_cpp_test ${ament_LIBRARIES})
41+
ament_add_gmock(${BTCPP_LIBRARY}_test ${BT_TESTS})
42+
target_link_libraries(${BTCPP_LIBRARY}_test
43+
${TEST_DEPENDECIES}
44+
${ament_LIBRARIES})
4345

4446
else()
4547

tests/gtest_subtree.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
#include <gtest/gtest.h>
2+
#include <gmock/gmock-matchers.h>
23
#include "behaviortree_cpp/bt_factory.h"
34
#include "../sample_nodes/dummy_nodes.h"
45
#include "../sample_nodes/movebase_node.h"
6+
#include "behaviortree_cpp/exceptions.h"
7+
#include "behaviortree_cpp/xml_parsing.h"
58
#include "test_helper.hpp"
69

710
using namespace BT;
11+
using ::testing::Contains;
12+
using ::testing::Pair;
813

914
TEST(SubTree, SiblingPorts_Issue_72)
1015
{
@@ -516,6 +521,11 @@ class Assert : public BT::SyncActionNode
516521
private:
517522
virtual BT::NodeStatus tick() override
518523
{
524+
const auto& condition = getInput<bool>("condition");
525+
if(!condition.has_value())
526+
{
527+
throw RuntimeError("Missing input port 'condition'.");
528+
}
519529
if(getInput<bool>("condition").value())
520530
return BT::NodeStatus::SUCCESS;
521531
else
@@ -586,6 +596,19 @@ TEST(SubTree, SubtreeModels)
586596

587597
BehaviorTreeFactory factory;
588598
auto tree = factory.createTreeFromText(xml_text);
599+
600+
const TreeNode* subtreeNode = nullptr;
601+
tree.applyVisitor([&subtreeNode](const TreeNode* node) {
602+
if(node->name() == "MySub")
603+
{
604+
subtreeNode = node;
605+
}
606+
});
607+
608+
ASSERT_NE(subtreeNode, nullptr);
609+
EXPECT_THAT(subtreeNode->config().input_ports, Contains(Pair("in_name", "{my_name}")));
610+
EXPECT_THAT(subtreeNode->config().output_ports, Contains(Pair("out_state", "{my_"
611+
"state}")));
589612
tree.tickWhileRunning();
590613
}
591614

0 commit comments

Comments
 (0)