Skip to content

Commit 9336ead

Browse files
dyackzannbbrooks
andauthored
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 1fcb624 commit 9336ead

File tree

5 files changed

+209
-7
lines changed

5 files changed

+209
-7
lines changed

Diff for: include/behaviortree_cpp/blackboard.h

+25-1
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)

Diff for: include/behaviortree_cpp/tree_node.h

+37-2
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
{
@@ -521,6 +520,30 @@ inline Expected<Timestamp> TreeNode::getInputStamped(const std::string& key,
521520

522521
if(!entry->value.empty())
523522
{
523+
// Support vector<Any> -> vector<typename T::value_type> conversion.
524+
// Only want to compile this path when T is a vector type.
525+
if constexpr(is_vector<T>::value)
526+
{
527+
if(!std::is_same_v<T, std::vector<Any>> &&
528+
any_value.type() == typeid(std::vector<Any>))
529+
{
530+
// If the object was originally placed on the blackboard as a vector<Any>, attempt to unwrap the vector
531+
// elements according to the templated type.
532+
auto any_vec = any_value.cast<std::vector<Any>>();
533+
if(!any_vec.empty() &&
534+
any_vec.front().type() != typeid(typename T::value_type))
535+
{
536+
return nonstd::make_unexpected("Invalid cast requested from vector<Any> to "
537+
"vector<typename T::value_type>."
538+
" Element type does not align.");
539+
}
540+
destination = T();
541+
std::transform(
542+
any_vec.begin(), any_vec.end(), std::back_inserter(destination),
543+
[](Any& element) { return element.cast<typename T::value_type>(); });
544+
return Timestamp{ entry->sequence_id, entry->stamp };
545+
}
546+
}
524547
if(!std::is_same_v<T, std::string> && any_value.isString())
525548
{
526549
destination = parseString<T>(any_value.cast<std::string>());
@@ -593,7 +616,19 @@ inline Result TreeNode::setOutput(const std::string& key, const T& value)
593616
}
594617

595618
remapped_key = stripBlackboardPointer(remapped_key);
596-
config().blackboard->set(static_cast<std::string>(remapped_key), value);
619+
620+
if constexpr(is_vector<T>::value && !std::is_same_v<T, std::vector<Any>>)
621+
{
622+
// If the object is a vector but not a vector<Any>, convert it to vector<Any> before placing it on the blackboard.
623+
auto any_vec = std::vector<Any>();
624+
std::transform(value.begin(), value.end(), std::back_inserter(any_vec),
625+
[](const auto& element) { return BT::Any(element); });
626+
config().blackboard->set(static_cast<std::string>(remapped_key), any_vec);
627+
}
628+
else
629+
{
630+
config().blackboard->set(static_cast<std::string>(remapped_key), value);
631+
}
597632

598633
return {};
599634
}

Diff for: src/blackboard.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,18 @@ std::shared_ptr<Blackboard::Entry> Blackboard::createEntryImpl(const std::string
217217
if(storage_it != storage_.end())
218218
{
219219
const auto& prev_info = storage_it->second->info;
220+
auto prev_type_demangled = BT::demangle(prev_info.type());
221+
// Allow mismatch if going from vector -> vector<Any>.
222+
bool previous_is_vector = BT::isVector(prev_type_demangled);
223+
bool new_is_vector_any = info.type() == typeid(std::vector<Any>);
224+
220225
if(prev_info.type() != info.type() && prev_info.isStronglyTyped() &&
221-
info.isStronglyTyped())
226+
info.isStronglyTyped() && !(previous_is_vector && new_is_vector_any))
222227
{
223228
auto msg = StrCat("Blackboard entry [", key,
224229
"]: once declared, the type of a port"
225230
" shall not change. Previously declared type [",
226-
BT::demangle(prev_info.type()), "], current type [",
231+
prev_type_demangled, "], current type [",
227232
BT::demangle(info.type()), "]");
228233

229234
throw LogicError(msg);

Diff for: src/xml_parsing.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -786,8 +786,15 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
786786

787787
// special case related to convertFromString
788788
bool const string_input = (prev_info->type() == typeid(std::string));
789-
790-
if(port_type_mismatch && !string_input)
789+
// special case related to unwrapping vector<Any> -> vector<T> objects.
790+
bool const vec_any_input = (prev_info->type() == typeid(std::vector<Any>));
791+
// special case related to wrapping vector<T> -> vector<Any> objects.
792+
auto prev_type_demangled = demangle(prev_info->type());
793+
bool previous_is_vector = BT::isVector(prev_type_demangled);
794+
bool new_is_vector_any = port_info.type() == typeid(std::vector<Any>);
795+
796+
if(port_type_mismatch && !string_input &&
797+
!vec_any_input & !(previous_is_vector && new_is_vector_any))
791798
{
792799
blackboard->debugMessage();
793800

Diff for: tests/gtest_ports.cpp

+131
Original file line numberDiff line numberDiff line change
@@ -705,3 +705,134 @@ TEST(PortTest, DefaultWronglyOverriden)
705705
// This is correct
706706
ASSERT_NO_THROW(auto tree = factory.createTreeFromText(xml_txt_correct));
707707
}
708+
709+
class OutputVectorStringNode : public SyncActionNode
710+
{
711+
public:
712+
OutputVectorStringNode(const std::string& name, const NodeConfig& config)
713+
: SyncActionNode(name, config)
714+
{}
715+
static PortsList providedPorts()
716+
{
717+
return { InputPort<std::string>("string1", "val1", "First string"),
718+
InputPort<std::string>("string2", "val2", "Second string"),
719+
OutputPort<std::vector<std::string>>("string_vector", "{string_vector}",
720+
"Vector of strings.") };
721+
}
722+
723+
NodeStatus tick() override
724+
{
725+
auto string1 = getInput<std::string>("string1");
726+
auto string2 = getInput<std::string>("string2");
727+
728+
std::vector<std::string> out = { string1.value(), string2.value() };
729+
setOutput("string_vector", out);
730+
return NodeStatus::SUCCESS;
731+
}
732+
};
733+
734+
class InputVectorStringNode : public SyncActionNode
735+
{
736+
public:
737+
InputVectorStringNode(const std::string& name, const NodeConfig& config)
738+
: SyncActionNode(name, config)
739+
{}
740+
static PortsList providedPorts()
741+
{
742+
return { InputPort<std::vector<std::string>>("string_vector", "{string_vector}",
743+
"Vector of strings.") };
744+
}
745+
746+
NodeStatus tick() override
747+
{
748+
std::vector<std::string> expected_vec = { "val1", "val2" };
749+
std::vector<std::string> actual_vec;
750+
751+
if(!getInput<std::vector<std::string>>("string_vector", actual_vec))
752+
{
753+
return NodeStatus::FAILURE;
754+
}
755+
if(expected_vec == actual_vec)
756+
{
757+
return NodeStatus::SUCCESS;
758+
}
759+
else
760+
{
761+
return NodeStatus::FAILURE;
762+
}
763+
}
764+
};
765+
766+
class InputVectorDoubleNode : public SyncActionNode
767+
{
768+
public:
769+
InputVectorDoubleNode(const std::string& name, const NodeConfig& config)
770+
: SyncActionNode(name, config)
771+
{}
772+
static PortsList providedPorts()
773+
{
774+
return { InputPort<std::vector<double>>("double_vector", "{double_vector}",
775+
"Vector of doubles.") };
776+
}
777+
778+
NodeStatus tick() override
779+
{
780+
std::vector<double> expected_vec = { 1.0, 2.0 };
781+
std::vector<double> actual_vec;
782+
783+
if(!getInput<std::vector<double>>("double_vector", actual_vec))
784+
{
785+
return NodeStatus::FAILURE;
786+
}
787+
if(expected_vec == actual_vec)
788+
{
789+
return NodeStatus::SUCCESS;
790+
}
791+
else
792+
{
793+
return NodeStatus::FAILURE;
794+
}
795+
}
796+
};
797+
798+
TEST(PortTest, VectorAny)
799+
{
800+
BT::BehaviorTreeFactory factory;
801+
factory.registerNodeType<OutputVectorStringNode>("OutputVectorStringNode");
802+
factory.registerNodeType<InputVectorStringNode>("InputVectorStringNode");
803+
factory.registerNodeType<InputVectorDoubleNode>("InputVectorDoubleNode");
804+
805+
std::string xml_txt_good = R"(
806+
<root BTCPP_format="4" >
807+
<BehaviorTree>
808+
<Sequence name="root_sequence">
809+
<OutputVectorStringNode/>
810+
<InputVectorStringNode/>
811+
</Sequence>
812+
</BehaviorTree>
813+
</root>)";
814+
815+
std::string xml_txt_bad = R"(
816+
<root BTCPP_format="4" >
817+
<BehaviorTree>
818+
<Sequence name="root_sequence">
819+
<OutputVectorStringNode/>
820+
<InputVectorDoubleNode double_vector="{string_vector}"/>
821+
</Sequence>
822+
</BehaviorTree>
823+
</root>)";
824+
825+
// Test that setting and retrieving a vector<string> works.
826+
BT::Tree tree;
827+
ASSERT_NO_THROW(tree = factory.createTreeFromText(xml_txt_good));
828+
829+
BT::NodeStatus status;
830+
ASSERT_NO_THROW(status = tree.tickOnce());
831+
ASSERT_EQ(status, NodeStatus::SUCCESS);
832+
833+
// Test that setting a port as a vector<string> and attempting to retrie it as a vector<double> fails.
834+
ASSERT_NO_THROW(tree = factory.createTreeFromText(xml_txt_bad));
835+
836+
ASSERT_NO_THROW(status = tree.tickOnce());
837+
ASSERT_EQ(status, NodeStatus::FAILURE);
838+
}

0 commit comments

Comments
 (0)