diff --git a/rcl_action/include/rcl_action/goal_handle.h b/rcl_action/include/rcl_action/goal_handle.h index f5032d1d1..ecd903401 100644 --- a/rcl_action/include/rcl_action/goal_handle.h +++ b/rcl_action/include/rcl_action/goal_handle.h @@ -202,15 +202,37 @@ rcl_action_goal_handle_get_status( * Lock-Free | Yes * * \param[in] goal_handle struct containing the goal and metadata - * \return `true` if a goal is in one of the following states: ACCEPTED, EXECUTING, or CANCELING, or - * \return `false` otherwise, also - * \return `false` if the goal handle pointer is invalid + * \return `true` if the goal is in one of the following states: ACCEPTED, EXECUTING, or CANCELING, or + * \return `false` if the goal handle pointer is invalid, or + * \return `false` otherwise */ RCL_ACTION_PUBLIC RCL_WARN_UNUSED bool rcl_action_goal_handle_is_active(const rcl_action_goal_handle_t * goal_handle); +/// Check if a goal can be transitioned to CANCELING in its current state. +/** + * This is a non-blocking call. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | No + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param[in] goal_handle struct containing the goal and metadata + * \return `true` if the goal can be transitioned to CANCELING from its current state, or + * \return `false` if the goal handle pointer is invalid, or + * \return `false` otherwise +*/ +RCL_ACTION_PUBLIC +RCL_WARN_UNUSED +bool +rcl_action_goal_handle_is_cancelable(const rcl_action_goal_handle_t * goal_handle); + /// Check if a rcl_action_goal_handle_t is valid. /** * This is a non-blocking call. @@ -229,8 +251,9 @@ rcl_action_goal_handle_is_active(const rcl_action_goal_handle_t * goal_handle); * Lock-Free | Yes * * \param[in] goal_handle struct to evaluate as valid or not - * \return `true` if the goal handle is valid, `false` otherwise, also - * \return `false` if the goal handle pointer is null + * \return `true` if the goal handle is valid, or + * \return `false` if the goal handle pointer is null, or + * \return `false` otherwise */ RCL_ACTION_PUBLIC RCL_WARN_UNUSED diff --git a/rcl_action/src/rcl_action/goal_handle.c b/rcl_action/src/rcl_action/goal_handle.c index dce06dc36..1048b1def 100644 --- a/rcl_action/src/rcl_action/goal_handle.c +++ b/rcl_action/src/rcl_action/goal_handle.c @@ -137,6 +137,18 @@ rcl_action_goal_handle_is_active(const rcl_action_goal_handle_t * goal_handle) } } +bool +rcl_action_goal_handle_is_cancelable(const rcl_action_goal_handle_t * goal_handle) +{ + if (!rcl_action_goal_handle_is_valid(goal_handle)) { + return false; // error message is set + } + // Check if the state machine reports a cancel event is valid + rcl_action_goal_state_t state = rcl_action_transition_goal_state( + goal_handle->impl->state, GOAL_EVENT_CANCEL); + return GOAL_STATE_CANCELING == state; +} + bool rcl_action_goal_handle_is_valid(const rcl_action_goal_handle_t * goal_handle) { diff --git a/rcl_action/test/rcl_action/test_goal_handle.cpp b/rcl_action/test/rcl_action/test_goal_handle.cpp index 6700eaa54..e5b0b59d9 100644 --- a/rcl_action/test/rcl_action/test_goal_handle.cpp +++ b/rcl_action/test/rcl_action/test_goal_handle.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include "rcl_action/goal_handle.h" @@ -156,8 +156,9 @@ TEST(TestGoalHandle, test_goal_handle_update_state_invalid) rcl_reset_error(); } -using EventStatePair = std::pair; -using StateTransitionSequence = std::vector; +using EventStateActiveCancelableTuple = + std::tuple; +using StateTransitionSequence = std::vector; const std::vector event_strs = { "EXECUTE", "CANCEL", "SET_SUCCEEDED", "SET_ABORTED", "SET_CANCELED"}; @@ -169,8 +170,8 @@ class TestGoalHandleStateTransitionSequence const testing::TestParamInfo & info) { std::stringstream result; - for (const EventStatePair & event_state : info.param) { - result << "_" << event_strs[event_state.first]; + for (const EventStateActiveCancelableTuple & event_state : info.param) { + result << "_" << event_strs[std::get<0>(event_state)]; } return result.str(); } @@ -216,15 +217,22 @@ TEST_P(TestGoalHandleStateTransitionSequence, test_goal_handle_state_transitions // Walk through state transitions rcl_ret_t ret; - for (const EventStatePair & event_state : this->test_sequence) { - ret = rcl_action_update_goal_state(&this->goal_handle, event_state.first); - const rcl_action_goal_state_t & expected_state = event_state.second; - if (GOAL_STATE_UNKNOWN == expected_state) { + for (const EventStateActiveCancelableTuple & event_state : this->test_sequence) { + rcl_action_goal_event_t goal_event; + rcl_action_goal_state_t expected_goal_state; + bool expected_is_active; + bool expected_is_cancelable; + std::tie(goal_event, expected_goal_state, expected_is_active, expected_is_cancelable) = + event_state; + ret = rcl_action_update_goal_state(&this->goal_handle, goal_event); + if (GOAL_STATE_UNKNOWN == expected_goal_state) { EXPECT_EQ(ret, RCL_RET_ACTION_GOAL_EVENT_INVALID); continue; } EXPECT_EQ(ret, RCL_RET_OK); - expect_state_eq(expected_state); + expect_state_eq(expected_goal_state); + EXPECT_EQ(expected_is_active, rcl_action_goal_handle_is_active(&this->goal_handle)); + EXPECT_EQ(expected_is_cancelable, rcl_action_goal_handle_is_cancelable(&this->goal_handle)); } } @@ -232,40 +240,40 @@ TEST_P(TestGoalHandleStateTransitionSequence, test_goal_handle_state_transitions // Note, each sequence starts in the ACCEPTED state const StateTransitionSequence valid_state_transition_sequences[] = { { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_SET_CANCELED, GOAL_STATE_CANCELED}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_SET_CANCELED, GOAL_STATE_CANCELED, false, false), }, { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_SUCCEEDED}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_SUCCEEDED, false, false), }, { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_SET_ABORTED, GOAL_STATE_ABORTED}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_SET_ABORTED, GOAL_STATE_ABORTED, false, false), }, { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_SUCCEEDED}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_SUCCEEDED, false, false), }, { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_SET_ABORTED, GOAL_STATE_ABORTED}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_SET_ABORTED, GOAL_STATE_ABORTED, false, false), }, { - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_SET_CANCELED, GOAL_STATE_CANCELED}, + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_SET_CANCELED, GOAL_STATE_CANCELED, false, false), }, { - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_SET_ABORTED, GOAL_STATE_ABORTED}, + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_SET_ABORTED, GOAL_STATE_ABORTED, false, false), }, // This is an odd case, but valid nonetheless { - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_SUCCEEDED}, + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_SUCCEEDED, false, false), }, }; @@ -277,27 +285,27 @@ INSTANTIATE_TEST_CASE_P( const StateTransitionSequence invalid_state_transition_sequences[] = { { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_EXECUTE, GOAL_STATE_UNKNOWN}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_UNKNOWN, false, false), }, { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING}, - {GOAL_EVENT_CANCEL, GOAL_STATE_UNKNOWN}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_CANCELING, true, false), + std::make_tuple(GOAL_EVENT_CANCEL, GOAL_STATE_UNKNOWN, false, false), }, { - {GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING}, - {GOAL_EVENT_EXECUTE, GOAL_STATE_UNKNOWN}, + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_EXECUTING, true, true), + std::make_tuple(GOAL_EVENT_EXECUTE, GOAL_STATE_UNKNOWN, false, false), }, { - {GOAL_EVENT_SET_CANCELED, GOAL_STATE_UNKNOWN}, + std::make_tuple(GOAL_EVENT_SET_CANCELED, GOAL_STATE_UNKNOWN, false, false), }, { - {GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_UNKNOWN}, + std::make_tuple(GOAL_EVENT_SET_SUCCEEDED, GOAL_STATE_UNKNOWN, false, false), }, { - {GOAL_EVENT_SET_ABORTED, GOAL_STATE_UNKNOWN}, + std::make_tuple(GOAL_EVENT_SET_ABORTED, GOAL_STATE_UNKNOWN, false, false), }, };