Skip to content

Commit 449c07a

Browse files
christophebedard-apexaihliberackifujitatomoya
committed
Add spin_until_complete
Co-authored-by: Hubert Liberacki <hliberacki@gmail.com> Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com> Signed-off-by: Hubert Liberacki <hliberacki@gmail.com> Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> Signed-off-by: Christophe Bedard <christophe.bedard@apex.ai>
1 parent f9c4894 commit 449c07a

File tree

5 files changed

+173
-22
lines changed

5 files changed

+173
-22
lines changed

rclcpp/include/rclcpp/executor.hpp

+49-22
Original file line numberDiff line numberDiff line change
@@ -350,32 +350,22 @@ class Executor
350350
virtual void
351351
spin_once(std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));
352352

353-
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
353+
/// Spin (blocking) until the condition is complete, it times out waiting, or rclcpp is
354+
/// interrupted.
354355
/**
355-
* \param[in] future The future to wait on. If this function returns SUCCESS, the future can be
356-
* accessed without blocking (though it may still throw an exception).
356+
* \param[in] condition The callable condition to wait on.
357357
* \param[in] timeout Optional timeout parameter, which gets passed to Executor::spin_node_once.
358358
* `-1` is block forever, `0` is non-blocking.
359359
* If the time spent inside the blocking loop exceeds this timeout, return a TIMEOUT return
360360
* code.
361361
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
362362
*/
363-
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
363+
template<typename DurationT = std::chrono::milliseconds>
364364
FutureReturnCode
365-
spin_until_future_complete(
366-
const FutureT & future,
367-
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
365+
spin_until_complete(
366+
const std::function<bool(void)> condition,
367+
DurationT timeout = DurationT(-1))
368368
{
369-
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
370-
// inside a callback executed by an executor.
371-
372-
// Check the future before entering the while loop.
373-
// If the future is already complete, don't try to spin.
374-
std::future_status status = future.wait_for(std::chrono::seconds(0));
375-
if (status == std::future_status::ready) {
376-
return FutureReturnCode::SUCCESS;
377-
}
378-
379369
auto end_time = std::chrono::steady_clock::now();
380370
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
381371
timeout);
@@ -384,17 +374,20 @@ class Executor
384374
}
385375
std::chrono::nanoseconds timeout_left = timeout_ns;
386376

377+
// Preliminary check, finish if condition is done already.
378+
if (condition()) {
379+
return FutureReturnCode::SUCCESS;
380+
}
381+
387382
if (spinning.exchange(true)) {
388-
throw std::runtime_error("spin_until_future_complete() called while already spinning");
383+
throw std::runtime_error("spin_until_complete() called while already spinning");
389384
}
390385
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
391386
while (rclcpp::ok(this->context_) && spinning.load()) {
392387
// Do one item of work.
393388
spin_once_impl(timeout_left);
394389

395-
// Check if the future is set, return SUCCESS if it is.
396-
status = future.wait_for(std::chrono::seconds(0));
397-
if (status == std::future_status::ready) {
390+
if (condition()) {
398391
return FutureReturnCode::SUCCESS;
399392
}
400393
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
@@ -410,10 +403,44 @@ class Executor
410403
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
411404
}
412405

413-
// The future did not complete before ok() returned false, return INTERRUPTED.
406+
// The condition did not pass before ok() returned false, return INTERRUPTED.
414407
return FutureReturnCode::INTERRUPTED;
415408
}
416409

