BehaviorTree
Core Library to create and execute Behavior Trees
Loading...
Searching...
No Matches
loop_node.h
1/* Copyright (C) 2022-2025 Davide Faconti - All Rights Reserved
2*
3* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
4* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
5* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7*
8* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
10* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11*/
12
13#pragma once
14
15#include "behaviortree_cpp/decorator_node.h"
16
17#include <deque>
18#include <vector>
19
20namespace BT
21{
22
23// this object will allow us to modify the queue in place,
24// when popping, in a thread safe-way and without copying the entire queue.
25template <typename T>
27
28/**
29 * @brief The LoopNode class is used to pop_front elements from a std::deque.
30 * This element is copied into the port "value" and the child will be executed,
31 * as long as we have elements in the queue.
32 *
33 * See Example 4: ex04_waypoints
34 *
35 * NOTE: unless T is `Any`, `string` or `double`, you must register the loop manually into
36 * the factory.
37 */
38template <typename T = Any>
39class LoopNode : public DecoratorNode
40{
41 bool child_running_ = false;
42 SharedQueue<T> static_queue_;
43 SharedQueue<T> current_queue_;
44
45public:
46 LoopNode(const std::string& name, const NodeConfig& config)
47 : DecoratorNode(name, config)
48 {
49 auto raw_port = getRawPortValue("queue");
50 if(!isBlackboardPointer(raw_port))
51 {
52 static_queue_ = convertFromString<SharedQueue<T>>(raw_port);
53 }
54 }
55
56 NodeStatus tick() override
57 {
58 bool popped = false;
59 if(status() == NodeStatus::IDLE)
60 {
61 child_running_ = false;
62 current_queue_.reset();
63
64 // special case: the port contains a string that was converted to SharedQueue<T>
65 if(static_queue_)
66 {
67 current_queue_ = std::make_shared<std::deque<T>>();
68 *current_queue_ = *static_queue_;
69 }
70 }
71
72 // Pop value from queue, if the child is not RUNNING
73 if(!child_running_)
74 {
75 // if the port is static, any_ref is empty, otherwise it will keep access to
76 // port locked for thread-safety
77 AnyPtrLocked any_ref =
78 static_queue_ ? AnyPtrLocked() : getLockedPortContent("queue");
79 if(any_ref)
80 {
81 // Try SharedQueue<T> first, then fall back to std::vector<T>.
82 // This allows upstream nodes that output vector<T> to be used
83 // directly with LoopNode without manual conversion. Issue #969.
84 auto queue_result = any_ref.get()->tryCast<SharedQueue<T>>();
85 if(queue_result)
86 {
87 current_queue_ = queue_result.value();
88 }
89 else if(!current_queue_)
90 {
91 // Only convert on first read; after that, use the
92 // already-converted current_queue_ (which gets popped).
93 auto vec_result = any_ref.get()->tryCast<std::vector<T>>();
94 if(vec_result)
95 {
96 // Accept std::vector<T> from upstream nodes. Issue #969.
97 const auto& vec = vec_result.value();
98 current_queue_ = std::make_shared<std::deque<T>>(vec.begin(), vec.end());
99 }
100 else if(any_ref.get()->isString())
101 {
102 // Accept string values (e.g. from subtree remapping). Issue #1065.
103 auto str = any_ref.get()->cast<std::string>();
104 current_queue_ = convertFromString<SharedQueue<T>>(str);
105 }
106 else
107 {
108 throw RuntimeError("LoopNode: port 'queue' must contain either "
109 "SharedQueue<T>, std::vector<T>, or a string");
110 }
111 }
112 }
113
114 if(current_queue_ && !current_queue_->empty())
115 {
116 auto value = std::move(current_queue_->front());
117 current_queue_->pop_front();
118 popped = true;
119 setOutput("value", value);
120 }
121 }
122
123 if(!popped && !child_running_)
124 {
125 return getInput<NodeStatus>("if_empty").value();
126 }
127
128 if(status() == NodeStatus::IDLE)
129 {
130 setStatus(NodeStatus::RUNNING);
131 }
132
133 NodeStatus child_state = child_node_->executeTick();
134 child_running_ = (child_state == NodeStatus::RUNNING);
135
136 if(isStatusCompleted(child_state))
137 {
139 }
140
141 if(child_state == NodeStatus::FAILURE)
142 {
143 return NodeStatus::FAILURE;
144 }
145 return NodeStatus::RUNNING;
146 }
147
148 static PortsList providedPorts()
149 {
150 // Use an untyped BidirectionalPort to accept both SharedQueue<T>
151 // and std::vector<T> without triggering a port type mismatch. Issue #969.
152 return { BidirectionalPort("queue"),
153 InputPort<NodeStatus>("if_empty", NodeStatus::SUCCESS,
154 "Status to return if queue is empty: "
155 "SUCCESS, FAILURE, SKIPPED"),
156 OutputPort<T>("value") };
157 }
158};
159
160template <>
161inline SharedQueue<int> convertFromString<SharedQueue<int>>(StringView str)
162{
163 auto parts = splitString(str, ';');
164 SharedQueue<int> output = std::make_shared<std::deque<int>>();
165 for(const StringView& part : parts)
166 {
168 }
169 return output;
170}
171
172template <>
174{
175 auto parts = splitString(str, ';');
176 SharedQueue<bool> output = std::make_shared<std::deque<bool>>();
177 for(const StringView& part : parts)
178 {
180 }
181 return output;
182}
183
184template <>
186{
187 auto parts = splitString(str, ';');
188 SharedQueue<double> output = std::make_shared<std::deque<double>>();
189 for(const StringView& part : parts)
190 {
192 }
193 return output;
194}
195
196template <>
197inline SharedQueue<std::string>
199{
200 auto parts = splitString(str, ';');
202 for(const StringView& part : parts)
203 {
205 }
206 return output;
207}
208
209} // namespace BT
Definition: safe_any.hpp:50
The DecoratorNode is the base class for nodes that have exactly one child.
Definition: decorator_node.h:19
The LoopNode class is used to pop_front elements from a std::deque. This element is copied into the p...
Definition: loop_node.h:40
NodeStatus tick() override
Method to be implemented by the user.
Definition: loop_node.h:56
virtual BT::NodeStatus executeTick()
The method that should be used to invoke tick() and setStatus();.
void setStatus(NodeStatus new_status)
setStatus changes the status of the node. it will throw if you try to change the status to IDLE,...
Definition: action_node.h:24
NodeStatus
Definition: basic_types.h:34
Definition: tree_node.h:105