Skip to content

Commit 3efde1e

Browse files
committed
Add RunOnce, based on #472
1 parent e698c8b commit 3efde1e

File tree

5 files changed

+168
-6
lines changed

5 files changed

+168
-6
lines changed

include/behaviortree_cpp/behavior_tree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "behaviortree_cpp/decorators/inverter_node.h"
3030
#include "behaviortree_cpp/decorators/retry_node.h"
3131
#include "behaviortree_cpp/decorators/repeat_node.h"
32+
#include "behaviortree_cpp/decorators/run_once_node.h"
3233
#include "behaviortree_cpp/decorators/subtree_node.h"
3334

3435
#include "behaviortree_cpp/actions/always_success_node.h"
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* Copyright (C) 2023 Davide Faconti - All Rights Reserved
2+
* Copyright (C) 2022 Gaël Écorchard, Czech Institute of Informatics, Robotics, and Cybernetics (ciirc) <[email protected]>
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+
#pragma once
15+
16+
#include "behaviortree_cpp/decorator_node.h"
17+
18+
19+
namespace BT {
20+
21+
/**
22+
* @brief The RunOnceNode is used when you want to execute the child
23+
* only once.
24+
* If the child is asynchronous, we will tick until either SUCCESS or FAILURE is
25+
* returned.
26+
*
27+
* After that first execution, you can set value of the port "then_skip" to:
28+
*
29+
* - if TRUE (default), the node will be skipped in the future.
30+
* - if FALSE, return synchronously the same status returned by the child, forever.
31+
*/
32+
class RunOnceNode : public DecoratorNode
33+
{
34+
public:
35+
RunOnceNode(const std::string& name, const NodeConfig& config) :
36+
DecoratorNode(name, config)
37+
{
38+
setRegistrationID("RunOnce");
39+
}
40+
41+
static PortsList providedPorts()
42+
{
43+
return {InputPort<bool>(
44+
"then_skip", true,
45+
"If true, skip after the first execution, "
46+
"otherwise return the same NodeStatus returned once bu the child.")};
47+
}
48+
49+
private:
50+
51+
virtual BT::NodeStatus tick() override;
52+
53+
bool already_ticked_ = false;
54+
NodeStatus returned_status_ = NodeStatus::IDLE;
55+
};
56+
57+
//------------ implementation ----------------------------
58+
59+
inline NodeStatus RunOnceNode::tick()
60+
{
61+
bool skip = true;
62+
if(auto const res = getInput<bool>("then_skip"))
63+
{
64+
skip = res.value();
65+
}
66+
67+
if (already_ticked_)
68+
{
69+
return skip ? NodeStatus::SKIPPED : returned_status_;
70+
}
71+
72+
setStatus(NodeStatus::RUNNING);
73+
auto const status = child_node_->executeTick();
74+
75+
if(isStatusCompleted(status) ) {
76+
already_ticked_ = true;
77+
returned_status_ = status;
78+
}
79+
return status;
80+
}
81+
82+
} // namespace BT

src/bt_factory.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ BehaviorTreeFactory::BehaviorTreeFactory()
4848
registerNodeType<RepeatNode>("Repeat");
4949
registerNodeType<TimeoutNode<>>("Timeout");
5050
registerNodeType<DelayNode>("Delay");
51+
registerNodeType<RunOnceNode>("RunOnce");
5152

5253
registerNodeType<ForceSuccessNode>("ForceSuccess");
5354
registerNodeType<ForceFailureNode>("ForceFailure");

tests/gtest_decorator.cpp

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
#include <gtest/gtest.h>
1414
#include "action_test_node.h"
15-
#include "condition_test_node.h"
16-
#include "behaviortree_cpp/behavior_tree.h"
15+
#include "behaviortree_cpp/bt_factory.h"
16+
#include "test_helper.hpp"
1717

1818
using BT::NodeStatus;
1919
using std::chrono::milliseconds;
@@ -27,7 +27,7 @@ struct DeadlineTest : testing::Test
2727
{
2828
root.setChild(&action);
2929
}
30-
~DeadlineTest() = default;
30+
~DeadlineTest() override = default;
3131
};
3232