410+
/// Spin (blocking) for at least the given amount of duration.
411+
/**
412+
* \param[in] duration gets passed to Executor::spin_node_once,
413+
* spins the executor for given duration.
414+
*/
415+
template<typename DurationT>
416+
void
417+
spin_for(DurationT duration)
418+
{
419+
(void)spin_until_complete([]() {return false;}, duration);
420+
}
421+
422+
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
423+
/**
424+
* \param[in] future The future to wait on. If this function returns SUCCESS, the future can be
425+
* accessed without blocking (though it may still throw an exception).
426+
* \param[in] timeout Optional timeout parameter, which gets passed to Executor::spin_node_once.
427+
* `-1` is block forever, `0` is non-blocking.
428+
* If the time spent inside the blocking loop exceeds this timeout, return a TIMEOUT return
429+
* code.
430+
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
431+
*/
432+
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
433+
FutureReturnCode
434+
spin_until_future_complete(
435+
const FutureT & future,
436+
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
437+
{
438+
const auto condition = [&future]() {
439+
return future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
440+
};
441+
return spin_until_complete(condition, timeout);
442+
}
443+
417444
/// Cancel any running spin* function, causing it to return.
418445
/**
419446
* This function can be called asynchonously from any thread.

rclcpp/include/rclcpp/executors.hpp

+63
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,48 @@ namespace executors
6767
using rclcpp::executors::MultiThreadedExecutor;
6868
using rclcpp::executors::SingleThreadedExecutor;
6969

70+
/// Spin (blocking) until the conditon is complete, it times out waiting, or rclcpp is interrupted.
71+
/**
72+
* \param[in] executor The executor which will spin the node.
73+
* \param[in] node_ptr The node to spin.
74+
* \param[in] condition The callable condition to wait on.
75+
* \param[in] timeout Optional timeout parameter, which gets passed to
76+
* Executor::spin_node_once.
77+
* `-1` is block forever, `0` is non-blocking.
78+
* If the time spent inside the blocking loop exceeds this timeout, return a `TIMEOUT` return code.
79+
* \return The return code, one of `SUCCESS`, `INTERRUPTED`, or `TIMEOUT`.
80+
*/
81+
template<typename DurationT = std::chrono::milliseconds>
82+
rclcpp::FutureReturnCode
83+
spin_node_until_complete(
84+
rclcpp::Executor & executor,
85+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
86+
const std::function<bool(void)> condition,
87+
DurationT timeout = DurationT(-1))
88+
{
89+
// TODO(wjwwood): does not work recursively; can't call spin_node_until_complete
90+
// inside a callback executed by an executor.
91+
executor.add_node(node_ptr);
92+
auto retcode = executor.spin_until_complete(condition, timeout);
93+
executor.remove_node(node_ptr);
94+
return retcode;
95+
}
96+
97+
template<typename NodeT = rclcpp::Node, typename DurationT = std::chrono::milliseconds>
98+
rclcpp::FutureReturnCode
99+
spin_node_until_complete(
100+
rclcpp::Executor & executor,
101+
std::shared_ptr<NodeT> node_ptr,
102+
const std::function<bool(void)> & condition,
103+
DurationT timeout = DurationT(-1))
104+
{
105+
return rclcpp::executors::spin_node_until_complete(
106+
executor,
107+
node_ptr->get_node_base_interface(),
108+
condition,
109+
timeout);
110+
}
111+
70112
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
71113
/**
72114
* \param[in] executor The executor which will spin the node.
@@ -113,6 +155,27 @@ spin_node_until_future_complete(
113155

114156
} // namespace executors
115157

158+
template<typename DurationT = std::chrono::milliseconds>
159+
rclcpp::FutureReturnCode
160+
spin_until_complete(
161+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
162+
const std::function<bool(void)> condition,
163+
DurationT timeout = DurationT(-1))
164+
{
165+
rclcpp::executors::SingleThreadedExecutor executor;
166+
return executors::spin_node_until_complete(executor, node_ptr, condition, timeout);
167+
}
168+
169+
template<typename NodeT = rclcpp::Node, typename DurationT = std::chrono::milliseconds>
170+
rclcpp::FutureReturnCode
171+
spin_until_complete(
172+
std::shared_ptr<NodeT> node_ptr,
173+
const std::function<bool(void)> condition,
174+
DurationT timeout = DurationT(-1))
175+
{
176+
return rclcpp::spin_until_complete(node_ptr->get_node_base_interface(), condition, timeout);
177+
}
178+
116179
template<typename FutureT, typename TimeRepT = int64_t, typename TimeT = std::milli>
117180
rclcpp::FutureReturnCode
118181
spin_until_future_complete(

rclcpp/include/rclcpp/rclcpp.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
* - Executors (responsible for execution of callbacks through a blocking spin):
7070
* - rclcpp::spin()
7171
* - rclcpp::spin_some()
72+
* - rclcpp::spin_until_complete()
7273
* - rclcpp::spin_until_future_complete()
7374
* - rclcpp::executors::SingleThreadedExecutor
7475
* - rclcpp::executors::SingleThreadedExecutor::add_node()

rclcpp/test/rclcpp/executors/test_executors.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,26 @@ TYPED_TEST(TestExecutors, testSpinUntilFutureComplete)
220220
EXPECT_EQ(rclcpp::FutureReturnCode::SUCCESS, ret);
221221
}
222222

223+
// Check executor exits immediately if condition is complete.
224+
TYPED_TEST(TestExecutors, testSpinUntilCompleteCallable)
225+
{
226+
using ExecutorType = TypeParam;
227+
ExecutorType executor;
228+
executor.add_node(this->node);
229+
230+
// test success of an immediately completed condition
231+
auto condition = []() {return true;};
232+
233+
// spin_until_complete is expected to exit immediately, but would block up until its
234+
// timeout if the future is not checked before spin_once_impl.
235+
auto start = std::chrono::steady_clock::now();
236+
auto ret = executor.spin_until_complete(condition, 1s);
237+
executor.remove_node(this->node, true);
238+
// Check it didn't reach timeout
239+
EXPECT_GT(500ms, (std::chrono::steady_clock::now() - start));
240+
EXPECT_EQ(rclcpp::FutureReturnCode::SUCCESS, ret);
241+
}
242+
223243
// Same test, but uses a shared future.
224244
TYPED_TEST(TestExecutors, testSpinUntilSharedFutureComplete)
225245
{

rclcpp/test/rclcpp/test_executor.cpp

+40
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,37 @@ TEST_F(TestExecutor, spin_some_elapsed) {
283283
ASSERT_TRUE(timer_called);
284284
}
285285

286+
TEST_F(TestExecutor, spin_for_duration) {
287+
DummyExecutor dummy;
288+
auto node = std::make_shared<rclcpp::Node>("node", "ns");
289+
bool timer_called = false;
290+
auto timer =
291+
node->create_wall_timer(
292+
std::chrono::milliseconds(0), [&]() {
293+
timer_called = true;
294+
});
295+
dummy.add_node(node);
296+
// Wait for the wall timer to have expired.
297+
dummy.spin_for(std::chrono::milliseconds(0));
298+
299+
ASSERT_TRUE(timer_called);
300+
}
301+
302+
TEST_F(TestExecutor, spin_for_longer_timer) {
303+
DummyExecutor dummy;
304+
auto node = std::make_shared<rclcpp::Node>("node", "ns");
305+
bool timer_called = false;
306+
auto timer =
307+
node->create_wall_timer(
308+
std::chrono::seconds(10), [&]() {
309+
timer_called = true;
310+
});
311+
dummy.add_node(node);
312+
dummy.spin_for(std::chrono::milliseconds(5));
313+
314+
ASSERT_FALSE(timer_called);
315+
}
316+
286317
TEST_F(TestExecutor, spin_once_in_spin_once) {
287318
DummyExecutor dummy;
288319
auto node = std::make_shared<rclcpp::Node>("node", "ns");
@@ -488,6 +519,15 @@ TEST_F(TestExecutor, spin_until_future_complete_future_already_complete) {
488519
dummy.spin_until_future_complete(future, std::chrono::milliseconds(1)));
489520
}
490521

522+
TEST_F(TestExecutor, spin_until_complete_condition_already_complete) {
523+
DummyExecutor dummy;
524+
auto node = std::make_shared<rclcpp::Node>("node", "ns");
525+
auto condition = []() {return true;};
526+
EXPECT_EQ(
527+
rclcpp::FutureReturnCode::SUCCESS,
528+
dummy.spin_until_complete(condition, std::chrono::milliseconds(1)));
529+
}
530+
491531
TEST_F(TestExecutor, is_spinning) {
492532
DummyExecutor dummy;
493533
ASSERT_FALSE(dummy.is_spinning());

0 commit comments

Comments
 (0)