Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ec3d37c

Browse files
author
Davide Faconti
committed
Adding deadline decorator
1 parent 3f6a471 commit ec3d37c

File tree

7 files changed

+423
-0
lines changed

7 files changed

+423
-0
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ set(BT_Source
6161
src/decorators/negation_node.cpp
6262
src/decorators/repeat_node.cpp
6363
src/decorators/retry_node.cpp
64+
src/decorators/deadline_node.cpp
6465

6566
src/controls/parallel_node.cpp
6667
src/controls/fallback_node.cpp
@@ -111,6 +112,7 @@ set(BT_Tests
111112
gtest/gtest_parallel.cpp
112113
gtest/gtest_fallback.cpp
113114
gtest/gtest_factory.cpp
115+
gtest/gtest_decorator.cpp
114116
)
115117

116118
if(catkin_FOUND AND CATKIN_ENABLE_TESTING)

gtest/gtest_decorator.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* Copyright (C) 2018 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 <gtest/gtest.h>
14+
#include "action_test_node.h"
15+
#include "condition_test_node.h"
16+
#include "behavior_tree_core/behavior_tree.h"
17+
18+
using BT::NodeStatus;
19+
20+
21+
struct DeadlineTest : testing::Test
22+
{
23+
BT::DeadlineNode root;
24+
BT::AsyncActionTest action;
25+
26+
DeadlineTest()
27+
: root("deadline", 250)
28+
, action("action")
29+
{
30+
root.setChild(&action);
31+
}
32+
~DeadlineTest()
33+
{
34+
haltAllActions(&root);
35+
}
36+
};
37+
38+
39+
/****************TESTS START HERE***************************/
40+
41+
TEST_F(DeadlineTest, DeadlineTriggeredTest)
42+
{
43+
BT::NodeStatus state = root.executeTick();
44+
// deadline in 250 ms
45+
action.setTime(3);
46+
47+
ASSERT_EQ(NodeStatus::RUNNING, action.status());
48+
ASSERT_EQ(NodeStatus::RUNNING, state);
49+
50+
std::this_thread::sleep_for(std::chrono::milliseconds(350));
51+
state = root.executeTick();
52+
ASSERT_EQ(NodeStatus::IDLE, action.status());
53+
ASSERT_EQ(NodeStatus::FAILURE, state);
54+
}
55+
56+
TEST_F(DeadlineTest, DeadlineNotTriggeredTest)
57+
{
58+
BT::NodeStatus state = root.executeTick();
59+
// deadline in 250 ms
60+
action.setTime(2);
61+
62+
ASSERT_EQ(NodeStatus::RUNNING, action.status());
63+
ASSERT_EQ(NodeStatus::RUNNING, state);
64+
65+
std::this_thread::sleep_for(std::chrono::milliseconds(350));
66+
state = root.executeTick();
67+
ASSERT_EQ(NodeStatus::IDLE, action.status());
68+
ASSERT_EQ(NodeStatus::SUCCESS, state);
69+
}
70+

include/behavior_tree_core/behavior_tree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "behavior_tree_core/decorators/always_success_node.h"
3333
#include "behavior_tree_core/decorators/always_failure_node.h"
3434
#include "behavior_tree_core/decorators/blackboard_precondition_node.h"
35+
#include "behavior_tree_core/decorators/deadline_node.h"
3536

3637
namespace BT
3738
{
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#ifndef DEADLINE_NODE_H
2+
#define DEADLINE_NODE_H
3+
4+
#include "behavior_tree_core/decorator_node.h"
5+
#include <atomic>
6+
#include "timer_queue.h"
7+
8+
namespace BT
9+
{
10+
class DeadlineNode : public DecoratorNode
11+
{
12+
public:
13+
14+
DeadlineNode(const std::string& name, unsigned milliseconds);
15+
16+
DeadlineNode(const std::string& name,const NodeParameters& params);
17+
18+
static const NodeParameters& requiredNodeParameters()
19+
{
20+
static NodeParameters params = {{"msec", "0"}};
21+
return params;
22+
}
23+
24+
private:
25+
static TimerQueue& timer()
26+
{
27+
static TimerQueue timer_queue;
28+
return timer_queue;
29+
}
30+
31+
virtual BT::NodeStatus tick() override;
32+
33+
std::atomic<bool> child_halted_;
34+
uint64_t timer_id_;
35+
36+
unsigned msec_;
37+
};
38+
}
39+
40+
#endif // DEADLINE_NODE_H
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#ifndef TIMERQUEUE_H
2+
#define TIMERQUEUE_H
3+
4+
#include <mutex>
5+
#include <condition_variable>
6+
#include <thread>
7+
#include <queue>
8+
#include <chrono>
9+
#include <assert.h>
10+
11+
namespace BT {
12+
13+
// http://www.crazygaze.com/blog/2016/03/24/portable-c-timer-queue/
14+
15+
namespace details {
16+
17+
class Semaphore {
18+
public:
19+
Semaphore(unsigned int count = 0) : m_count(count) {}
20+
21+
void notify() {
22+
std::unique_lock<std::mutex> lock(m_mtx);
23+
m_count++;
24+
m_cv.notify_one();
25+
}
26+
27+
void wait() {
28+
std::unique_lock<std::mutex> lock(m_mtx);
29+
m_cv.wait(lock, [this]() { return m_count > 0; });
30+
m_count--;
31+
}
32+
33+
template <class Clock, class Duration>
34+
bool waitUntil(const std::chrono::time_point<Clock, Duration>& point) {
35+
std::unique_lock<std::mutex> lock(m_mtx);
36+
if (!m_cv.wait_until(lock, point, [this]() { return m_count > 0; }))
37+
return false;
38+
m_count--;
39+
return true;
40+
}
41+
42+
private:
43+
std::mutex m_mtx;
44+
std::condition_variable m_cv;
45+
unsigned int m_count;
46+
};
47+
48+
}
49+
50+
51+
52+
// Timer Queue
53+
//
54+
// Allows execution of handlers at a specified time in the future
55+
// Guarantees:
56+
// - All handlers are executed ONCE, even if canceled (aborted parameter will
57+
//be set to true)
58+
// - If TimerQueue is destroyed, it will cancel all handlers.
59+
// - Handlers are ALWAYS executed in the Timer Queue worker thread.
60+
// - Handlers execution order is NOT guaranteed
61+
//
62+
class TimerQueue {
63+
public:
64+
TimerQueue() {
65+
m_th = std::thread([this] { run(); });
66+
}
67+
68+
~TimerQueue() {
69+
cancelAll();
70+
// Abusing the timer queue to trigger the shutdown.
71+
add( std::chrono::milliseconds(0), [this](bool) { m_finish = true; });
72+
m_th.join();
73+
}
74+
75+
//! Adds a new timer
76+
// \return
77+
// Returns the ID of the new timer. You can use this ID to cancel the
78+
// timer
79+
uint64_t add(std::chrono::milliseconds milliseconds,
80+
std::function<void(bool)> handler)
81+
{
82+
WorkItem item;
83+
item.end = Clock::now() + milliseconds;
84+
item.handler = std::move(handler);
85+
86+
std::unique_lock<std::mutex> lk(m_mtx);
87+
uint64_t id = ++m_idcounter;
88+
item.id = id;
89+
m_items.push(std::move(item));
90+
lk.unlock();
91+
92+
// Something changed, so wake up timer thread
93+
m_checkWork.notify();
94+
return id;
95+
}
96+
97+
//! Cancels the specified timer
98+
// \return
99+
// 1 if the timer was cancelled.
100+
// 0 if you were too late to cancel (or the timer ID was never valid to
101+
// start with)
102+
size_t cancel(uint64_t id) {
103+
// Instead of removing the item from the container (thus breaking the
104+
// heap integrity), we set the item as having no handler, and put
105+
// that handler on a new item at the top for immediate execution
106+
// The timer thread will then ignore the original item, since it has no
107+
// handler.
108+
std::unique_lock<std::mutex> lk(m_mtx);
109+
for (auto&& item : m_items.getContainer()) {
110+
if (item.id == id && item.handler) {
111+
WorkItem newItem;
112+
// Zero time, so it stays at the top for immediate execution
113+
newItem.end = Clock::time_point();
114+
newItem.id = 0; // Means it is a canceled item
115+
// Move the handler from item to newitem.
116+
// Also, we need to manually set the handler to nullptr, since
117+
// the standard does not guarantee moving an std::function will
118+
// empty it. Some STL implementation will empty it, others will
119+
// not.
120+
newItem.handler = std::move(item.handler);
121+
item.handler = nullptr;
122+
m_items.push(std::move(newItem));
123+
124+
lk.unlock();
125+
// Something changed, so wake up timer thread
126+
m_checkWork.notify();
127+
return 1;
128+
}
129+
}
130+
return 0;
131+
}
132+
133+
//! Cancels all timers
134+
// \return
135+
// The number of timers cancelled
136+
size_t cancelAll() {
137+
// Setting all "end" to 0 (for immediate execution) is ok,
138+
// since it maintains the heap integrity
139+
std::unique_lock<std::mutex> lk(m_mtx);
140+
for (auto&& item : m_items.getContainer()) {
141+
if (item.id) {
142+
item.end = Clock::time_point();
143+
item.id = 0;
144+
}
145+
}
146+
auto ret = m_items.size();
147+
148+
lk.unlock();
149+
m_checkWork.notify();
150+
return ret;
151+
}
152+
153+
private:
154+
using Clock = std::chrono::steady_clock;
155+
TimerQueue(const TimerQueue&) = delete;
156+
TimerQueue& operator=(const TimerQueue&) = delete;
157+
158+
void run()
159+
{
160+
while (!m_finish) {
161+
auto end = calcWaitTime();
162+
if (end.first) {
163+
// Timers found, so wait until it expires (or something else
164+
// changes)
165+
m_checkWork.waitUntil(end.second);
166+
} else {
167+
// No timers exist, so wait forever until something changes
168+
m_checkWork.wait();
169+
}
170+
171+
// Check and execute as much work as possible, such as, all expired
172+
// timers
173+
checkWork();
174+
}
175+
176+
// If we are shutting down, we should not have any items left,
177+
// since the shutdown cancels all items
178+
assert(m_items.size() == 0);
179+
}
180+
181+
std::pair<bool, Clock::time_point> calcWaitTime()
182+
{
183+
std::lock_guard<std::mutex> lk(m_mtx);
184+
while (m_items.size())
185+
{
186+
if (m_items.top().handler)
187+
{
188+
// Item present, so return the new wait time
189+
return std::make_pair(true, m_items.top().end);
190+
}
191+
else{
192+
// Discard empty handlers (they were cancelled)
193+
m_items.pop();
194+
}
195+
}
196+
197+
// No items found, so return no wait time (causes the thread to wait
198+
// indefinitely)
199+
return std::make_pair(false, Clock::time_point());
200+
}
201+
202+
void checkWork() {
203+
std::unique_lock<std::mutex> lk(m_mtx);
204+
while (m_items.size() && m_items.top().end <= Clock::now()) {
205+
WorkItem item(std::move(m_items.top()));
206+
m_items.pop();
207+
208+
lk.unlock();
209+
if (item.handler)
210+
item.handler(item.id == 0);
211+
lk.lock();
212+
}
213+
}
214+
215+
details::Semaphore m_checkWork;
216+
std::thread m_th;
217+
bool m_finish = false;
218+
uint64_t m_idcounter = 0;
219+
220+
struct WorkItem {
221+
Clock::time_point end;
222+
uint64_t id; // id==0 means it was cancelled
223+
std::function<void(bool)> handler;
224+
bool operator>(const WorkItem& other) const {
225+
return end > other.end;
226+
}
227+
};
228+
229+
std::mutex m_mtx;
230+
// Inheriting from priority_queue, so we can access the internal container
231+
class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>,
232+
std::greater<WorkItem>> {
233+
public:
234+
std::vector<WorkItem>& getContainer() {
235+
return this->c;
236+
}
237+
} m_items;
238+
};
239+
240+
}
241+
242+
#endif // TIMERQUEUE_H

src/bt_factory.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ BehaviorTreeFactory::BehaviorTreeFactory()
3838
registerNodeType<BlackboardPreconditionNode<int>>("BlackboardCheckInt");
3939
registerNodeType<BlackboardPreconditionNode<double>>("BlackboardCheckDouble");
4040
registerNodeType<BlackboardPreconditionNode<std::string>>("BlackboardCheckString");
41+
42+
registerNodeType<DeadlineNode>("Deadline");
4143
}
4244

4345
bool BehaviorTreeFactory::unregisterBuilder(const std::string& ID)

0 commit comments

Comments
 (0)