Skip to content

Commit 9f17bc3

Browse files
committed
Merge branch 'parallel_all'
2 parents 00b3c75 + ac68234 commit 9f17bc3

File tree

6 files changed

+268
-5
lines changed

6 files changed

+268
-5
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

src/bt_factory.cpp

Lines changed: 2 additions & 1 deletion
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");
@@ -632,7 +633,7 @@ NodeStatus Tree::tickRoot(TickOption opt, std::chrono::milliseconds sleep_time)
632633
status = rootNode()->executeTick();
633634
}
634635

635-
if (status == NodeStatus::SUCCESS || status == NodeStatus::FAILURE)
636+
if (isStatusCompleted(status))
636637
{
637638
rootNode()->resetStatus();
638639
}

src/controls/parallel_all_node.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
auto const status = (failure_count_ >= failure_threshold_) ?
97+
NodeStatus::FAILURE : NodeStatus::SUCCESS;
98+
failure_count_ = 0;
99+
return status;
100+
}
101+
102+
// Some children haven't finished, yet.
103+
return NodeStatus::RUNNING;
104+
}
105+
106+
void ParallelAllNode::halt()
107+
{
108+
completed_list_.clear();
109+
failure_count_ = 0;
110+
ControlNode::halt();
111+
}
112+
113+
114+
size_t ParallelAllNode::failureThreshold() const
115+
{
116+
return failure_threshold_;
117+
}
118+
119+
120+
void ParallelAllNode::setFailureThreshold(int threshold)
121+
{
122+
if (threshold < 0)
123+
{
124+
failure_threshold_ = size_t(std::max(int(children_nodes_.size()) + threshold + 1, 0));
125+
}
126+
else
127+
{
128+
failure_threshold_ = size_t(threshold);
129+
}
130+
}
131+
132+
} // namespace BT

tests/gtest_parallel.cpp

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,15 +395,15 @@ TEST_F(ComplexParallelTest, ConditionRightFalseAction1Done)
395395
ASSERT_EQ(NodeStatus::SUCCESS, state);
396396
}
397397

398-
TEST(FailingParallel, FailingParallel)
398+
TEST(Parallel, FailingParallel)
399399
{
400400
static const char* xml_text = R"(
401401
<root BTCPP_format="4">
402402
<BehaviorTree ID="MainTree">
403403
<Parallel name="parallel" success_count="1" failure_count="3">
404404
<GoodTest name="first"/>
405405
<BadTest name="second"/>
406-
<GoodTest name="third"/>
406+
<SlowTest name="third"/>
407407
</Parallel>
408408
</BehaviorTree>
409409
</root> )";
@@ -412,7 +412,7 @@ TEST(FailingParallel, FailingParallel)
412412
BehaviorTreeFactory factory;
413413

414414
BT::TestNodeConfig good_config;
415-
good_config.async_delay = std::chrono::milliseconds(300);
415+
good_config.async_delay = std::chrono::milliseconds(200);
416416
good_config.return_status = NodeStatus::SUCCESS;
417417
factory.registerNodeType<BT::TestNode>("GoodTest", good_config);
418418

@@ -421,11 +421,77 @@ TEST(FailingParallel, FailingParallel)
421421
bad_config.return_status = NodeStatus::FAILURE;
422422
factory.registerNodeType<BT::TestNode>("BadTest", bad_config);
423423

424+
BT::TestNodeConfig slow_config;
425+
slow_config.async_delay = std::chrono::milliseconds(300);
426+
slow_config.return_status = NodeStatus::SUCCESS;
427+
factory.registerNodeType<BT::TestNode>("SlowTest", slow_config);
428+
424429
auto tree = factory.createTreeFromText(xml_text);
425430
BT::TreeObserver observer(tree);
426431

427432
auto state = tree.tickWhileRunning();
428433
// since at least one succeeded.
429434
ASSERT_EQ(NodeStatus::SUCCESS, state);
435+
ASSERT_EQ( 1, observer.getStatistics("first").success_count);
430436
ASSERT_EQ( 1, observer.getStatistics("second").failure_count);
437+
ASSERT_EQ( 0, observer.getStatistics("third").failure_count);
438+
}
439+
440+
TEST(Parallel, ParallelAll)
441+
{
442+
using namespace BT;
443+
444+
BehaviorTreeFactory factory;
445+
446+
BT::TestNodeConfig good_config;
447+
good_config.async_delay = std::chrono::milliseconds(300);
448+
good_config.return_status = NodeStatus::SUCCESS;
449+
factory.registerNodeType<BT::TestNode>("GoodTest", good_config);
450+
451+
BT::TestNodeConfig bad_config;
452+
bad_config.async_delay = std::chrono::milliseconds(100);
453+
bad_config.return_status = NodeStatus::FAILURE;
454+
factory.registerNodeType<BT::TestNode>("BadTest", bad_config);
455+
456+
{
457+
const char* xml_text = R"(
458+
<root BTCPP_format="4">
459+
<BehaviorTree ID="MainTree">
460+
<ParallelAll max_failures="1">
461+
<BadTest name="first"/>
462+
<GoodTest name="second"/>
463+
<GoodTest name="third"/>
464+
</ParallelAll>
465+
</BehaviorTree>
466+
</root> )";
467+
auto tree = factory.createTreeFromText(xml_text);
468+
BT::TreeObserver observer(tree);
469+
470+
auto state = tree.tickWhileRunning();
471+
ASSERT_EQ(NodeStatus::FAILURE, state);
472+
ASSERT_EQ( 1, observer.getStatistics("first").failure_count);
473+
ASSERT_EQ( 1, observer.getStatistics("second").success_count);
474+
ASSERT_EQ( 1, observer.getStatistics("third").success_count);
475+
}
476+
477+
{
478+
const char* xml_text = R"(
479+
<root BTCPP_format="4">
480+
<BehaviorTree ID="MainTree">
481+
<ParallelAll max_failures="2">
482+
<BadTest name="first"/>
483+
<GoodTest name="second"/>
484+
<GoodTest name="third"/>
485+
</ParallelAll>
486+
</BehaviorTree>
487+
</root> )";
488+
auto tree = factory.createTreeFromText(xml_text);
489+
BT::TreeObserver observer(tree);
490+
491+
auto state = tree.tickWhileRunning();
492+
ASSERT_EQ(NodeStatus::SUCCESS, state);
493+
ASSERT_EQ( 1, observer.getStatistics("first").failure_count);
494+
ASSERT_EQ( 1, observer.getStatistics("second").success_count);
495+
ASSERT_EQ( 1, observer.getStatistics("third").success_count);
496+
}
431497
}

0 commit comments

Comments
 (0)