Skip to content

Commit

Permalink
Add RunOnce, based on #472
Browse files Browse the repository at this point in the history
  • Loading branch information
facontidavide committed Mar 30, 2023
1 parent e698c8b commit 3efde1e
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 6 deletions.
1 change: 1 addition & 0 deletions include/behaviortree_cpp/behavior_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "behaviortree_cpp/decorators/inverter_node.h"
#include "behaviortree_cpp/decorators/retry_node.h"
#include "behaviortree_cpp/decorators/repeat_node.h"
#include "behaviortree_cpp/decorators/run_once_node.h"
#include "behaviortree_cpp/decorators/subtree_node.h"

#include "behaviortree_cpp/actions/always_success_node.h"
Expand Down
82 changes: 82 additions & 0 deletions include/behaviortree_cpp/decorators/run_once_node.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* Copyright (C) 2023 Davide Faconti - All Rights Reserved
* Copyright (C) 2022 Gaël Écorchard, Czech Institute of Informatics, Robotics, and Cybernetics (ciirc) <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* 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:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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,
* 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.
*/

#pragma once

#include "behaviortree_cpp/decorator_node.h"


namespace BT {

/**
* @brief The RunOnceNode is used when you want to execute the child
* only once.
* If the child is asynchronous, we will tick until either SUCCESS or FAILURE is
* returned.
*
* After that first execution, you can set value of the port "then_skip" to:
*
* - if TRUE (default), the node will be skipped in the future.
* - if FALSE, return synchronously the same status returned by the child, forever.
*/
class RunOnceNode : public DecoratorNode
{
public:
RunOnceNode(const std::string& name, const NodeConfig& config) :
DecoratorNode(name, config)
{
setRegistrationID("RunOnce");
}

static PortsList providedPorts()
{
return {InputPort<bool>(
"then_skip", true,
"If true, skip after the first execution, "
"otherwise return the same NodeStatus returned once bu the child.")};
}

private:

virtual BT::NodeStatus tick() override;

bool already_ticked_ = false;
NodeStatus returned_status_ = NodeStatus::IDLE;
};

//------------ implementation ----------------------------

inline NodeStatus RunOnceNode::tick()
{
bool skip = true;
if(auto const res = getInput<bool>("then_skip"))
{
skip = res.value();
}

if (already_ticked_)
{
return skip ? NodeStatus::SKIPPED : returned_status_;
}

setStatus(NodeStatus::RUNNING);
auto const status = child_node_->executeTick();

if(isStatusCompleted(status) ) {
already_ticked_ = true;
returned_status_ = status;
}
return status;
}

} // namespace BT
1 change: 1 addition & 0 deletions src/bt_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ BehaviorTreeFactory::BehaviorTreeFactory()
registerNodeType<RepeatNode>("Repeat");
registerNodeType<TimeoutNode<>>("Timeout");
registerNodeType<DelayNode>("Delay");
registerNodeType<RunOnceNode>("RunOnce");

registerNodeType<ForceSuccessNode>("ForceSuccess");
registerNodeType<ForceFailureNode>("ForceFailure");
Expand Down
43 changes: 37 additions & 6 deletions tests/gtest_decorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

#include <gtest/gtest.h>
#include "action_test_node.h"
#include "condition_test_node.h"
#include "behaviortree_cpp/behavior_tree.h"
#include "behaviortree_cpp/bt_factory.h"
#include "test_helper.hpp"

using BT::NodeStatus;
using std::chrono::milliseconds;
Expand All @@ -27,7 +27,7 @@ struct DeadlineTest : testing::Test
{
root.setChild(&action);
}
~DeadlineTest() = default;
~DeadlineTest() override = default;
};

struct RepeatTest : testing::Test
Expand All @@ -39,7 +39,7 @@ struct RepeatTest : testing::Test
{
root.setChild(&action);
}
~RepeatTest() = default;
~RepeatTest() override = default;
};

struct RepeatTestAsync : testing::Test
Expand All @@ -51,7 +51,7 @@ struct RepeatTestAsync : testing::Test
{
root.setChild(&action);
}
~RepeatTestAsync() = default;
~RepeatTestAsync() override = default;
};

struct RetryTest : testing::Test
Expand All @@ -63,7 +63,7 @@ struct RetryTest : testing::Test
{
root.setChild(&action);
}
~RetryTest() = default;
~RetryTest() override = default;
};

struct TimeoutAndRetry : testing::Test
Expand Down Expand Up @@ -189,3 +189,34 @@ TEST_F(TimeoutAndRetry, Issue57)
std::this_thread::sleep_for(std::chrono::microseconds(50));
}
}

TEST(Decorator, RunOnce)
{
BT::BehaviorTreeFactory factory;
std::array<int, 2> counters;
RegisterTestTick(factory, "Test", counters);

const std::string xml_text = R"(
<root BTCPP_format="4" >
<BehaviorTree>
<Sequence>
<RunOnce> <TestA/> </RunOnce>
<TestB/>
</Sequence>
</BehaviorTree>
</root>)";

auto tree = factory.createTreeFromText(xml_text);

for(int i=0; i<5; i++)
{
NodeStatus status = tree.tickWhileRunning();
ASSERT_EQ(status, NodeStatus::SUCCESS);
}
// counters[0] contains the number ot times TestA was ticked
ASSERT_EQ(counters[0], 1);
// counters[1] contains the number ot times TestB was ticked
ASSERT_EQ(counters[1], 5);
}


47 changes: 47 additions & 0 deletions tests/gtest_skipping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,50 @@ TEST(SkippingLogic, SkippingReactiveSequence)
// counters[1] contains the number ot times TestB was ticked
ASSERT_EQ(counters[1], 0);
}



TEST(SkippingLogic, WhileSkip)
{
BehaviorTreeFactory factory;
std::array<int, 2> counters;
RegisterTestTick(factory, "Test", counters);

const std::string xml_text_noskip = R"(
<root BTCPP_format="4" >
<BehaviorTree>
<Sequence>
<Script code=" doit:=true "/>
<Sequence>
<TestA _while="doit"/>
</Sequence>
</Sequence>
</BehaviorTree>
</root>)";

const std::string xml_text_skip = R"(
<root BTCPP_format="4" >
<BehaviorTree>
<Sequence>
<Script code=" doit:=false "/>
<Sequence>
<TestB _while="doit"/>
</Sequence>
</Sequence>
</BehaviorTree>
</root>)";


for(auto const* xml_text: {&xml_text_noskip, &xml_text_skip})
{
auto tree = factory.createTreeFromText(*xml_text);
NodeStatus status = tree.tickWhileRunning();
ASSERT_EQ(status, NodeStatus::SUCCESS);
}
// counters[0] contains the number ot times TestA was ticked
ASSERT_EQ(counters[0], 1);

// counters[1] contains the number ot times TestB was ticked
ASSERT_EQ(counters[1], 0);
}

0 comments on commit 3efde1e

Please sign in to comment.