/src/behaviortreecpp/src/tree_node.cpp
Line | Count | Source |
1 | | /* Copyright (C) 2015-2018 Michele Colledanchise - All Rights Reserved |
2 | | * Copyright (C) 2018-2025 Davide Faconti, Eurecat - All Rights Reserved |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), |
5 | | * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, |
6 | | * 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: |
7 | | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
8 | | * |
9 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
10 | | * 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, |
11 | | * 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. |
12 | | */ |
13 | | |
14 | | #include "behaviortree_cpp/tree_node.h" |
15 | | |
16 | | #include <array> |
17 | | #include <atomic> |
18 | | #include <cstring> |
19 | | #include <vector> |
20 | | |
21 | | namespace BT |
22 | | { |
23 | | |
24 | | struct TreeNode::PImpl |
25 | | { |
26 | | PImpl(std::string name, NodeConfig config) |
27 | 5.95k | : name(std::move(name)), config(std::move(config)) |
28 | 5.95k | {} |
29 | | |
30 | | const std::string name; |
31 | | |
32 | | NodeStatus status = NodeStatus::IDLE; |
33 | | |
34 | | std::condition_variable state_condition_variable; |
35 | | |
36 | | mutable std::mutex state_mutex; |
37 | | |
38 | | StatusChangeSignal state_change_signal; |
39 | | |
40 | | NodeConfig config; |
41 | | |
42 | | std::string registration_ID; |
43 | | |
44 | | PreTickCallback pre_tick_callback; |
45 | | PostTickCallback post_tick_callback; |
46 | | TickMonitorCallback tick_monitor_callback; |
47 | | |
48 | | std::mutex callback_injection_mutex; |
49 | | |
50 | | std::shared_ptr<WakeUpSignal> wake_up; |
51 | | |
52 | | std::array<ScriptFunction, size_t(PreCond::COUNT_)> pre_parsed; |
53 | | std::array<ScriptFunction, size_t(PostCond::COUNT_)> post_parsed; |
54 | | }; |
55 | | |
56 | | TreeNode::TreeNode(std::string name, NodeConfig config) |
57 | 5.95k | : _p(new PImpl(std::move(name), std::move(config))) |
58 | 5.95k | {} |
59 | | |
60 | 0 | TreeNode::TreeNode(TreeNode&& other) noexcept : _p(std::move(other._p)) |
61 | 0 | {} |
62 | | |
63 | | TreeNode& TreeNode::operator=(TreeNode&& other) noexcept |
64 | 0 | { |
65 | 0 | this->_p = std::move(other._p); |
66 | 0 | return *this; |
67 | 0 | } |
68 | | |
69 | 5.95k | TreeNode::~TreeNode() = default; |
70 | | |
71 | | NodeStatus TreeNode::executeTick() |
72 | 0 | { |
73 | 0 | auto new_status = _p->status; |
74 | 0 | PreTickCallback pre_tick; |
75 | 0 | PostTickCallback post_tick; |
76 | 0 | TickMonitorCallback monitor_tick; |
77 | 0 | { |
78 | 0 | const std::scoped_lock lk(_p->callback_injection_mutex); |
79 | 0 | pre_tick = _p->pre_tick_callback; |
80 | 0 | post_tick = _p->post_tick_callback; |
81 | 0 | monitor_tick = _p->tick_monitor_callback; |
82 | 0 | } |
83 | | |
84 | | // a pre-condition may return the new status. |
85 | | // In this case it override the actual tick() |
86 | 0 | if(auto precond = checkPreConditions()) |
87 | 0 | { |
88 | 0 | new_status = precond.value(); |
89 | 0 | } |
90 | 0 | else |
91 | 0 | { |
92 | | // injected pre-callback |
93 | 0 | bool substituted = false; |
94 | 0 | if(pre_tick && !isStatusCompleted(_p->status)) |
95 | 0 | { |
96 | 0 | auto override_status = pre_tick(*this); |
97 | 0 | if(isStatusCompleted(override_status)) |
98 | 0 | { |
99 | | // don't execute the actual tick() |
100 | 0 | substituted = true; |
101 | 0 | new_status = override_status; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | // Call the ACTUAL tick |
106 | 0 | if(!substituted) |
107 | 0 | { |
108 | 0 | using namespace std::chrono; |
109 | | // Use atomic_thread_fence to prevent compiler reordering of time measurements. |
110 | | // See issue #861 for details. |
111 | 0 | const auto t1 = steady_clock::now(); |
112 | 0 | std::atomic_thread_fence(std::memory_order_seq_cst); |
113 | 0 | try |
114 | 0 | { |
115 | 0 | new_status = tick(); |
116 | 0 | } |
117 | 0 | catch(const NodeExecutionError&) |
118 | 0 | { |
119 | | // Already wrapped by a child node, re-throw as-is to preserve original info |
120 | 0 | throw; |
121 | 0 | } |
122 | 0 | catch(const std::exception& ex) |
123 | 0 | { |
124 | | // Wrap the exception with this node's context |
125 | 0 | throw NodeExecutionError({ name(), fullPath(), registrationName() }, ex.what()); |
126 | 0 | } |
127 | 0 | std::atomic_thread_fence(std::memory_order_seq_cst); |
128 | 0 | const auto t2 = steady_clock::now(); |
129 | 0 | if(monitor_tick) |
130 | 0 | { |
131 | 0 | monitor_tick(*this, new_status, duration_cast<microseconds>(t2 - t1)); |
132 | 0 | } |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | // injected post callback |
137 | 0 | if(isStatusCompleted(new_status)) |
138 | 0 | { |
139 | 0 | checkPostConditions(new_status); |
140 | 0 | } |
141 | |
|
142 | 0 | if(post_tick) |
143 | 0 | { |
144 | 0 | auto override_status = post_tick(*this, new_status); |
145 | 0 | if(isStatusCompleted(override_status)) |
146 | 0 | { |
147 | 0 | new_status = override_status; |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | // preserve the IDLE state if skipped, but communicate SKIPPED to parent |
152 | 0 | if(new_status != NodeStatus::SKIPPED) |
153 | 0 | { |
154 | 0 | setStatus(new_status); |
155 | 0 | } |
156 | 0 | return new_status; |
157 | 0 | } |
158 | | |
159 | | void TreeNode::haltNode() |
160 | 6.54k | { |
161 | 6.54k | halt(); |
162 | | |
163 | 6.54k | const auto& parse_executor = _p->post_parsed[size_t(PostCond::ON_HALTED)]; |
164 | 6.54k | if(parse_executor) |
165 | 0 | { |
166 | 0 | Ast::Environment env = { config().blackboard, config().enums }; |
167 | 0 | parse_executor(env); |
168 | 0 | } |
169 | 6.54k | } |
170 | | |
171 | | void TreeNode::setStatus(NodeStatus new_status) |
172 | 0 | { |
173 | 0 | if(new_status == NodeStatus::IDLE) |
174 | 0 | { |
175 | 0 | throw RuntimeError("Node [", name(), |
176 | 0 | "]: you are not allowed to set manually the status to IDLE. " |
177 | 0 | "If you know what you are doing (?) use resetStatus() instead."); |
178 | 0 | } |
179 | | |
180 | 0 | NodeStatus prev_status = NodeStatus::IDLE; |
181 | 0 | { |
182 | 0 | const std::unique_lock<std::mutex> UniqueLock(_p->state_mutex); |
183 | 0 | prev_status = _p->status; |
184 | 0 | _p->status = new_status; |
185 | 0 | } |
186 | 0 | if(prev_status != new_status) |
187 | 0 | { |
188 | 0 | _p->state_condition_variable.notify_all(); |
189 | 0 | _p->state_change_signal.notify(std::chrono::high_resolution_clock::now(), *this, |
190 | 0 | prev_status, new_status); |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | TreeNode::PreScripts& TreeNode::preConditionsScripts() |
195 | 5.95k | { |
196 | 5.95k | return _p->pre_parsed; |
197 | 5.95k | } |
198 | | |
199 | | TreeNode::PostScripts& TreeNode::postConditionsScripts() |
200 | 5.91k | { |
201 | 5.91k | return _p->post_parsed; |
202 | 5.91k | } |
203 | | |
204 | | Expected<NodeStatus> TreeNode::checkPreConditions() |
205 | 0 | { |
206 | 0 | Ast::Environment env = { config().blackboard, config().enums }; |
207 | | |
208 | | // Check pre-conditions in order: FAILURE_IF, SUCCESS_IF, SKIP_IF, WHILE_TRUE. |
209 | | // IMPORTANT: _failureIf, _successIf, and _skipIf are evaluated ONLY when the |
210 | | // node is IDLE or SKIPPED. They are NOT re-evaluated while the node is RUNNING. |
211 | 0 | for(size_t index = 0; index < size_t(PreCond::COUNT_); index++) |
212 | 0 | { |
213 | 0 | const auto& parse_executor = _p->pre_parsed[index]; |
214 | 0 | if(!parse_executor) |
215 | 0 | { |
216 | 0 | continue; |
217 | 0 | } |
218 | | |
219 | 0 | const auto preID = static_cast<PreCond>(index); |
220 | | |
221 | | // _failureIf, _successIf, _skipIf: only checked when IDLE or SKIPPED |
222 | | // _while: checked here AND also when RUNNING (see below) |
223 | 0 | if(_p->status == NodeStatus::IDLE || _p->status == NodeStatus::SKIPPED) |
224 | 0 | { |
225 | | // what to do if the condition is true |
226 | 0 | if(parse_executor(env).cast<bool>()) |
227 | 0 | { |
228 | 0 | if(preID == PreCond::FAILURE_IF) |
229 | 0 | { |
230 | 0 | return NodeStatus::FAILURE; |
231 | 0 | } |
232 | 0 | if(preID == PreCond::SUCCESS_IF) |
233 | 0 | { |
234 | 0 | return NodeStatus::SUCCESS; |
235 | 0 | } |
236 | 0 | if(preID == PreCond::SKIP_IF) |
237 | 0 | { |
238 | 0 | return NodeStatus::SKIPPED; |
239 | 0 | } |
240 | 0 | } |
241 | | // if the conditions is false |
242 | 0 | else if(preID == PreCond::WHILE_TRUE) |
243 | 0 | { |
244 | 0 | return NodeStatus::SKIPPED; |
245 | 0 | } |
246 | 0 | } |
247 | 0 | else if(_p->status == NodeStatus::RUNNING && preID == PreCond::WHILE_TRUE) |
248 | 0 | { |
249 | | // _while is the ONLY precondition checked while RUNNING. |
250 | | // If the condition becomes false, halt the node and return SKIPPED. |
251 | 0 | if(!parse_executor(env).cast<bool>()) |
252 | 0 | { |
253 | 0 | haltNode(); |
254 | 0 | return NodeStatus::SKIPPED; |
255 | 0 | } |
256 | 0 | } |
257 | 0 | } |
258 | 0 | return nonstd::make_unexpected(""); // no precondition |
259 | 0 | } |
260 | | |
261 | | void TreeNode::checkPostConditions(NodeStatus status) |
262 | 0 | { |
263 | 0 | auto ExecuteScript = [this](const PostCond& cond) { |
264 | 0 | const auto& parse_executor = _p->post_parsed[size_t(cond)]; |
265 | 0 | if(parse_executor) |
266 | 0 | { |
267 | 0 | Ast::Environment env = { config().blackboard, config().enums }; |
268 | 0 | parse_executor(env); |
269 | 0 | } |
270 | 0 | }; |
271 | |
|
272 | 0 | if(status == NodeStatus::SUCCESS) |
273 | 0 | { |
274 | 0 | ExecuteScript(PostCond::ON_SUCCESS); |
275 | 0 | } |
276 | 0 | else if(status == NodeStatus::FAILURE) |
277 | 0 | { |
278 | 0 | ExecuteScript(PostCond::ON_FAILURE); |
279 | 0 | } |
280 | 0 | ExecuteScript(PostCond::ALWAYS); |
281 | 0 | } |
282 | | |
283 | | void TreeNode::resetStatus() |
284 | 12.9k | { |
285 | 12.9k | NodeStatus prev_status = NodeStatus::IDLE; |
286 | 12.9k | { |
287 | 12.9k | const std::unique_lock<std::mutex> lock(_p->state_mutex); |
288 | 12.9k | prev_status = _p->status; |
289 | 12.9k | _p->status = NodeStatus::IDLE; |
290 | 12.9k | } |
291 | | |
292 | 12.9k | if(prev_status != NodeStatus::IDLE) |
293 | 0 | { |
294 | 0 | _p->state_condition_variable.notify_all(); |
295 | 0 | _p->state_change_signal.notify(std::chrono::high_resolution_clock::now(), *this, |
296 | 0 | prev_status, NodeStatus::IDLE); |
297 | 0 | } |
298 | 12.9k | } |
299 | | |
300 | | NodeStatus TreeNode::status() const |
301 | 4.47k | { |
302 | 4.47k | const std::lock_guard<std::mutex> lock(_p->state_mutex); |
303 | 4.47k | return _p->status; |
304 | 4.47k | } |
305 | | |
306 | | NodeStatus TreeNode::waitValidStatus() |
307 | 0 | { |
308 | 0 | std::unique_lock<std::mutex> lock(_p->state_mutex); |
309 | |
|
310 | 0 | while(isHalted()) |
311 | 0 | { |
312 | 0 | _p->state_condition_variable.wait(lock); |
313 | 0 | } |
314 | 0 | return _p->status; |
315 | 0 | } |
316 | | |
317 | | const std::string& TreeNode::name() const |
318 | 0 | { |
319 | 0 | return _p->name; |
320 | 0 | } |
321 | | |
322 | | bool TreeNode::isHalted() const |
323 | 0 | { |
324 | 0 | return _p->status == NodeStatus::IDLE; |
325 | 0 | } |
326 | | |
327 | | TreeNode::StatusChangeSubscriber |
328 | | TreeNode::subscribeToStatusChange(TreeNode::StatusChangeCallback callback) |
329 | 0 | { |
330 | 0 | return _p->state_change_signal.subscribe(std::move(callback)); |
331 | 0 | } |
332 | | |
333 | | void TreeNode::setPreTickFunction(PreTickCallback callback) |
334 | 0 | { |
335 | 0 | const std::unique_lock lk(_p->callback_injection_mutex); |
336 | 0 | _p->pre_tick_callback = std::move(callback); |
337 | 0 | } |
338 | | |
339 | | void TreeNode::setPostTickFunction(PostTickCallback callback) |
340 | 0 | { |
341 | 0 | const std::unique_lock lk(_p->callback_injection_mutex); |
342 | 0 | _p->post_tick_callback = std::move(callback); |
343 | 0 | } |
344 | | |
345 | | void TreeNode::setTickMonitorCallback(TickMonitorCallback callback) |
346 | 0 | { |
347 | 0 | const std::unique_lock lk(_p->callback_injection_mutex); |
348 | 0 | _p->tick_monitor_callback = std::move(callback); |
349 | 0 | } |
350 | | |
351 | | uint16_t TreeNode::UID() const |
352 | 561 | { |
353 | 561 | return _p->config.uid; |
354 | 561 | } |
355 | | |
356 | | const std::string& TreeNode::fullPath() const |
357 | 2 | { |
358 | 2 | return _p->config.path; |
359 | 2 | } |
360 | | |
361 | | const std::string& TreeNode::registrationName() const |
362 | 0 | { |
363 | 0 | return _p->registration_ID; |
364 | 0 | } |
365 | | |
366 | | const NodeConfig& TreeNode::config() const |
367 | 10 | { |
368 | 10 | return _p->config; |
369 | 10 | } |
370 | | |
371 | | NodeConfig& TreeNode::config() |
372 | 15.4k | { |
373 | 15.4k | return _p->config; |
374 | 15.4k | } |
375 | | |
376 | | StringView TreeNode::getRawPortValue(const std::string& key) const |
377 | 0 | { |
378 | 0 | auto remap_it = _p->config.input_ports.find(key); |
379 | 0 | if(remap_it == _p->config.input_ports.end()) |
380 | 0 | { |
381 | 0 | remap_it = _p->config.output_ports.find(key); |
382 | 0 | if(remap_it == _p->config.output_ports.end()) |
383 | 0 | { |
384 | 0 | throw std::logic_error(StrCat("[", key, "] not found")); |
385 | 0 | } |
386 | 0 | } |
387 | 0 | return remap_it->second; |
388 | 0 | } |
389 | | |
390 | | bool TreeNode::isBlackboardPointer(StringView str, StringView* stripped_pointer) |
391 | 1.55k | { |
392 | 1.55k | if(str.size() < 3) |
393 | 789 | { |
394 | 789 | return false; |
395 | 789 | } |
396 | | // strip leading and following spaces |
397 | 770 | size_t front_index = 0; |
398 | 770 | size_t last_index = str.size() - 1; |
399 | 966 | while(str[front_index] == ' ' && front_index <= last_index) |
400 | 196 | { |
401 | 196 | front_index++; |
402 | 196 | } |
403 | 872 | while(str[last_index] == ' ' && front_index <= last_index) |
404 | 102 | { |
405 | 102 | last_index--; |
406 | 102 | } |
407 | 770 | const auto size = (last_index - front_index) + 1; |
408 | 770 | auto valid = size >= 3 && str[front_index] == '{' && str[last_index] == '}'; |
409 | 770 | if(valid && stripped_pointer != nullptr) |
410 | 150 | { |
411 | 150 | *stripped_pointer = StringView(&str[front_index + 1], size - 2); |
412 | 150 | } |
413 | 770 | return valid; |
414 | 1.55k | } |
415 | | |
416 | | StringView TreeNode::stripBlackboardPointer(StringView str) |
417 | 150 | { |
418 | 150 | StringView out; |
419 | 150 | if(isBlackboardPointer(str, &out)) |
420 | 150 | { |
421 | 150 | return out; |
422 | 150 | } |
423 | 0 | return {}; |
424 | 150 | } |
425 | | |
426 | | Expected<StringView> TreeNode::getRemappedKey(StringView port_name, |
427 | | StringView remapped_port) |
428 | 0 | { |
429 | 0 | if(remapped_port == "{=}" || remapped_port == "=") |
430 | 0 | { |
431 | 0 | return { port_name }; |
432 | 0 | } |
433 | 0 | StringView stripped; |
434 | 0 | if(isBlackboardPointer(remapped_port, &stripped)) |
435 | 0 | { |
436 | 0 | return { stripped }; |
437 | 0 | } |
438 | 0 | return nonstd::make_unexpected("Not a blackboard pointer"); |
439 | 0 | } |
440 | | |
441 | | void TreeNode::emitWakeUpSignal() |
442 | 0 | { |
443 | 0 | if(_p->wake_up) |
444 | 0 | { |
445 | 0 | _p->wake_up->emitSignal(); |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | bool TreeNode::requiresWakeUp() const |
450 | 0 | { |
451 | 0 | return bool(_p->wake_up); |
452 | 0 | } |
453 | | |
454 | | void TreeNode::setRegistrationID(StringView ID) |
455 | 10.4k | { |
456 | 10.4k | _p->registration_ID.assign(ID.data(), ID.size()); |
457 | 10.4k | } |
458 | | |
459 | | void TreeNode::setWakeUpInstance(std::shared_ptr<WakeUpSignal> instance) |
460 | 3.25k | { |
461 | 3.25k | _p->wake_up = instance; |
462 | 3.25k | } |
463 | | |
464 | | void TreeNode::modifyPortsRemapping(const PortsRemapping& new_remapping) |
465 | 0 | { |
466 | 0 | for(const auto& new_it : new_remapping) |
467 | 0 | { |
468 | 0 | auto it = _p->config.input_ports.find(new_it.first); |
469 | 0 | if(it != _p->config.input_ports.end()) |
470 | 0 | { |
471 | 0 | it->second = new_it.second; |
472 | 0 | } |
473 | 0 | it = _p->config.output_ports.find(new_it.first); |
474 | 0 | if(it != _p->config.output_ports.end()) |
475 | 0 | { |
476 | 0 | it->second = new_it.second; |
477 | 0 | } |
478 | 0 | } |
479 | 0 | } |
480 | | |
481 | | template <> |
482 | | std::string toStr<PreCond>(const PreCond& cond) |
483 | 23.8k | { |
484 | 23.8k | if(cond < PreCond::COUNT_) |
485 | 23.8k | { |
486 | 23.8k | return BT::PreCondNames[static_cast<size_t>(cond)]; |
487 | 23.8k | } |
488 | 0 | return "Undefined"; |
489 | 23.8k | } |
490 | | |
491 | | template <> |
492 | | std::string toStr<PostCond>(const PostCond& cond) |
493 | 23.8k | { |
494 | 23.8k | if(cond < BT::PostCond::COUNT_) |
495 | 23.8k | { |
496 | 23.8k | return BT::PostCondNames[static_cast<size_t>(cond)]; |
497 | 23.8k | } |
498 | 0 | return "Undefined"; |
499 | 23.8k | } |
500 | | |
501 | | AnyPtrLocked BT::TreeNode::getLockedPortContent(const std::string& key) |
502 | 0 | { |
503 | 0 | if(auto remapped_key = getRemappedKey(key, getRawPortValue(key))) |
504 | 0 | { |
505 | 0 | const auto bb_key = std::string(*remapped_key); |
506 | 0 | auto result = _p->config.blackboard->getAnyLocked(bb_key); |
507 | 0 | if(!result && _p->config.manifest != nullptr) |
508 | 0 | { |
509 | | // Entry doesn't exist yet. Create it using the port's type info |
510 | | // from the manifest so that getLockedPortContent works even when |
511 | | // the port is not explicitly declared in XML. Issue #942. |
512 | 0 | auto port_it = _p->config.manifest->ports.find(key); |
513 | 0 | if(port_it != _p->config.manifest->ports.end()) |
514 | 0 | { |
515 | 0 | _p->config.blackboard->createEntry(bb_key, port_it->second); |
516 | 0 | result = _p->config.blackboard->getAnyLocked(bb_key); |
517 | 0 | } |
518 | 0 | } |
519 | 0 | return result; |
520 | 0 | } |
521 | 0 | return {}; |
522 | 0 | } |
523 | | |
524 | | } // namespace BT |