BehaviorTree
Core Library to create and execute Behavior Trees
Loading...
Searching...
No Matches
basic_types.h
1#pragma once
2
3#include "behaviortree_cpp/contrib/expected.hpp"
4#include "behaviortree_cpp/exceptions.h"
5#include "behaviortree_cpp/utils/safe_any.hpp"
6
7#include <chrono>
8#include <functional>
9#include <iostream>
10#include <string_view>
11#include <typeinfo>
12#include <unordered_map>
13#include <utility>
14#include <variant>
15#include <vector>
16
17namespace BT
18{
19/// Enumerates the possible types of nodes
20enum class NodeType
21{
22 UNDEFINED = 0,
23 ACTION,
24 CONDITION,
25 CONTROL,
26 DECORATOR,
27 SUBTREE
28};
29
30/// Enumerates the states every node can be in after execution during a particular
31/// time step.
32/// IMPORTANT: Your custom nodes should NEVER return IDLE.
33enum class NodeStatus
34{
35 IDLE = 0,
36 RUNNING = 1,
37 SUCCESS = 2,
38 FAILURE = 3,
39 SKIPPED = 4,
40};
41
42inline bool isStatusActive(const NodeStatus& status)
43{
44 return status != NodeStatus::IDLE && status != NodeStatus::SKIPPED;
45}
46
47inline bool isStatusCompleted(const NodeStatus& status)
48{
49 return status == NodeStatus::SUCCESS || status == NodeStatus::FAILURE;
50}
51
52enum class PortDirection
53{
54 INPUT,
55 OUTPUT,
56 INOUT
57};
58
60
61bool StartWith(StringView str, StringView prefix);
62
63bool StartWith(StringView str, char prefix);
64
65// vector of key/value pairs
67
68/** Usage: given a function/method like this:
69 *
70 * Expected<double> getAnswer();
71 *
72 * User code can check result and error message like this:
73 *
74 * auto res = getAnswer();
75 * if( res )
76 * {
77 * std::cout << "answer was: " << res.value() << std::endl;
78 * }
79 * else{
80 * std::cerr << "failed to get the answer: " << res.error() << std::endl;
81 * }
82 *
83 * */
84template <typename T>
85using Expected = nonstd::expected<T, std::string>;
86
87struct AnyTypeAllowed
88{
89};
90
91/**
92 * @brief convertFromJSON will parse a json string and use JsonExporter
93 * to convert its content to a given type. It will work only if
94 * the type was previously registered. May throw if it fails.
95 *
96 * @param json_text a valid JSON string
97 * @param type you must specify the typeid()
98 * @return the object, wrapped in Any.
99 */
100[[nodiscard]] Any convertFromJSON(StringView json_text, std::type_index type);
101
102/// Same as the non template version, but with automatic casting
103template <typename T>
104[[nodiscard]] inline T convertFromJSON(StringView str)
105{
106 return convertFromJSON(str, typeid(T)).cast<T>();
107}
108
109/**
110 * convertFromString is used to convert a string into a custom type.
111 *
112 * This function is invoked under the hood by TreeNode::getInput(), but only when the
113 * input port contains a string.
114 *
115 * If you have a custom type, you need to implement the corresponding
116 * template specialization.
117 *
118 * If the string starts with the prefix "json:", it will
119 * fall back to convertFromJSON()
120 */
121template <typename T>
122[[nodiscard]] inline T convertFromString(StringView str)
123{
124 // if string starts with "json:{", try to parse it as json
125 if(StartWith(str, "json:"))
126 {
127 str.remove_prefix(5);
128 return convertFromJSON<T>(str);
129 }
130
131 auto type_name = BT::demangle(typeid(T));
132
133 std::cerr << "You (maybe indirectly) called BT::convertFromString() for type ["
134 << type_name << "], but I can't find the template specialization.\n"
135 << std::endl;
136
137 throw LogicError(std::string("You didn't implement the template specialization of "
138 "convertFromString for this type: ") +
139 type_name);
140}
141
142template <>
143[[nodiscard]] std::string convertFromString<std::string>(StringView str);
144
145template <>
146[[nodiscard]] const char* convertFromString<const char*>(StringView str);
147
148template <>
149[[nodiscard]] int8_t convertFromString<int8_t>(StringView str);
150
151template <>
152[[nodiscard]] int16_t convertFromString<int16_t>(StringView str);
153
154template <>
155[[nodiscard]] int32_t convertFromString<int32_t>(StringView str);
156
157template <>
158[[nodiscard]] int64_t convertFromString<int64_t>(StringView str);
159
160template <>
161[[nodiscard]] uint8_t convertFromString<uint8_t>(StringView str);
162
163template <>
164[[nodiscard]] uint16_t convertFromString<uint16_t>(StringView str);
165
166template <>
167[[nodiscard]] uint32_t convertFromString<uint32_t>(StringView str);
168
169template <>
170[[nodiscard]] uint64_t convertFromString<uint64_t>(StringView str);
171
172template <>
173[[nodiscard]] float convertFromString<float>(StringView str);
174
175template <>
176[[nodiscard]] double convertFromString<double>(StringView str);
177
178/**
179 * @brief Parse a double from a string using the semantics of
180 * std::from_chars(std::chars_format::general): locale-independent (only '.' as
181 * the decimal separator), rejecting leading whitespace, a leading '+', and hex
182 * floats. Includes a thread-safe fallback for standard libraries that lack the
183 * floating-point std::from_chars overload (e.g. Apple libc++).
184 *
185 * @param str the input string.
186 * @param out set to the parsed value on success (left untouched on failure).
187 * @param require_full_consumption when true, the whole string must be a valid
188 * double (trailing characters cause failure); when false, parsing stops
189 * at the first non-numeric character.
190 * @return true on success.
191 */
192[[nodiscard]] bool parseDouble(StringView str, double& out,
193 bool require_full_consumption);
194
195// Integer numbers separated by the character ";"
196template <>
197[[nodiscard]] std::vector<int> convertFromString<std::vector<int>>(StringView str);
198
199// Real numbers separated by the character ";"
200template <>
201[[nodiscard]] std::vector<double> convertFromString<std::vector<double>>(StringView str);
202
203// Boolean values separated by the character ";"
204template <>
205[[nodiscard]] std::vector<bool> convertFromString<std::vector<bool>>(StringView str);
206
207// Strings separated by the character ";"
208template <>
209[[nodiscard]] std::vector<std::string>
210convertFromString<std::vector<std::string>>(StringView str);
211
212// This recognizes either 0/1, true/false, TRUE/FALSE
213template <>
214[[nodiscard]] bool convertFromString<bool>(StringView str);
215
216// Names with all capital letters
217template <>
218[[nodiscard]] NodeStatus convertFromString<NodeStatus>(StringView str);
219
220// Names with all capital letters
221template <>
222[[nodiscard]] NodeType convertFromString<NodeType>(StringView str);
223
224template <>
225[[nodiscard]] PortDirection convertFromString<PortDirection>(StringView str);
226
228
230
231// helper function
232template <typename T>
233[[nodiscard]] inline StringConverter GetAnyFromStringFunctor()
234{
235 if constexpr(std::is_constructible_v<StringView, T>)
236 {
237 return [](StringView str) { return Any(str); };
238 }
239 else if constexpr(std::is_same_v<BT::AnyTypeAllowed, T> || std::is_enum_v<T>)
240 {
241 return {};
242 }
243 else
244 {
245 return [](StringView str) { return Any(convertFromString<T>(str)); };
246 }
247}
248
249template <>
250[[nodiscard]] inline StringConverter GetAnyFromStringFunctor<void>()
251{
252 return {};
253}
254
255//------------------------------------------------------------------
256
257template <typename T>
258constexpr bool IsConvertibleToString()
259{
260 return std::is_convertible_v<T, std::string> ||
261 std::is_convertible_v<T, std::string_view>;
262}
263
264Expected<std::string> toJsonString(const Any& value);
265
266/**
267 * @brief toStr is the reverse operation of convertFromString.
268 *
269 * If T is a custom type and there is no template specialization,
270 * it will try to fall back to toJsonString()
271 */
272template <typename T>
273[[nodiscard]] std::string toStr(const T& value)
274{
275 if constexpr(IsConvertibleToString<T>())
276 {
277 return static_cast<std::string>(value);
278 }
279 else if constexpr(!std::is_arithmetic_v<T>)
280 {
281 if(auto str = toJsonString(Any(value)))
282 {
283 return *str;
284 }
285
286 throw LogicError(StrCat("Function BT::toStr<T>() not specialized for type [",
287 BT::demangle(typeid(T)), "]"));
288 }
289 else
290 {
291 return std::to_string(value);
292 }
293}
294
295template <>
296[[nodiscard]] std::string toStr<bool>(const bool& value);
297
298template <>
299[[nodiscard]] std::string toStr<std::string>(const std::string& value);
300
301template <>
302[[nodiscard]] std::string toStr<BT::NodeStatus>(const BT::NodeStatus& status);
303
304/**
305 * @brief toStr converts NodeStatus to string. Optionally colored.
306 */
307[[nodiscard]] std::string toStr(BT::NodeStatus status, bool colored);
308
309std::ostream& operator<<(std::ostream& os, const BT::NodeStatus& status);
310
311template <>
312[[nodiscard]] std::string toStr<BT::NodeType>(const BT::NodeType& type);
313
314std::ostream& operator<<(std::ostream& os, const BT::NodeType& type);
315
316template <>
317[[nodiscard]] std::string toStr<BT::PortDirection>(const BT::PortDirection& direction);
318
319std::ostream& operator<<(std::ostream& os, const BT::PortDirection& type);
320
321// Small utility, unless you want to use <boost/algorithm/string.hpp>
322[[nodiscard]] std::vector<StringView> splitString(const StringView& strToSplit,
323 char delimeter);
324
325template <typename Predicate>
326using enable_if = typename std::enable_if<Predicate::value>::type*;
327
328template <typename Predicate>
329using enable_if_not = typename std::enable_if<!Predicate::value>::type*;
330
331#ifdef USE_BTCPP3_OLD_NAMES
332// note: we also use the name Optional instead of expected because it is more intuitive
333// for users that are not up to date with "modern" C++
334template <typename T>
335using Optional = nonstd::expected<T, std::string>;
336#endif
337
338/** Usage: given a function/method like:
339 *
340 * Result DoSomething();
341 *
342 * User code can check result and error message like this:
343 *
344 * auto res = DoSomething();
345 * if( res )
346 * {
347 * std::cout << "DoSomething() done " << std::endl;
348 * }
349 * else{
350 * std::cerr << "DoSomething() failed with message: " << res.error() << std::endl;
351 * }
352 *
353 * */
354using Result = Expected<std::monostate>;
355
356struct Timestamp
357{
358 // Number being incremented every time a new value is written
359 uint64_t seq = 0;
360 // Last update time. Nanoseconds since epoch
361 std::chrono::nanoseconds time = std::chrono::nanoseconds(0);
362};
363
364[[nodiscard]] bool IsAllowedPortName(StringView str);
365
366[[nodiscard]] bool IsReservedAttribute(StringView str);
367
368/// Returns the first forbidden character found in the name, or '\0' if valid.
369/// Forbidden characters include: space, tab, newline, CR, < > & " ' / \ : * ? | .
370/// and control characters (ASCII 0-31, 127). UTF-8 multibyte sequences are allowed.
371[[nodiscard]] char findForbiddenChar(StringView name);
372
373class TypeInfo
374{
375public:
376 template <typename T>
377 static TypeInfo Create()
378 {
379 return TypeInfo{ typeid(T), GetAnyFromStringFunctor<T>() };
380 }
381
382 TypeInfo() : type_info_(typeid(AnyTypeAllowed)), type_str_("AnyTypeAllowed")
383 {}
384
385 TypeInfo(std::type_index type_info, StringConverter conv)
386 : type_info_(type_info), converter_(conv), type_str_(BT::demangle(type_info))
387 {}
388
389 [[nodiscard]] const std::type_index& type() const;
390
391 [[nodiscard]] const std::string& typeName() const;
392
393 [[nodiscard]] Any parseString(const char* str) const;
394
395 [[nodiscard]] Any parseString(const std::string& str) const;
396
397 template <typename T>
398 [[nodiscard]] Any parseString(const T&) const
399 {
400 // avoid compilation errors
401 return {};
402 }
403
404 [[nodiscard]] bool isStronglyTyped() const
405 {
406 return type_info_ != typeid(AnyTypeAllowed) && type_info_ != typeid(BT::Any);
407 }
408
409 [[nodiscard]] const StringConverter& converter() const
410 {
411 return converter_;
412 }
413
414private:
415 std::type_index type_info_;
416 StringConverter converter_;
417 std::string type_str_;
418};
419
420class PortInfo : public TypeInfo
421{
422public:
423 PortInfo(PortDirection direction = PortDirection::INOUT)
424 : TypeInfo(), direction_(direction)
425 {}
426
427 PortInfo(PortDirection direction, std::type_index type_info, StringConverter conv)
428 : TypeInfo(type_info, conv), direction_(direction)
429 {}
430
431 [[nodiscard]] PortDirection direction() const;
432
433 void setDescription(StringView description);
434
435 template <typename T>
436 void setDefaultValue(const T& default_value)
437 {
438 default_value_ = Any(default_value);
439 try
440 {
441 default_value_str_ = BT::toStr(default_value);
442 }
443 // NOLINTNEXTLINE(bugprone-empty-catch)
444 catch(LogicError&)
445 {
446 // conversion to string not available for this type, ignore
447 }
448 }
449
450 [[nodiscard]] const std::string& description() const;
451
452 [[nodiscard]] const Any& defaultValue() const;
453
454 [[nodiscard]] const std::string& defaultValueString() const;
455
456private:
457 PortDirection direction_;
458 std::string description_;
459 Any default_value_;
460 std::string default_value_str_;
461};
462
463template <typename T = AnyTypeAllowed>
464[[nodiscard]] std::pair<std::string, PortInfo> CreatePort(PortDirection direction,
465 StringView name,
466 StringView description = {})
467{
468 auto sname = static_cast<std::string>(name);
469 if(!IsAllowedPortName(sname))
470 {
471 throw RuntimeError("The name of a port must not be `name` or `ID` "
472 "and must start with an alphabetic character. "
473 "Underscore is reserved.");
474 }
475
476 std::pair<std::string, PortInfo> out;
477
478 if(std::is_same<T, void>::value)
479 {
480 out = { sname, PortInfo(direction) };
481 }
482 else
483 {
484 out = { sname, PortInfo(direction, typeid(T), GetAnyFromStringFunctor<T>()) };
485 }
486 if(!description.empty())
487 {
488 out.second.setDescription(description);
489 }
490 return out;
491}
492
493//----------
494/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT, ...)
495 *
496 * @param name the name of the port
497 * @param description optional human-readable description
498 */
499template <typename T = AnyTypeAllowed>
500[[nodiscard]] inline std::pair<std::string, PortInfo>
501InputPort(StringView name, StringView description = {})
502{
503 return CreatePort<T>(PortDirection::INPUT, name, description);
504}
505
506/** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
507 *
508 * @param name the name of the port
509 * @param description optional human-readable description
510 */
511template <typename T = AnyTypeAllowed>
512[[nodiscard]] inline std::pair<std::string, PortInfo>
513OutputPort(StringView name, StringView description = {})
514{
515 return CreatePort<T>(PortDirection::OUTPUT, name, description);
516}
517
518/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
519 *
520 * @param name the name of the port
521 * @param description optional human-readable description
522 */
523template <typename T = AnyTypeAllowed>
524[[nodiscard]] inline std::pair<std::string, PortInfo>
525BidirectionalPort(StringView name, StringView description = {})
526{
527 return CreatePort<T>(PortDirection::INOUT, name, description);
528}
529//----------
530
531namespace details
532{
533
534template <typename T = AnyTypeAllowed, typename DefaultT = T>
535[[nodiscard]] inline std::pair<std::string, PortInfo>
536PortWithDefault(PortDirection direction, StringView name, const DefaultT& default_value,
537 StringView description)
538{
539 static_assert(IsConvertibleToString<DefaultT>() || std::is_convertible_v<T, DefaultT> ||
540 std::is_constructible_v<T, DefaultT>,
541 "The default value must be either the same of the port or string");
542
543 auto out = CreatePort<T>(direction, name, description);
544
545 if constexpr(std::is_constructible_v<T, DefaultT>)
546 {
547 out.second.setDefaultValue(T(default_value));
548 }
549 else if constexpr(IsConvertibleToString<DefaultT>())
550 {
551 out.second.setDefaultValue(std::string(default_value));
552 }
553 else
554 {
555 out.second.setDefaultValue(default_value);
556 }
557 return out;
558}
559
560} // end namespace details
561
562/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT,...)
563 * It also sets the PortInfo::defaultValue()
564 *
565 * @param name the name of the port
566 * @param default_value default value of the port, either type T of BlackboardKey
567 * @param description optional human-readable description
568 */
569template <typename T = AnyTypeAllowed, typename DefaultT = T>
570[[nodiscard]] inline std::pair<std::string, PortInfo>
571InputPort(StringView name, const DefaultT& default_value, StringView description)
572{
573 return details::PortWithDefault<T, DefaultT>(PortDirection::INPUT, name, default_value,
574 description);
575}
576
577/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
578 * It also sets the PortInfo::defaultValue()
579 *
580 * @param name the name of the port
581 * @param default_value default value of the port, either type T of BlackboardKey
582 * @param description optional human-readable description
583 */
584template <typename T = AnyTypeAllowed, typename DefaultT = T>
585[[nodiscard]] inline std::pair<std::string, PortInfo>
586BidirectionalPort(StringView name, const DefaultT& default_value, StringView description)
587{
588 return details::PortWithDefault<T, DefaultT>(PortDirection::INOUT, name, default_value,
589 description);
590}
591
592/** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
593 * It also sets the PortInfo::defaultValue()
594 *
595 * @param name the name of the port
596 * @param default_value default blackboard entry where the output is written
597 * @param description optional human-readable description
598 */
599template <typename T = AnyTypeAllowed>
600[[nodiscard]] inline std::pair<std::string, PortInfo> OutputPort(StringView name,
601 StringView default_value,
602 StringView description)
603{
604 if(default_value.empty() || default_value.front() != '{' || default_value.back() != '}')
605 {
606 throw LogicError("Output port can only refer to blackboard entries, i.e. use the "
607 "syntax '{port_name}'");
608 }
609 auto out = CreatePort<T>(PortDirection::OUTPUT, name, description);
610 out.second.setDefaultValue(default_value);
611 return out;
612}
613
614//----------
615
617
618template <typename T, typename = void>
620{
621};
622
623template <typename T>
625 T, typename std::enable_if<
626 std::is_same<decltype(T::providedPorts()), PortsList>::value>::type>
627 : std::true_type
628{
629};
630
631template <typename T, typename = void>
633{
634};
635
636template <typename T>
638 T, typename std::enable_if<
639 std::is_same<decltype(T::metadata()), KeyValueVector>::value>::type>
640 : std::true_type
641{
642};
643
644template <typename T>
645[[nodiscard]] inline PortsList
646getProvidedPorts(enable_if<has_static_method_providedPorts<T>> = nullptr)
647{
648 return T::providedPorts();
649}
650
651template <typename T>
652[[nodiscard]] inline PortsList
653getProvidedPorts(enable_if_not<has_static_method_providedPorts<T>> = nullptr)
654{
655 return {};
656}
657
658using TimePoint = std::chrono::high_resolution_clock::time_point;
659using Duration = std::chrono::high_resolution_clock::duration;
660
661} // namespace BT
Definition: safe_any.hpp:50
Definition: exceptions.h:48
Definition: basic_types.h:421
Definition: basic_types.h:374
The SwitchNode is equivalent to a switch statement, where a certain branch (child) is executed accord...
Definition: basic_types.h:532
Definition: action_node.h:24
std::pair< std::string, PortInfo > BidirectionalPort(StringView name, StringView description={})
Definition: basic_types.h:525
NodeStatus
Definition: basic_types.h:34
Any convertFromJSON(StringView json_text, std::type_index type)
convertFromJSON will parse a json string and use JsonExporter to convert its content to a given type....
char findForbiddenChar(StringView name)
bool parseDouble(StringView str, double &out, bool require_full_consumption)
Parse a double from a string using the semantics of std::from_chars(std::chars_format::general): loca...
std::pair< std::string, PortInfo > OutputPort(StringView name, StringView default_value, StringView description)
Definition: basic_types.h:600
std::string toStr(BT::NodeStatus status, bool colored)
toStr converts NodeStatus to string. Optionally colored.
T convertFromJSON(StringView str)
Same as the non template version, but with automatic casting.
Definition: basic_types.h:104
NodeType
Enumerates the possible types of nodes.
Definition: basic_types.h:21
std::pair< std::string, PortInfo > InputPort(StringView name, const DefaultT &default_value, StringView description)
Definition: basic_types.h:571
std::pair< std::string, PortInfo > OutputPort(StringView name, StringView description={})
Definition: basic_types.h:513
std::pair< std::string, PortInfo > InputPort(StringView name, StringView description={})
Definition: basic_types.h:501
std::pair< std::string, PortInfo > BidirectionalPort(StringView name, const DefaultT &default_value, StringView description)
Definition: basic_types.h:586
std::string toStr(const T &value)
toStr is the reverse operation of convertFromString.
Definition: basic_types.h:273
T convertFromString(StringView str)
Definition: basic_types.h:122
Definition: basic_types.h:88
Definition: basic_types.h:357
Definition: basic_types.h:633
Definition: basic_types.h:620