Skip to content

Commit bcd112c

Browse files
committed
support Enums in string conversion
1 parent 5e16d72 commit bcd112c

File tree

4 files changed

+116
-12
lines changed

4 files changed

+116
-12
lines changed

include/behaviortree_cpp/basic_types.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,15 @@ using StringView = std::string_view;
7272
template <typename T>
7373
inline T convertFromString(StringView /*str*/)
7474
{
75-
static_assert(true, "This template specialization of convertFromString doesn't exist");
75+
auto type_name = BT::demangle(typeid(T));
76+
77+
std::cerr << "You (maybe indirectly) called BT::convertFromString() for type ["
78+
<< type_name << "], but I can't find the template specialization.\n"
79+
<< std::endl;
80+
81+
throw LogicError(std::string("You didn't implement the template specialization of "
82+
"convertFromString for this type: ") +
83+
type_name);
7684
}
7785

7886
template <>

include/behaviortree_cpp/tree_node.h

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,6 @@ class TreeNode
302302
std::array<ScriptFunction, size_t(PreCond::COUNT_)> pre_parsed_;
303303
std::array<ScriptFunction, size_t(PostCond::COUNT_)> post_parsed_;
304304

305-
std::shared_ptr<ScriptingEnumsRegistry> scripting_enums_;
306-
307305
Expected<NodeStatus> checkPreConditions();
308306
void checkPostConditions(NodeStatus status);
309307

@@ -316,6 +314,28 @@ class TreeNode
316314
template <typename T>
317315
inline Result TreeNode::getInput(const std::string& key, T& destination) const
318316
{
317+
// address the special case where T is an enum
318+
auto ParseString = [this](const std::string& str) -> T
319+
{
320+
if constexpr (std::is_enum_v<T> && !std::is_same_v<T, NodeStatus>)
321+
{
322+
auto it = config_.enums->find(str);
323+
// conversion available
324+
if( it != config_.enums->end() )
325+
{
326+
return static_cast<T>(it->second);
327+
}
328+
else {
329+
// hopefully str contains a number that can be parsed. May throw
330+
return static_cast<T>(convertFromString<int>(str));
331+
}
332+
}
333+
else {
334+
return convertFromString<T>(str);
335+
}
336+
};
337+
338+
319339
auto remap_it = config_.input_ports.find(key);
320340
if (remap_it == config_.input_ports.end())
321341
{
@@ -327,18 +347,17 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
327347
auto remapped_res = getRemappedKey(key, remap_it->second);
328348
try
329349
{
350+
// pure string, not a blackboard key
330351
if (!remapped_res)
331352
{
332-
destination = convertFromString<T>(remap_it->second);
353+
destination = ParseString(remap_it->second);
333354
return {};
334355
}
335356
const auto& remapped_key = remapped_res.value();
336357

337358
if (!config_.blackboard)
338359
{
339-
return nonstd::make_unexpected("getInput() trying to access a Blackboard(BB) "
340-
"entry, "
341-
"but BB is invalid");
360+
return nonstd::make_unexpected("getInput(): trying to access an invalid Blackboard");
342361
}
343362

344363
std::unique_lock<std::mutex> entry_lock(config_.blackboard->entryMutex());
@@ -348,7 +367,7 @@ inline Result TreeNode::getInput(const std::string& key, T& destination) const
348367
if (!std::is_same_v<T, std::string> &&
349368
val->type() == typeid(std::string))
350369
{
351-
destination = convertFromString<T>(val->cast<std::string>());
370+
destination = ParseString(val->cast<std::string>());
352371
}
353372
else
354373
{

src/basic_types.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,31 +107,47 @@ template <>
107107
int convertFromString<int>(StringView str)
108108
{
109109
int result = 0;
110-
std::from_chars(str.data(), str.data() + str.size(), result);
110+
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
111+
if(ec != std::errc())
112+
{
113+
throw RuntimeError(StrCat("Can't convert string [", str, "] to int"));
114+
}
111115
return result;
112116
}
113117

114118
template <>
115119
long convertFromString<long>(StringView str)
116120
{
117121
long result = 0;
118-
std::from_chars(str.data(), str.data() + str.size(), result);
122+
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
123+
if(ec != std::errc())
124+
{
125+
throw RuntimeError(StrCat("Can't convert string [", str, "] to long"));
126+
}
119127
return result;
120128
}
121129

122130
template <>
123131
unsigned convertFromString<unsigned>(StringView str)
124132
{
125133
unsigned result = 0;
126-
std::from_chars(str.data(), str.data() + str.size(), result);
134+
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
135+
if(ec != std::errc())
136+
{
137+
throw RuntimeError(StrCat("Can't convert string [", str, "] to unsigned"));
138+
}
127139
return result;
128140
}
129141

130142
template <>
131143
unsigned long convertFromString<unsigned long>(StringView str)
132144
{
133145
unsigned long result = 0;
134-
std::from_chars(str.data(), str.data() + str.size(), result);
146+
auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
147+
if(ec != std::errc())
148+
{
149+
throw RuntimeError(StrCat("Can't convert string [", str, "] to unsigned long"));
150+
}
135151
return result;
136152
}
137153

tests/gtest_ports.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ class ActionVectorIn : public SyncActionNode
197197
{
198198
return {BT::InputPort<std::vector<double>>("states")};
199199
}
200+
private:
200201
std::vector<double>* states_;
201202
};
202203

@@ -230,3 +231,63 @@ TEST(PortTest, SubtreeStringInput_Issue489)
230231
ASSERT_EQ(7, states[1]);
231232
}
232233

234+
enum class Color
235+
{
236+
Red = 0,
237+
Blue = 1,
238+
Green = 2,
239+
Undefined
240+
};
241+
242+
class ActionEnum : public SyncActionNode
243+
{
244+
public:
245+
ActionEnum(const std::string& name, const NodeConfig& config) :
246+
SyncActionNode(name, config)
247+
{}
248+
249+
NodeStatus tick() override
250+
{
251+
getInput("color", color);
252+
return NodeStatus::SUCCESS;
253+
}
254+
255+
static PortsList providedPorts()
256+
{
257+
return {BT::InputPort<Color>("color")};
258+
}
259+
260+
Color color = Color::Undefined;
261+
};
262+
263+
TEST(PortTest, StrintToEnum)
264+
{
265+
std::string xml_txt = R"(
266+
<root BTCPP_format="4" >
267+
<BehaviorTree ID="Main">
268+
<Sequence>
269+
<ActionEnum color="Blue"/>
270+
<ActionEnum color="2"/>
271+
</Sequence>
272+
</BehaviorTree>
273+
</root>)";
274+
275+
BehaviorTreeFactory factory;
276+
factory.registerNodeType<ActionEnum>("ActionEnum");
277+
factory.registerScriptingEnums<Color>();
278+
279+
auto tree = factory.createTreeFromText(xml_txt);
280+
281+
NodeStatus status = tree.tickWhileRunning();
282+
283+
ASSERT_EQ(status, NodeStatus::SUCCESS);
284+
285+
auto first_node = dynamic_cast<ActionEnum*>(tree.subtrees.front()->nodes[1].get());
286+
auto second_node = dynamic_cast<ActionEnum*>(tree.subtrees.front()->nodes[2].get());
287+
288+
ASSERT_EQ(Color::Blue, first_node->color);
289+
ASSERT_EQ(Color::Green, second_node->color);
290+
}
291+
292+
293+

0 commit comments

Comments
 (0)