3333
struct RepeatTest : testing::Test
@@ -39,7 +39,7 @@ struct RepeatTest : testing::Test
3939
{
4040
root.setChild(&action);
4141
}
42-
~RepeatTest() = default;
42+
~RepeatTest() override = default;
4343
};
4444

4545
struct RepeatTestAsync : testing::Test
@@ -51,7 +51,7 @@ struct RepeatTestAsync : testing::Test
5151
{
5252
root.setChild(&action);
5353
}
54-
~RepeatTestAsync() = default;
54+
~RepeatTestAsync() override = default;
5555
};
5656

5757
struct RetryTest : testing::Test
@@ -63,7 +63,7 @@ struct RetryTest : testing::Test
6363
{
6464
root.setChild(&action);
6565
}
66-
~RetryTest() = default;
66+
~RetryTest() override = default;
6767
};
6868

6969
struct TimeoutAndRetry : testing::Test
@@ -189,3 +189,34 @@ TEST_F(TimeoutAndRetry, Issue57)
189189
std::this_thread::sleep_for(std::chrono::microseconds(50));
190190
}
191191
}
192+
193+
TEST(Decorator, RunOnce)
194+
{
195+
BT::BehaviorTreeFactory factory;
196+
std::array<int, 2> counters;
197+
RegisterTestTick(factory, "Test", counters);
198+
199+
const std::string xml_text = R"(
200+
<root BTCPP_format="4" >
201+
<BehaviorTree>
202+
<Sequence>
203+
<RunOnce> <TestA/> </RunOnce>
204+
<TestB/>
205+
</Sequence>
206+
</BehaviorTree>
207+
</root>)";
208+
209+
auto tree = factory.createTreeFromText(xml_text);
210+
211+
for(int i=0; i<5; i++)
212+
{
213+
NodeStatus status = tree.tickWhileRunning();
214+
ASSERT_EQ(status, NodeStatus::SUCCESS);
215+
}
216+
// counters[0] contains the number ot times TestA was ticked
217+
ASSERT_EQ(counters[0], 1);
218+
// counters[1] contains the number ot times TestB was ticked
219+
ASSERT_EQ(counters[1], 5);
220+
}
221+
222+

tests/gtest_skipping.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,50 @@ TEST(SkippingLogic, SkippingReactiveSequence)
175175
// counters[1] contains the number ot times TestB was ticked
176176
ASSERT_EQ(counters[1], 0);
177177
}
178+
179+
180+
181+
TEST(SkippingLogic, WhileSkip)
182+
{
183+
BehaviorTreeFactory factory;
184+
std::array<int, 2> counters;
185+
RegisterTestTick(factory, "Test", counters);
186+
187+
const std::string xml_text_noskip = R"(
188+
<root BTCPP_format="4" >
189+
<BehaviorTree>
190+
<Sequence>
191+
<Script code=" doit:=true "/>
192+
<Sequence>
193+
<TestA _while="doit"/>
194+
</Sequence>
195+
</Sequence>
196+
</BehaviorTree>
197+
</root>)";
198+
199+
const std::string xml_text_skip = R"(
200+
<root BTCPP_format="4" >
201+
<BehaviorTree>
202+
<Sequence>
203+
<Script code=" doit:=false "/>
204+
<Sequence>
205+
<TestB _while="doit"/>
206+
</Sequence>
207+
</Sequence>
208+
</BehaviorTree>
209+
</root>)";
210+
211+
212+
for(auto const* xml_text: {&xml_text_noskip, &xml_text_skip})
213+
{
214+
auto tree = factory.createTreeFromText(*xml_text);
215+
NodeStatus status = tree.tickWhileRunning();
216+
ASSERT_EQ(status, NodeStatus::SUCCESS);
217+
}
218+
// counters[0] contains the number ot times TestA was ticked
219+
ASSERT_EQ(counters[0], 1);
220+
221+
// counters[1] contains the number ot times TestB was ticked
222+
ASSERT_EQ(counters[1], 0);
223+
}
224+

0 commit comments

Comments
 (0)