Skip to content

Commit ff1bfcd

Browse files
Fix Issue 593 (#594): support skipping in Parallel node
1 parent 00b3c75 commit ff1bfcd

File tree

9 files changed

+289
-61
lines changed

9 files changed

+289
-61
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ list(APPEND BT_SOURCE
112112
src/controls/if_then_else_node.cpp
113113
src/controls/fallback_node.cpp
114114
src/controls/parallel_node.cpp
115+
src/controls/parallel_all_node.cpp
115116
src/controls/reactive_sequence.cpp
116117
src/controls/reactive_fallback.cpp
117118
src/controls/sequence_node.cpp

include/behaviortree_cpp/behavior_tree.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* Copyright (C) 2015-2018 Michele Colledanchise - All Rights Reserved
2-
* Copyright (C) 2018-2022 Davide Faconti - All Rights Reserved
2+
* Copyright (C) 2018-2023 Davide Faconti - All Rights Reserved
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
55
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
@@ -14,6 +14,7 @@
1414
#pragma once
1515

1616
#include "behaviortree_cpp/controls/parallel_node.h"
17+
#include "behaviortree_cpp/controls/parallel_all_node.h"
1718
#include "behaviortree_cpp/controls/reactive_sequence.h"
1819
#include "behaviortree_cpp/controls/reactive_fallback.h"
1920
#include "behaviortree_cpp/controls/fallback_node.h"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* Copyright (C) 2023 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 <set>
16+
#include "behaviortree_cpp/control_node.h"
17+
18+
namespace BT
19+
{
20+
/**
21+
* @brief The ParallelAllNode execute all its children
22+
* __concurrently__, but not in separate threads!
23+
*
24+
* It differs in the way ParallelNode works because the latter may stop
25+
* and halt other children if a certain number of SUCCESS/FAILURES is reached,
26+
* whilst this one will always complete the execution of ALL its children.
27+
*
28+
* Note that threshold indexes work as in Python:
29+
* https://www.i2tutorials.com/what-are-negative-indexes-and-why-are-they-used/
30+
*
31+
* Therefore -1 is equivalent to the number of children.
32+
*/
33+
class ParallelAllNode : public ControlNode
34+
{
35+
public:
36+
37+
ParallelAllNode(const std::string& name, const NodeConfig& config);
38+
39+
static PortsList providedPorts()
40+
{
41+
return {InputPort<int>("max_failures", 1,
42+
"If the number of children returning FAILURE exceeds this value, "
43+
"ParallelAll returns FAILURE")};
44+
}
45+
46+
~ParallelAllNode() override = default;
47+
48+
virtual void halt() override;
49+
50+
size_t failureThreshold() const;
51+
void setFailureThreshold(int threshold);
52+
53+
private:
54+
size_t failure_threshold_;
55+
56+
std::set<size_t> completed_list_;
57+
size_t failure_count_ = 0;
58+
59+
virtual BT::NodeStatus tick() override;
60+
};
61+
62+
} // namespace BT

include/behaviortree_cpp/controls/parallel_node.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,19 @@ class ParallelNode : public ControlNode
6565
private:
6666
int success_threshold_;
6767
int failure_threshold_;
68+
69+
std::set<size_t> completed_list_;
6870

69-
std::set<size_t> skip_list_;
70-
bool all_skipped_ = true;
71+
size_t success_count_ = 0;
72+
size_t failure_count_ = 0;
7173

7274
bool read_parameter_from_ports_;
7375
static constexpr const char* THRESHOLD_SUCCESS = "success_count";
7476
static constexpr const char* THRESHOLD_FAILURE = "failure_count";
7577

7678
virtual BT::NodeStatus tick() override;
79+
80+
void clear();
7781
};
7882

7983
} // namespace BT

src/bt_factory.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ BehaviorTreeFactory::BehaviorTreeFactory():
5656
#endif
5757

5858
registerNodeType<ParallelNode>("Parallel");
59+
registerNodeType<ParallelAllNode>("ParallelAll");
5960
registerNodeType<ReactiveSequence>("ReactiveSequence");
6061
registerNodeType<ReactiveFallback>("ReactiveFallback");
6162
registerNodeType<IfThenElseNode>("IfThenElse");

