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 // special case: the port contains a string that was converted to SharedQueue<T>
63 if(static_queue_)
64 {
65 current_queue_ = std::make_shared<std::deque<T>>();
66 *current_queue_ = *static_queue_;
67 }
68 }
69
70 // Pop value from queue, if the child is not RUNNING
71 if(!child_running_)
72 {
73 // if the port is static, any_ref is empty, otherwise it will keep access to
74 // port locked for thread-safety
75 AnyPtrLocked any_ref =
76 static_queue_ ? AnyPtrLocked() : getLockedPortContent("queue");
77 if(any_ref)
78 {
79 // Try SharedQueue<T> first, then fall back to std::vector<T>.
80 // This allows upstream nodes that output vector<T> to be used
81 // directly with LoopNode without manual conversion. Issue #969.
82 auto queue_result = any_ref.get()->tryCast<SharedQueue<T>>();
83 if(queue_result)
84 {
85 current_queue_ = queue_result.value();
86 }
87 else if(!current_queue_)
88 {
89 // Only convert on first read; after that, use the
90 // already-converted current_queue_ (which gets popped).
91 auto vec_result = any_ref.get()->tryCast<std::vector<T>>();
92 if(vec_result)
93 {
94 // Accept std::vector<T> from upstream nodes. Issue #969.
95 const auto& vec = vec_result.value();
96 current_queue_ = std::make_shared<std::deque<T>>(vec.begin(), vec.end());
97 }
98 else if(any_ref.get()->isString())
99 {
100 // Accept string values (e.g. from subtree remapping). Issue #1065.
101 auto str = any_ref.get()->cast<std::string>();
102 current_queue_ = convertFromString<SharedQueue<T>>(str);
103 }
104 else
105 {
106 throw RuntimeError("LoopNode: port 'queue' must contain either "
107 "SharedQueue<T>, std::vector<T>, or a string");
108 }
109 }
110 }
111
112 if(current_queue_ && !current_queue_->empty())
113 {
114 auto value = std::move(current_queue_->front());
115 current_queue_->pop_front();
116 popped = true;
117 setOutput("value", value);
118 }
119 }
120
121 if(!popped && !child_running_)
122 {
123 return getInput<NodeStatus>("if_empty").value();
124 }
125
126 if(status() == NodeStatus::IDLE)
127 {
128 setStatus(NodeStatus::RUNNING);
129 }
130
131 NodeStatus child_state = child_node_->executeTick();
132 child_running_ = (child_state == NodeStatus::RUNNING);
133
134 if(isStatusCompleted(child_state))
135 {
137 }
138
139 if(child_state == NodeStatus::FAILURE)
140 {
141 return NodeStatus::FAILURE;
142 }
143 return NodeStatus::RUNNING;
144 }
145
146 static PortsList providedPorts()
147 {
148 // Use an untyped BidirectionalPort to accept both SharedQueue<T>
149 // and std::vector<T> without triggering a port type mismatch. Issue #969.
150 return { BidirectionalPort("queue"),
151 InputPort<NodeStatus>("if_empty", NodeStatus::SUCCESS,
152 "Status to return if queue is empty: "
153 "SUCCESS, FAILURE, SKIPPED"),
154 OutputPort<T>("value") };
155 }
156};
157
158template <>
159inline SharedQueue<int> convertFromString<SharedQueue<int>>(StringView str)
160{
161 auto parts = splitString(str, ';');
162 SharedQueue<int> output = std::make_shared<std::deque<int>>();
163 for(const StringView& part : parts)
164 {
166 }
167 return output;
168}
169
170template <>
172{
173 auto parts = splitString(str, ';');
174 SharedQueue<bool> output = std::make_shared<std::deque<bool>>();
175 for(const StringView& part : parts)
176 {
178 }
179 return output;
180}
181
182template <>
184{
185 auto parts = splitString(str, ';');
186 SharedQueue<double> output = std::make_shared<std::deque<double>>();
187 for(const StringView& part : parts)
188 {
190 }
191 return output;
192}
193
194template <>
195inline SharedQueue<std::string>
197{
198 auto parts = splitString(str, ';');
200 for(const StringView& part : parts)
201 {
203 }
204 return output;
205}
206
207} // 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