Skip to content

Commit ed6c31c

Browse files
dyackzannbbrooks
andcommitted
Update setOutput<T> to convert vectors to vector<Any> and getInput<T> to convert vector<BT::Any> type to vector<T>
* Support vector<Any> -> vector<typename T::value_type> conversion Don't check port type alignment for vector<Any> * Convert vector to vector<Any> before placing on the blackboard Also update checks to allow mismatch when a port was declared as a vector<T> and we have an input port that takes it in as a vector<Any> * Update include/behaviortree_cpp/blackboard.h Co-authored-by: Nathan Brooks <[email protected]> * Fix formatting with pre-commit * Add unit test passing a vector through ports --------- Co-authored-by: Nathan Brooks <[email protected]>
1 parent 87d2f03 commit ed6c31c

File tree

5 files changed

+209
-7
lines changed

5 files changed

+209
-7
lines changed

include/behaviortree_cpp/blackboard.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <memory>
55
#include <unordered_map>
66
#include <mutex>
7+
#include <regex>
78

89
#include "behaviortree_cpp/basic_types.h"
910
#include "behaviortree_cpp/contrib/json.hpp"
@@ -25,6 +26,23 @@ struct StampedValue
2526
Timestamp stamp;
2627
};
2728

29+
// Helper trait to check if templated type is a std::vector
30+
template <typename T>
31+
struct is_vector : std::false_type
32+
{
33+
};
34+
35+
template <typename T, typename A>
36+
struct is_vector<std::vector<T, A>> : std::true_type
37+
{
38+
};
39+
40+
// Helper function to check if a demangled type string is a std::vector<..>
41+
inline bool isVector(const std::string& type_name)
42+
{
43+
return std::regex_match(type_name, std::regex(R"(^std::vector<.*>$)"));
44+
}
45+
2846
/**
2947
* @brief The Blackboard is the mechanism used by BehaviorTrees to exchange
3048
* typed data.
@@ -257,8 +275,14 @@ inline void Blackboard::set(const std::string& key, const T& value)
257275

258276
std::type_index previous_type = entry.info.type();
259277

278+
// Allow mismatch if going from vector -> vector<Any>.
279+
auto prev_type_demangled = BT::demangle(entry.value.type());
280+
bool previous_is_vector = BT::isVector(prev_type_demangled);
281+
bool new_is_vector_any = new_value.type() == typeid(std::vector<Any>);
282+
260283
// check type mismatch
261-
if(previous_type != std::type_index(typeid(T)) && previous_type != new_value.type())
284+
if(previous_type != std::type_index(typeid(T)) && previous_type != new_value.type() &&
285+
!(previous_is_vector && new_is_vector_any))
262286
{
263287
bool mismatching = true;
264288
if(std::is_constructible<StringView, T>::value)

include/behaviortree_cpp/tree_node.h

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
namespace BT
3232
{
33-
3433
/// This information is used mostly by the XMLParser.
3534
struct TreeNodeManifest
3635
{
@@ -534,6 +533,30 @@ inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
534533

535534
if(!entry->value.empty())
536535
{
536+
// Support vector<Any> -> vector<typename T::value_type> conversion.
537+
// Only want to compile this path when T is a vector type.
538+
if constexpr(is_vector<T>::value)
539+
{
540+
if(!std::is_same_v<T, std::vector<Any>> &&
541+
any_value.type() == typeid(std::vector<Any>))
542+
{
543+
// If the object was originally placed on the blackboard as a vector<Any>, attempt to unwrap the vector
544+
// elements according to the templated type.
545+
auto any_vec = any_value.cast<std::vector<Any>>();
546+
if(!any_vec.empty() &&
547+
any_vec.front().type() != typeid(typename T::value_type))
548+
{
549+
return nonstd::make_unexpected("Invalid cast requested from vector<Any> to "
550+
"vector<typename T::value_type>."
551+
" Element type does not align.");
552+
}
553+
destination = T();
554+
std::transform(
555+
any_vec.begin(), any_vec.end(), std::back_inserter(destination),
556+
[](Any& element) { return element.cast<typename T::value_type>(); });
557+
return Timestamp{ entry->sequence_id, entry->stamp };
558+
}
559+
}
537560
if(!std::is_same_v<T, std::string> && any_value.isString())
538561
{
539562
destination = parseString<T>(any_value.cast<std::string>());
@@ -607,7 +630,19 @@ inline Result TreeNode::setOutput(const std::string& key, const T& value)
607630
}
608631

609632
remapped_key = stripBlackboardPointer(remapped_key);
610-
config().blackboard->set(static_cast<std::string>(remapped_key), value);
633+
634+
if constexpr(is_vector<T>::value && !std::is_same_v<T, std::vector<Any>>)
635+
{
636+
// If the object is a vector but not a vector<Any>, convert it to vector<Any> before placing it on the blackboard.
637+
auto any_vec = std::vector<Any>();
638+
std::transform(value.begin(), value.end(), std::back_inserter(any_vec),
639+
[](const auto& element) { return BT::Any(element); });
640+
config().blackboard->set(static_cast<std::string>(remapped_key), any_vec);
641+
}
642+
else
643+
{
644+
config().blackboard->set(static_cast<std::string>(remapped_key), value);
645+
}
611646

612647
return {};
613648
}

src/blackboard.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,18 @@ std::shared_ptr<Blackboard::Entry> Blackboard::createEntryImpl(const std::string
221221
if(storage_it != storage_.end())
222222
{
223223
const auto& prev_info = storage_it->second->info;
224+
auto prev_type_demangled = BT::demangle(prev_info.type());
225+
// Allow mismatch if going from vector -> vector<Any>.
226+
bool previous_is_vector = BT::isVector(prev_type_demangled);
227+
bool new_is_vector_any = info.type() == typeid(std::vector<Any>);
228+
224229
if(prev_info.type() != info.type() && prev_info.isStronglyTyped() &&
225-
info.isStronglyTyped())
230+
info.isStronglyTyped() && !(previous_is_vector && new_is_vector_any))
226231
{
227232
auto msg = StrCat("Blackboard entry [", key,
228233
"]: once declared, the type of a port"
229234
" shall not change. Previously declared type [",
230-
BT::demangle(prev_info.type()), "], current type [",
235+
prev_type_demangled, "], current type [",
231236
BT::demangle(info.type()), "]");
232237

233238
throw LogicError(msg);

src/xml_parsing.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -831,8 +831,15 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
831831

832832
// special case related to convertFromString
833833
bool const string_input = (prev_info->type() == typeid(std::string));
834-
835-
if(port_type_mismatch && !string_input)
834+
// special case related to unwrapping vector<Any> -> vector<T> objects.
835+
bool const vec_any_input = (prev_info->type() == typeid(std::vector<Any>));
836+
// special case related to wrapping vector<T> -> vector<Any> objects.
837+
auto prev_type_demangled = demangle(prev_info->type());
838+
bool previous_is_vector = BT::isVector(prev_type_demangled);
839+
bool new_is_vector_any = port_info.type() == typeid(std::vector<Any>);
840+
841+
if(port_type_mismatch && !string_input &&
842+
!vec_any_input & !(previous_is_vector && new_is_vector_any))
836843
{
837844
blackboard->debugMessage();
838845

tests/gtest_ports.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,3 +730,134 @@ TEST(PortTest, DefaultWronglyOverriden)
730730
// This is correct
731731
ASSERT_NO_THROW(auto tree = factory.createTreeFromText(xml_txt_correct));
732732
}
733+
734+
class OutputVectorStringNode : public SyncActionNode
735+
{
736+
public:
737+
OutputVectorStringNode(const std::string& name, const NodeConfig& config)
738+
: SyncActionNode(name, config)
739+
{}
740+
static PortsList providedPorts()
741+
{
742+
return { InputPort<std::string>("string1", "val1", "First string"),
743+
InputPort<std::string>("string2", "val2", "Second string"),
744+
OutputPort<std::vector<std::string>>("string_vector", "{string_vector}",
745+
"Vector of strings.") };
746+
}
747+
748+
NodeStatus tick() override
749+
{
750+
auto string1 = getInput<std::string>("string1");
751+
auto string2 = getInput<std::string>("string2");
752+
753+
std::vector<std::string> out = { string1.value(), string2.value() };
754+
setOutput("string_vector", out);
755+
return NodeStatus::SUCCESS;
756+
}
757+
};
758+
759+
class InputVectorStringNode : public SyncActionNode
760+
{
761+
public:
762+
InputVectorStringNode(const std::string& name, const NodeConfig& config)
763+
: SyncActionNode(name, config)
764+
{}
765+
static PortsList providedPorts()
766+
{
767+
return { InputPort<std::vector<std::string>>("string_vector", "{string_vector}",
768+
"Vector of strings.") };
769+
}
770+
771+
NodeStatus tick() override
772+
{
773+
std::vector<std::string> expected_vec = { "val1", "val2" };
774+
std::vector<std::string> actual_vec;
775+
776+
if(!getInput<std::vector<std::string>>("string_vector", actual_vec))
777+
{
778+
return NodeStatus::FAILURE;
779+
}
780+
if(expected_vec == actual_vec)
781+
{
782+
return NodeStatus::SUCCESS;
783+
}
784+
else
785+
{
786+
return NodeStatus::FAILURE;
787+
}
788+
}
789+
};
790+
791+
class InputVectorDoubleNode : public SyncActionNode
792+
{
793+
public:
794+
InputVectorDoubleNode(const std::string& name, const NodeConfig& config)
795+
: SyncActionNode(name, config)
796+
{}
797+
static PortsList providedPorts()
798+
{
799+
return { InputPort<std::vector<double>>("double_vector", "{double_vector}",
800+
"Vector of doubles.") };
801+
}
802+
803+
NodeStatus tick() override
804+
{
805+
std::vector<double> expected_vec = { 1.0, 2.0 };
806+
std::vector<double> actual_vec;
807+
808+
if(!getInput<std::vector<double>>("double_vector", actual_vec))
809+
{
810+
return NodeStatus::FAILURE;
811+
}
812+
if(expected_vec == actual_vec)
813+
{
814+
return NodeStatus::SUCCESS;
815+
}
816+
else
817+
{
818+
return NodeStatus::FAILURE;
819+
}
820+
}
821+
};
822+
823+
TEST(PortTest, VectorAny)
824+
{
825+
BT::BehaviorTreeFactory factory;
826+
factory.registerNodeType<OutputVectorStringNode>("OutputVectorStringNode");
827+
factory.registerNodeType<InputVectorStringNode>("InputVectorStringNode");
828+
factory.registerNodeType<InputVectorDoubleNode>("InputVectorDoubleNode");
829+
830+
std::string xml_txt_good = R"(
831+
<root BTCPP_format="4" >
832+
<BehaviorTree>
833+
<Sequence name="root_sequence">
834+
<OutputVectorStringNode/>
835+
<InputVectorStringNode/>
836+
</Sequence>
837+
</BehaviorTree>
838+
</root>)";
839+
840+
std::string xml_txt_bad = R"(
841+
<root BTCPP_format="4" >
842+
<BehaviorTree>
843+
<Sequence name="root_sequence">
844+
<OutputVectorStringNode/>
845+
<InputVectorDoubleNode double_vector="{string_vector}"/>
846+
</Sequence>
847+
</BehaviorTree>
848+
</root>)";
849+
850+
// Test that setting and retrieving a vector<string> works.
851+
BT::Tree tree;
852+
ASSERT_NO_THROW(tree = factory.createTreeFromText(xml_txt_good));
853+
854+
BT::NodeStatus status;
855+
ASSERT_NO_THROW(status = tree.tickOnce());
856+
ASSERT_EQ(status, NodeStatus::SUCCESS);
857+
858+
// Test that setting a port as a vector<string> and attempting to retrie it as a vector<double> fails.
859+
ASSERT_NO_THROW(tree = factory.createTreeFromText(xml_txt_bad));
860+
861+
ASSERT_NO_THROW(status = tree.tickOnce());
862+
ASSERT_EQ(status, NodeStatus::FAILURE);
863+
}

0 commit comments

Comments
 (0)