src/controls/parallel_all_node.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/* Copyright (C) 2023 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+
#include <algorithm>
14+
#include <cstddef>
15+
16+
#include "behaviortree_cpp/controls/parallel_all_node.h"
17+
18+
namespace BT
19+
{
20+
21+
ParallelAllNode::ParallelAllNode(const std::string& name, const NodeConfig& config) :
22+
ControlNode::ControlNode(name, config),
23+
failure_threshold_(1)
24+
{}
25+
26+
NodeStatus ParallelAllNode::tick()
27+
{
28+
int max_failures = 0;
29+
if (!getInput("max_failures", max_failures))
30+
{
31+
throw RuntimeError("Missing parameter [max_failures] in ParallelNode");
32+
}
33+
const size_t children_count = children_nodes_.size();
34+
setFailureThreshold(max_failures);
35+
36+
size_t skipped_count = 0;
37+
38+
if (children_count < failure_threshold_)
39+
{
40+
throw LogicError("Number of children is less than threshold. Can never fail.");
41+
}
42+
43+
setStatus(NodeStatus::RUNNING);
44+
45+
// Routing the tree according to the sequence node's logic:
46+
for (size_t index = 0; index < children_count; index++)
47+
{
48+
TreeNode* child_node = children_nodes_[index];
49+
50+
// already completed
51+
if(completed_list_.count(index) != 0)
52+
{
53+
continue;
54+
}
55+
56+
NodeStatus const child_status = child_node->executeTick();
57+
58+
switch (child_status)
59+
{
60+
case NodeStatus::SUCCESS: {
61+
completed_list_.insert(index);
62+
}
63+
break;
64+
65+
case NodeStatus::FAILURE: {
66+
completed_list_.insert(index);
67+
failure_count_++;
68+
}
69+
break;
70+
71+
case NodeStatus::RUNNING: {
72+
// Still working. Check the next
73+
}
74+
break;
75+
76+
case NodeStatus::SKIPPED: {
77+
skipped_count++;
78+
}
79+
break;
80+
81+
case NodeStatus::IDLE: {
82+
throw LogicError("[", name(), "]: A children should not return IDLE");
83+
}
84+
}
85+
}
86+
87+
if(skipped_count == children_count)
88+
{
89+
return NodeStatus::SKIPPED;
90+
}
91+
if( skipped_count + completed_list_.size() >= children_count)
92+
{
93+
// DONE
94+
haltChildren();
95+
completed_list_.clear();
96+
failure_count_ = 0;
97+
return (failure_count_ >= failure_threshold_) ? NodeStatus::FAILURE :
98+
NodeStatus::SUCCESS;
99+
}
100+
101+
// Some children haven't finished, yet.
102+
return NodeStatus::RUNNING;
103+
}
104+
105+
void ParallelAllNode::halt()
106+
{
107+
completed_list_.clear();
108+
failure_count_ = 0;
109+
ControlNode::halt();
110+
}
111+
112+
113+
size_t ParallelAllNode::failureThreshold() const
114+
{
115+
return failure_threshold_;
116+
}
117+
118+
119+
void ParallelAllNode::setFailureThreshold(int threshold)
120+
{
121+
if (threshold < 0)
122+
{
123+
failure_threshold_ = size_t(std::max(int(children_nodes_.size()) + threshold + 1, 0));
124+
}
125+
else
126+
{
127+
failure_threshold_ = size_t(threshold);
128+
}
129+
}
130+
131+
} // namespace BT

src/controls/parallel_node.cpp

Lines changed: 55 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ NodeStatus ParallelNode::tick()
5252
}
5353
}
5454

55-
size_t success_children_num = 0;
56-
size_t failure_children_num = 0;
57-
5855
const size_t children_count = children_nodes_.size();
5956

6057
if (children_count < successThreshold())
@@ -67,79 +64,81 @@ NodeStatus ParallelNode::tick()
6764
throw LogicError("Number of children is less than threshold. Can never fail.");
6865
}
6966

70-
if(status() == NodeStatus::IDLE)
71-
{
72-
all_skipped_ = true;
73-
}
7467
setStatus(NodeStatus::RUNNING);
7568

69+
size_t skipped_count = 0;
70+
7671
// Routing the tree according to the sequence node's logic:
7772
for (size_t i = 0; i < children_count; i++)
7873
{
79-
TreeNode* child_node = children_nodes_[i];
80-
81-
bool const in_skip_list = (skip_list_.count(i) != 0);
82-
83-
NodeStatus const prev_status = child_node->status();
84-
NodeStatus const child_status =
85-
in_skip_list ? prev_status : child_node->executeTick();
74+
if(completed_list_.count(i) == 0)
75+
{
76+
TreeNode* child_node = children_nodes_[i];
77+
NodeStatus const child_status = child_node->executeTick();
78+
79+
switch (child_status)
80+
{
81+
case NodeStatus::SKIPPED: {
82+
skipped_count++;
83+
} break;
84+
85+
case NodeStatus::SUCCESS: {
86+
completed_list_.insert(i);
87+
success_count_++;
88+
}
89+
break;
8690

87-
// switch to RUNNING state as soon as you find an active child
88-
all_skipped_ &= (child_status == NodeStatus::SKIPPED);
91+
case NodeStatus::FAILURE: {
92+
completed_list_.insert(i);
93+
failure_count_++;
94+
}
95+
break;
8996

90-
switch (child_status)
91-
{
92-
case NodeStatus::SUCCESS: {
93-
skip_list_.insert(i);
94-
success_children_num++;
95-
96-
if (success_children_num == successThreshold())
97-
{
98-
skip_list_.clear();
99-
resetChildren();
100-
return NodeStatus::SUCCESS;
97+
case NodeStatus::RUNNING: {
98+
// Still working. Check the next
10199
}
102-
}
103-
break;
104-
105-
case NodeStatus::FAILURE: {
106-
skip_list_.insert(i);
107-
failure_children_num++;
108-
109-
// It fails if it is not possible to succeed anymore or if
110-
// number of failures are equal to failure_threshold_
111-
if ((failure_children_num > children_count - successThreshold()) ||
112-
(failure_children_num == failureThreshold()))
113-
{
114-
skip_list_.clear();
115-
resetChildren();
116-
return NodeStatus::FAILURE;
100+
break;
101+
102+
case NodeStatus::IDLE: {
103+
throw LogicError("[", name(), "]: A children should not return IDLE");
117104
}
118105
}
119-
break;
106+
}
120107

121-
case NodeStatus::RUNNING: {
122-
// Still working. Check the next
123-
}
124-
break;
108+
const size_t required_success_count = successThreshold();
125109

126-
case NodeStatus::SKIPPED: {
127-
// Node requested to be skipped or halted. Check the next
128-
}
129-
break;
110+
if(success_count_ >= required_success_count ||
111+
(success_threshold_ < 0 && (success_count_ + skipped_count) >= required_success_count))
112+
{
113+
clear();
114+
resetChildren();
115+
return NodeStatus::SUCCESS;
116+
}
130117

131-
case NodeStatus::IDLE: {
132-
throw LogicError("[", name(), "]: A children should not return IDLE");
133-
}
118+
// It fails if it is not possible to succeed anymore or if
119+
// number of failures are equal to failure_threshold_
120+
if (((children_count - failure_count_) < required_success_count) ||
121+
(failure_count_ == failureThreshold()))
122+
{
123+
clear();
124+
resetChildren();
125+
return NodeStatus::FAILURE;
134126
}
135127
}
136128
// Skip if ALL the nodes have been skipped
137-
return all_skipped_ ? NodeStatus::SKIPPED : NodeStatus::RUNNING;
129+
return (skipped_count == children_count) ? NodeStatus::SKIPPED : NodeStatus::RUNNING;
130+
}
131+
132+
void ParallelNode::clear()
133+
{
134+
completed_list_.clear();
135+
success_count_ = 0;
136+
failure_count_ = 0;
138137
}
139138

140139
void ParallelNode::halt()
141140
{
142-
skip_list_.clear();
141+
clear();
143142
ControlNode::halt();
144143
}
145144

0 commit comments

Comments
 (0)