Skip to content

Commit

Permalink
support Enums in string conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
facontidavide committed Jan 2, 2023
1 parent 5e16d72 commit bcd112c
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 12 deletions.
10 changes: 9 additions & 1 deletion include/behaviortree_cpp/basic_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,15 @@ using StringView = std::string_view;
template <typename T>
inline T convertFromString(StringView /*str*/)
{
static_assert(true, "This template specialization of convertFromString doesn't exist");
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 <>
Expand Down
33 changes: 26 additions & 7 deletions include/behaviortree_cpp/tree_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,6 @@ class TreeNode
std::array<ScriptFunction, size_t(PreCond::COUNT_)> pre_parsed_;
std::array<ScriptFunction, size_t(PostCond::COUNT_)> post_parsed_;

std::shared_ptr<ScriptingEnumsRegistry> scripting_enums_;

Expected<NodeStatus> checkPreConditions();
void checkPostConditions(NodeStatus status);

Expand All @@ -316,6 +314,28 @@ class TreeNode
template <typename T>
inline Result TreeNode::getInput(const std::string& key, T& destination) const
{
// address the special case where T is an enum
auto ParseString = [this](const std::string& str) -> T
{
if constexpr (std::is_enum_v<T> && !std::is_same_v<T, NodeStatus>)
{
auto it = config_.enums->find(str);
// conversion available
if( it != config_.enums->end() )
{
return static_cast<T>(it->second);
}
else {
// hopefully str contains a number that can be parsed. May throw
return static_cast<T>(convertFromString<int>(str));
}
}
else {
return convertFromString<T>(str);
}
};


auto remap_it = config_.input_ports.find(key);
if (remap_it == config_.input_ports.end())
{
Expand All @@ -327,18 +347,17 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
auto remapped_res = getRemappedKey(key, remap_it->second);
try
{
// pure string, not a blackboard key
if (!remapped_res)
{
destination = convertFromString<T>(remap_it->second);
destination = ParseString(remap_it->second);
return {};
}
const auto& remapped_key = remapped_res.value();

if (!config_.blackboard)
{
return nonstd::make_unexpected("getInput() trying to access a Blackboard(BB) "
"entry, "
"but BB is invalid");
return nonstd::make_unexpected("getInput(): trying to access an invalid Blackboard");
}

std::unique_lock<std::mutex> entry_lock(config_.blackboard->entryMutex());
Expand All @@ -348,7 +367,7 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
if (!std::is_same_v<T, std::string> &&
val->type() == typeid(std::string))
{
destination = convertFromString<T>(val->cast<std::string>());
destination = ParseString(val->cast<std::string>());
}
else
{
Expand Down
24 changes: 20 additions & 4 deletions src/basic_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,31 +107,47 @@ template <>
int convertFromString<int>(StringView str)
{
int result = 0;
std::from_chars(str.data(), str.data() + str.size(), result);
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
if(ec != std::errc())
{
throw RuntimeError(StrCat("Can't convert string [", str, "] to int"));
}
return result;
}

template <>
long convertFromString<long>(StringView str)
{
long result = 0;
std::from_chars(str.data(), str.data() + str.size(), result);
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
if(ec != std::errc())
{
throw RuntimeError(StrCat("Can't convert string [", str, "] to long"));
}
return result;
}

template <>
unsigned convertFromString<unsigned>(StringView str)
{
unsigned result = 0;
std::from_chars(str.data(), str.data() + str.size(), result);
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
if(ec != std::errc())
{
throw RuntimeError(StrCat("Can't convert string [", str, "] to unsigned"));
}
return result;
}

template <>
unsigned long convertFromString<unsigned long>(StringView str)
{
unsigned long result = 0;
std::from_chars(str.data(), str.data() + str.size(), result);
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
if(ec != std::errc())
{
throw RuntimeError(StrCat("Can't convert string [", str, "] to unsigned long"));
}
return result;
}

Expand Down
61 changes: 61 additions & 0 deletions tests/gtest_ports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class ActionVectorIn : public SyncActionNode
{
return {BT::InputPort<std::vector<double>>("states")};
}
private:
std::vector<double>* states_;
};

Expand Down Expand Up @@ -230,3 +231,63 @@ TEST(PortTest, SubtreeStringInput_Issue489)
ASSERT_EQ(7, states[1]);
}

enum class Color
{
Red = 0,
Blue = 1,
Green = 2,
Undefined
};

class ActionEnum : public SyncActionNode
{
public:
ActionEnum(const std::string& name, const NodeConfig& config) :
SyncActionNode(name, config)
{}

NodeStatus tick() override
{
getInput("color", color);
return NodeStatus::SUCCESS;
}

static PortsList providedPorts()
{
return {BT::InputPort<Color>("color")};
}

Color color = Color::Undefined;
};

TEST(PortTest, StrintToEnum)
{
std::string xml_txt = R"(
<root BTCPP_format="4" >
<BehaviorTree ID="Main">
<Sequence>
<ActionEnum color="Blue"/>
<ActionEnum color="2"/>
</Sequence>
</BehaviorTree>
</root>)";

BehaviorTreeFactory factory;
factory.registerNodeType<ActionEnum>("ActionEnum");
factory.registerScriptingEnums<Color>();

auto tree = factory.createTreeFromText(xml_txt);

NodeStatus status = tree.tickWhileRunning();

ASSERT_EQ(status, NodeStatus::SUCCESS);

auto first_node = dynamic_cast<ActionEnum*>(tree.subtrees.front()->nodes[1].get());
auto second_node = dynamic_cast<ActionEnum*>(tree.subtrees.front()->nodes[2].get());

ASSERT_EQ(Color::Blue, first_node->color);
ASSERT_EQ(Color::Green, second_node->color);
}



0 comments on commit bcd112c

Please sign in to comment.