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
373/// Describes a character returned by findForbiddenChar() for an error message.
374[[nodiscard]] std::string formatForbiddenChar(char c);
375
376class TypeInfo
377{
378public:
379 template <typename T>
380 static TypeInfo Create()
381 {
382 return TypeInfo{ typeid(T), GetAnyFromStringFunctor<T>() };
383 }
384
385 TypeInfo() : type_info_(typeid(AnyTypeAllowed)), type_str_("AnyTypeAllowed")
386 {}
387
388 TypeInfo(std::type_index type_info, StringConverter conv)
389 : type_info_(type_info), converter_(conv), type_str_(BT::demangle(type_info))
390 {}
391
392 [[nodiscard]] const std::type_index& type() const;
393
394 [[nodiscard]] const std::string& typeName() const;
395
396 [[nodiscard]] Any parseString(const char* str) const;
397
398 [[nodiscard]] Any parseString(const std::string& str) const;
399
400 template <typename T>
401 [[nodiscard]] Any parseString(const T&) const
402 {
403 // avoid compilation errors
404 return {};
405 }
406
407 [[nodiscard]] bool isStronglyTyped() const
408 {
409 return type_info_ != typeid(AnyTypeAllowed) && type_info_ != typeid(BT::Any);
410 }
411
412 [[nodiscard]] const StringConverter& converter() const
413 {
414 return converter_;
415 }
416
417private:
418 std::type_index type_info_;
419 StringConverter converter_;
420 std::string type_str_;
421};
422
423class PortInfo : public TypeInfo
424{
425public:
426 PortInfo(PortDirection direction = PortDirection::INOUT)
427 : TypeInfo(), direction_(direction)
428 {}
429
430 PortInfo(PortDirection direction, std::type_index type_info, StringConverter conv)
431 : TypeInfo(type_info, conv), direction_(direction)
432 {}
433
434 [[nodiscard]] PortDirection direction() const;
435
436 void setDescription(StringView description);
437
438 template <typename T>
439 void setDefaultValue(const T& default_value)
440 {
441 default_value_ = Any(default_value);
442 try
443 {
444 default_value_str_ = BT::toStr(default_value);
445 }
446 // NOLINTNEXTLINE(bugprone-empty-catch)
447 catch(LogicError&)
448 {
449 // conversion to string not available for this type, ignore
450 }
451 }
452
453 [[nodiscard]] const std::string& description() const;
454
455 [[nodiscard]] const Any& defaultValue() const;
456
457 [[nodiscard]] const std::string& defaultValueString() const;
458
459private:
460 PortDirection direction_;
461 std::string description_;
462 Any default_value_;
463 std::string default_value_str_;
464};
465
466template <typename T = AnyTypeAllowed>
467[[nodiscard]] std::pair<std::string, PortInfo> CreatePort(PortDirection direction,
468 StringView name,
469 StringView description = {})
470{
471 auto sname = static_cast<std::string>(name);
472 if(!IsAllowedPortName(sname))
473 {
474 if(const char c = findForbiddenChar(sname); c != '\0')
475 {
476 throw RuntimeError("Port name '", sname, "' contains forbidden character ",
477 formatForbiddenChar(c));
478 }
479 throw RuntimeError("The name of a port must not be `name` or `ID` "
480 "and must start with an alphabetic character. "
481 "Underscore is reserved.");
482 }
483
484 std::pair<std::string, PortInfo> out;
485
486 if(std::is_same<T, void>::value)
487 {
488 out = { sname, PortInfo(direction) };
489 }
490 else
491 {
492 out = { sname, PortInfo(direction, typeid(T), GetAnyFromStringFunctor<T>()) };
493 }
494 if(!description.empty())
495 {
496 out.second.setDescription(description);
497 }
498 return out;
499}
500
501//----------
502/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT, ...)
503 *
504 * @param name the name of the port
505 * @param description optional human-readable description
506 */
507template <typename T = AnyTypeAllowed>
508[[nodiscard]] inline std::pair<std::string, PortInfo>
509InputPort(StringView name, StringView description = {})
510{
511 return CreatePort<T>(PortDirection::INPUT, name, description);
512}
513
514/** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
515 *
516 * @param name the name of the port
517 * @param description optional human-readable description
518 */
519template <typename T = AnyTypeAllowed>
520[[nodiscard]] inline std::pair<std::string, PortInfo>
521OutputPort(StringView name, StringView description = {})
522{
523 return CreatePort<T>(PortDirection::OUTPUT, name, description);
524}
525
526/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
527 *
528 * @param name the name of the port
529 * @param description optional human-readable description
530 */
531template <typename T = AnyTypeAllowed>
532[[nodiscard]] inline std::pair<std::string, PortInfo>
533BidirectionalPort(StringView name, StringView description = {})
534{
535 return CreatePort<T>(PortDirection::INOUT, name, description);
536}
537//----------
538
539namespace details
540{
541
542template <typename T = AnyTypeAllowed, typename DefaultT = T>
543[[nodiscard]] inline std::pair<std::string, PortInfo>
544PortWithDefault(PortDirection direction, StringView name, const DefaultT& default_value,
545 StringView description)
546{
547 static_assert(IsConvertibleToString<DefaultT>() || std::is_convertible_v<T, DefaultT> ||
548 std::is_constructible_v<T, DefaultT>,
549 "The default value must be either the same of the port or string");
550
551 auto out = CreatePort<T>(direction, name, description);
552
553 if constexpr(std::is_constructible_v<T, DefaultT>)
554 {
555 out.second.setDefaultValue(T(default_value));
556 }
557 else if constexpr(IsConvertibleToString<DefaultT>())
558 {
559 out.second.setDefaultValue(std::string(default_value));
560 }
561 else
562 {
563 out.second.setDefaultValue(default_value);
564 }
565 return out;
566}
567
568} // end namespace details
569
570/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INPUT,...)
571 * It also sets the PortInfo::defaultValue()
572 *
573 * @param name the name of the port
574 * @param default_value default value of the port, either type T of BlackboardKey
575 * @param description optional human-readable description
576 */
577template <typename T = AnyTypeAllowed, typename DefaultT = T>
578[[nodiscard]] inline std::pair<std::string, PortInfo>
579InputPort(StringView name, const DefaultT& default_value, StringView description)
580{
581 return details::PortWithDefault<T, DefaultT>(PortDirection::INPUT, name, default_value,
582 description);
583}
584
585/** Syntactic sugar to invoke CreatePort<T>(PortDirection::INOUT,...)
586 * It also sets the PortInfo::defaultValue()
587 *
588 * @param name the name of the port
589 * @param default_value default value of the port, either type T of BlackboardKey
590 * @param description optional human-readable description
591 */
592template <typename T = AnyTypeAllowed, typename DefaultT = T>
593[[nodiscard]] inline std::pair<std::string, PortInfo>
594BidirectionalPort(StringView name, const DefaultT& default_value, StringView description)
595{
596 return details::PortWithDefault<T, DefaultT>(PortDirection::INOUT, name, default_value,
597 description);
598}
599
600/** Syntactic sugar to invoke CreatePort<T>(PortDirection::OUTPUT,...)
601 * It also sets the PortInfo::defaultValue()
602 *
603 * @param name the name of the port
604 * @param default_value default blackboard entry where the output is written
605 * @param description optional human-readable description
606 */
607template <typename T = AnyTypeAllowed>
608[[nodiscard]] inline std::pair<std::string, PortInfo> OutputPort(StringView name,
609 StringView default_value,
610 StringView description)
611{
612 if(default_value.empty() || default_value.front() != '{' || default_value.back() != '}')
613 {
614 throw LogicError("Output port can only refer to blackboard entries, i.e. use the "
615 "syntax '{port_name}'");
616 }
617 auto out = CreatePort<T>(PortDirection::OUTPUT, name, description);
618 out.second.setDefaultValue(default_value);
619 return out;
620}
621
622//----------
623
625
626template <typename T, typename = void>
628{
629};
630
631template <typename T>
633 T, typename std::enable_if<
634 std::is_same<decltype(T::providedPorts()), PortsList>::value>::type>
635 : std::true_type
636{
637};
638
639template <typename T, typename = void>
641{
642};
643
644template <typename T>
646 T, typename std::enable_if<
647 std::is_same<decltype(T::metadata()), KeyValueVector>::value>::type>
648 : std::true_type
649{
650};
651
652template <typename T>
653[[nodiscard]] inline PortsList
654getProvidedPorts(enable_if<has_static_method_providedPorts<T>> = nullptr)
655{
656 return T::providedPorts();
657}
658
659template <typename T>
660[[nodiscard]] inline PortsList
661getProvidedPorts(enable_if_not<has_static_method_providedPorts<T>> = nullptr)
662{
663 return {};
664}
665
666using TimePoint = std::chrono::high_resolution_clock::time_point;
667using Duration = std::chrono::high_resolution_clock::duration;
668
669} // namespace BT
Definition: safe_any.hpp:57
Definition: exceptions.h:48
Definition: basic_types.h:424
Definition: basic_types.h:377
The SwitchNode is equivalent to a switch statement, where a certain branch (child) is executed accord...
Definition: basic_types.h:540
Definition: action_node.h:24
std::pair< std::string, PortInfo > BidirectionalPort(StringView name, StringView description={})
Definition: basic_types.h:533
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:608
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:579
std::pair< std::string, PortInfo > OutputPort(StringView name, StringView description={})
Definition: basic_types.h:521
std::string formatForbiddenChar(char c)
Describes a character returned by findForbiddenChar() for an error message.
std::pair< std::string, PortInfo > InputPort(StringView name, StringView description={})
Definition: basic_types.h:509
std::pair< std::string, PortInfo > BidirectionalPort(StringView name, const DefaultT &default_value, StringView description)
Definition: basic_types.h:594
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:641
Definition: basic_types.h:628