Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
892cae9915 | ||
|
|
14c8dd1072 | ||
|
|
011b878554 | ||
|
|
de4c7fcd77 | ||
|
|
fb8e1dda25 | ||
|
|
1dfefe5b63 | ||
|
|
ce86ef7e62 | ||
|
|
7b6ee8a2e7 | ||
|
|
aae375be91 | ||
|
|
1564fc23c6 | ||
|
|
9ff1201ded | ||
|
|
b9b46c5871 | ||
|
|
30e61c955d | ||
|
|
687057ffb6 | ||
|
|
9db7fa2ccb | ||
|
|
387bf7bb51 | ||
|
|
9db7659dab | ||
|
|
48a4761faa | ||
|
|
6c10f941d3 | ||
|
|
9c493c4615 | ||
|
|
34cc960124 | ||
|
|
06d400d795 | ||
|
|
605251bd71 | ||
|
|
f6b80abe4a | ||
|
|
2cc43b3274 | ||
|
|
6069c3df10 | ||
|
|
687adf494b | ||
|
|
e7afaa636a | ||
|
|
2d1b770e85 | ||
|
|
80768ed14e | ||
|
|
9cabd69412 | ||
|
|
a0a2a067d8 | ||
|
|
094618a044 | ||
|
|
f5e08c2d0c | ||
|
|
016cfeac99 | ||
|
|
a13e16e2cb | ||
|
|
aad8cb53ad | ||
|
|
8c0161a07f | ||
|
|
a7f05a904a | ||
|
|
d7245365ed | ||
|
|
3310f9eaed | ||
|
|
785a70d604 | ||
|
|
9984197c29 | ||
|
|
e9b1004247 | ||
|
|
e64627004f |
45
.github/ISSUE_TEMPLATE.md
vendored
45
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,45 +0,0 @@
|
||||
<!--
|
||||
For general questions, please ask on ROS answers: https://answers.ros.org, make sure to include at least the `ros2` tag and the rosdistro version you are running, e.g. `ardent`.
|
||||
For general design discussions, please post on discourse: https://discourse.ros.org/c/ng-ros
|
||||
Not sure if this is the right repository? Open an issue on https://github.com/ros2/ros2/issues
|
||||
For Bug report or feature requests, please fill out the relevant category below
|
||||
-->
|
||||
|
||||
## Bug report
|
||||
|
||||
**Required Info:**
|
||||
|
||||
- Operating System:
|
||||
- <!-- OS and version (e.g. Windows 10, Ubuntu 16.04...) -->
|
||||
- Installation type:
|
||||
- <!-- binaries or from source -->
|
||||
- Version or commit hash:
|
||||
- <!-- Output of git rev-parse HEAD, release version, or repos file -->
|
||||
- DDS implementation:
|
||||
- <!-- rmw_implementation used (e.g. Fast-RTPS, RTI Connext, etc -->
|
||||
- Client library (if applicable):
|
||||
- <!-- e.g. rclcpp, rclpy, or N/A -->
|
||||
|
||||
#### Steps to reproduce issue
|
||||
<!-- Detailed instructions on how to reliably reproduce this issue http://sscce.org/
|
||||
``` code that can be copy-pasted is preferred ``` -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
#### Expected behavior
|
||||
|
||||
#### Actual behavior
|
||||
|
||||
#### Additional information
|
||||
|
||||
<!-- If you are reporting a bug delete everything below
|
||||
If you are requesting a feature deleted everything above this line -->
|
||||
----
|
||||
## Feature request
|
||||
|
||||
#### Feature description
|
||||
<!-- Description in a few sentences what the feature consists of and what problem it will solve -->
|
||||
|
||||
#### Implementation considerations
|
||||
<!-- Relevant information on how the feature could be implemented and pros and cons of the different solutions -->
|
||||
13
.github/workflows/mirror-rolling-to-master.yaml
vendored
13
.github/workflows/mirror-rolling-to-master.yaml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Mirror rolling to master
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ rolling ]
|
||||
|
||||
jobs:
|
||||
mirror-to-master:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: zofrex/mirror-branch@v1
|
||||
with:
|
||||
target-branch: master
|
||||
@@ -1,2 +0,0 @@
|
||||
# This file was generated by https://github.com/audrow/update-ros2-repos
|
||||
* @ivanpauno @hidmic @wjwwood
|
||||
@@ -2,6 +2,44 @@
|
||||
Changelog for package rclcpp
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
29.4.0 (2025-04-04)
|
||||
-------------------
|
||||
* Removed trailing whitespace from the codebase. (`#2791 <https://github.com/ros2/rclcpp/issues/2791>`_)
|
||||
* Expanded docstring of `get_rmw_qos_profile()` (`#2787 <https://github.com/ros2/rclcpp/issues/2787>`_)
|
||||
* Set envars to run tests with rmw_zenoh_cpp with multicast discovery (`#2776 <https://github.com/ros2/rclcpp/issues/2776>`_)
|
||||
* fix: Compilefix for clang (`#2775 <https://github.com/ros2/rclcpp/issues/2775>`_)
|
||||
* add exception doc for configure_introspection. (`#2773 <https://github.com/ros2/rclcpp/issues/2773>`_)
|
||||
* feat: Add ClockWaiter and ClockConditionalVariable (`#2691 <https://github.com/ros2/rclcpp/issues/2691>`_)
|
||||
* doc: Added warning to not instantiate Clock directly with RCL_ROS_TIME (`#2768 <https://github.com/ros2/rclcpp/issues/2768>`_)
|
||||
* Use rmw_event_type_is_supported in test_qos_event (`#2761 <https://github.com/ros2/rclcpp/issues/2761>`_)
|
||||
* Support action typesupport helper (`#2750 <https://github.com/ros2/rclcpp/issues/2750>`_)
|
||||
* use maybe_unused attribute for the portability. (`#2758 <https://github.com/ros2/rclcpp/issues/2758>`_)
|
||||
* Executor strong reference fix (`#2745 <https://github.com/ros2/rclcpp/issues/2745>`_)
|
||||
* Cleanup of https://github.com/ros2/rclcpp/pull/2683 (`#2714 <https://github.com/ros2/rclcpp/issues/2714>`_)
|
||||
* Fix typo in doc section for get_service_typesupport_handle (`#2751 <https://github.com/ros2/rclcpp/issues/2751>`_)
|
||||
* Test case and fix for for https://github.com/ros2/rclcpp/issues/2652 (`#2713 <https://github.com/ros2/rclcpp/issues/2713>`_)
|
||||
* fix(timer): Delete node, after executor thread terminated (`#2737 <https://github.com/ros2/rclcpp/issues/2737>`_)
|
||||
* update doc section for spin_xxx methods. (`#2730 <https://github.com/ros2/rclcpp/issues/2730>`_)
|
||||
* fix: Expose timers used by rclcpp::Waitables (`#2699 <https://github.com/ros2/rclcpp/issues/2699>`_)
|
||||
* use rmw_qos_profile_rosout_default instead of rcl. (`#2663 <https://github.com/ros2/rclcpp/issues/2663>`_)
|
||||
* fix(Executor): Fixed entities not beeing executed after just beeing added (`#2724 <https://github.com/ros2/rclcpp/issues/2724>`_)
|
||||
* fix: make the loop condition align with the description (`#2726 <https://github.com/ros2/rclcpp/issues/2726>`_)
|
||||
* Collect log messages from rcl, and reset. (`#2720 <https://github.com/ros2/rclcpp/issues/2720>`_)
|
||||
* Contributors: Abhishek Kashyap, Alejandro Hernández Cordero, Barry Xu, Janosch Machowinski, Leander Stephen D'Souza, Tomoya Fujita, Yuyuan Yuan
|
||||
|
||||
29.3.0 (2024-12-20)
|
||||
-------------------
|
||||
* Fix transient local IPC publish (`#2708 <https://github.com/ros2/rclcpp/issues/2708>`_)
|
||||
* apply actual QoS from rmw to the IPC publisher. (`#2707 <https://github.com/ros2/rclcpp/issues/2707>`_)
|
||||
* Adding in topic name to logging on IPC issues (`#2706 <https://github.com/ros2/rclcpp/issues/2706>`_)
|
||||
* fix TestTimeSource.ROS_time_valid_attach_detach. (`#2700 <https://github.com/ros2/rclcpp/issues/2700>`_)
|
||||
* Update docstring for `rclcpp::Node::now()` (`#2696 <https://github.com/ros2/rclcpp/issues/2696>`_)
|
||||
* Re-enable executor test on rmw_connextdds. (`#2693 <https://github.com/ros2/rclcpp/issues/2693>`_)
|
||||
* Fix warnings on Windows. (`#2692 <https://github.com/ros2/rclcpp/issues/2692>`_)
|
||||
* Omnibus fixes for running tests with Connext. (`#2684 <https://github.com/ros2/rclcpp/issues/2684>`_)
|
||||
* fix(Executor): Fix segfault if callback group is deleted during rmw_wait (`#2683 <https://github.com/ros2/rclcpp/issues/2683>`_)
|
||||
* Contributors: Chris Lalancette, Jeffery Hsu, Patrick Roncagliolo, Steve Macenski, Tomoya Fujita, jmachowinski
|
||||
|
||||
29.2.0 (2024-11-25)
|
||||
-------------------
|
||||
* accept custom allocator for LoanedMessage. (`#2672 <https://github.com/ros2/rclcpp/issues/2672>`_)
|
||||
|
||||
@@ -434,4 +434,3 @@
|
||||
|
||||
- (tfoote) There should be no jumps in steady time. If there's a big change in system time, it doesn't necessarily mean that time jumped, just that you might have been sleeping for a long time. Most ntp systems adjust the slew rate these days instead of jumping but still that's an external process and I don't know of any APIs to introspect the state of the clock. I'm not sure that we have a way to detect jumps in time for system or steady time. To that end I think that we should be clear that we only provide callbacks when simulation time starts or stops, or simulation time jumps. We should also strongly recommend that operators not actively adjust their system clocks while running ROS nodes.
|
||||
- (jacobperron) I agree with Tully, if we don't have a way to detect system time jumps then I think we should just document that this only works with ROS time. In addition to documentation, we could log an info or warning message if the user registers jump callback with steady or system time, but it may be unnecessarily noisy.
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
## Introduction:
|
||||
|
||||
The original requirement came in **gazebo_ros_pkgs** for setting individual wheel slip parameters based on global wheel slip value [link to original issue](https://github.com/ros-simulation/gazebo_ros_pkgs/pull/1365).
|
||||
The original requirement came in **gazebo_ros_pkgs** for setting individual wheel slip parameters based on global wheel slip value [link to original issue](https://github.com/ros-simulation/gazebo_ros_pkgs/pull/1365).
|
||||
|
||||
The main requirement is to set one or more parameters after another parameter is set successfully.
|
||||
The main requirement is to set one or more parameters after another parameter is set successfully.
|
||||
|
||||
Additionally, it would be nice if users could be notified locally (via a callback) when parameters have been set successfully (i.e. post validation).
|
||||
|
||||
Related discussion can be found in [#609](https://github.com/ros2/rclcpp/issues/609) [#1789](https://github.com/ros2/rclcpp/pull/1789)
|
||||
|
||||
With the current parameters API, the `add_on_set_parameters_callback` is intended for validation of parameter values before they are set, it should **not** cause any side-effects.
|
||||
With the current parameters API, the `add_on_set_parameters_callback` is intended for validation of parameter values before they are set, it should **not** cause any side-effects.
|
||||
|
||||
There is also the `ParameterEventHandler` that publishes changes to node parameters on `/parameter_events` topic for external nodes to see. Though the node could subscribe to the `/parameter_events` topic to be notified of changes to its own parameters, it is less than ideal since there is a delay caused by waiting for an executor to process the callback.
|
||||
|
||||
@@ -19,11 +19,11 @@ We propose adding a `PostSetParametersCallbackHandle` for successful parameter s
|
||||
The validation callback is often abused to trigger side effects in the code, for instance updating class attributes even before a parameter has been set successfully. Instead of relying on the `/parameter_events` topic to be notified of parameter changes, users can register a callback with a new API, `add_post_set_parameters_callback`.
|
||||
|
||||
It is possible to use the proposed `add_post_set_parameters_callback` for setting additional parameters, but this might result in infinite recursion and does not allow those additional parameters to be set atomically with the original parameter(s) changed.
|
||||
To workaround these issues, we propose adding a "pre set" callback type that can be registered with `add_pre_set_parameters_callback`, which will be triggered before the validation callbacks and can be used to modify the parameter list.
|
||||
To workaround these issues, we propose adding a "pre set" callback type that can be registered with `add_pre_set_parameters_callback`, which will be triggered before the validation callbacks and can be used to modify the parameter list.
|
||||
|
||||

|
||||
|
||||
## Alternatives
|
||||
|
||||
* Users could call `set_parameter` while processing a message from the `/parameter_events` topic, however, there is extra overhead in having to create subscription (as noted earlier).
|
||||
* Users could call `set_parameter` inside the "on set" parameters callback, however it is not well-defined how side-effects should handle cases where parameter validation fails.
|
||||
* Users could call `set_parameter` inside the "on set" parameters callback, however it is not well-defined how side-effects should handle cases where parameter validation fails.
|
||||
|
||||
@@ -69,18 +69,17 @@ private:
|
||||
};
|
||||
|
||||
template<typename Alloc, typename T, typename D>
|
||||
void set_allocator_for_deleter(D * deleter, Alloc * alloc)
|
||||
void set_allocator_for_deleter([[maybe_unused]] D * deleter, [[maybe_unused]] Alloc * alloc)
|
||||
{
|
||||
(void) alloc;
|
||||
(void) deleter;
|
||||
throw std::runtime_error("Reached unexpected template specialization");
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
void set_allocator_for_deleter(std::default_delete<T> * deleter, std::allocator<U> * alloc)
|
||||
void set_allocator_for_deleter(
|
||||
[[maybe_unused]] std::default_delete<T> * deleter,
|
||||
[[maybe_unused]] std::allocator<U> * alloc)
|
||||
{
|
||||
(void) deleter;
|
||||
(void) alloc;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
template<typename Alloc, typename T>
|
||||
|
||||
@@ -789,6 +789,9 @@ public:
|
||||
* \param[in] clock clock to use to generate introspection timestamps
|
||||
* \param[in] qos_service_event_pub QoS settings to use when creating the introspection publisher
|
||||
* \param[in] introspection_state the state to set introspection to
|
||||
*
|
||||
* \throws anything rclcpp::exceptions::throw_from_rcl_error can throw if
|
||||
* it failed to configure introspection.
|
||||
*/
|
||||
void
|
||||
configure_introspection(
|
||||
|
||||
@@ -60,6 +60,13 @@ public:
|
||||
/**
|
||||
* Initializes the clock instance with the given clock_type.
|
||||
*
|
||||
* WARNING Don't instantiate a clock using RCL_ROS_TIME directly,
|
||||
* unless you really know what you are doing. By default no TimeSource
|
||||
* is attached to a new clock. This will lead to the unexpected behavior,
|
||||
* that your RCL_ROS_TIME will run always on system time. If you want
|
||||
* a RCL_ROS_TIME use Node::get_clock(), or make sure to attach a
|
||||
* TimeSource yourself.
|
||||
*
|
||||
* \param clock_type type of the clock.
|
||||
* \throws anything rclcpp::exceptions::throw_from_rcl_error can throw.
|
||||
*/
|
||||
@@ -194,11 +201,16 @@ public:
|
||||
ros_time_is_active();
|
||||
|
||||
/**
|
||||
* Deprecated. This API is broken, as there is no way to get a deep
|
||||
* copy of a clock. Therefore one can experience spurious wakeups triggered
|
||||
* by some other instance of a clock.
|
||||
*
|
||||
* Cancels an ongoing or future sleep operation of one thread.
|
||||
*
|
||||
* This function can be used by one thread, to wakeup another thread that is
|
||||
* blocked using any of the sleep_ or wait_ methods of this class.
|
||||
*/
|
||||
[[deprecated("Use ClockConditionalVariable")]]
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
cancel_sleep_or_wait();
|
||||
@@ -260,6 +272,117 @@ private:
|
||||
std::shared_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A synchronization primitive, equal to std::conditional_variable,
|
||||
* that works with the rclcpp::Clock.
|
||||
*
|
||||
* For more information on the API see https://en.cppreference.com/w/cpp/thread/condition_variable.
|
||||
*
|
||||
* Note, this class does not handle shutdowns, if you want to
|
||||
* haven them handles as well, use ClockConditionalVariable.
|
||||
*/
|
||||
class ClockWaiter
|
||||
{
|
||||
private:
|
||||
class ClockWaiterImpl;
|
||||
std::unique_ptr<ClockWaiterImpl> impl_;
|
||||
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS(ClockWaiter)
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
explicit ClockWaiter(const rclcpp::Clock::SharedPtr & clock);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
~ClockWaiter();
|
||||
|
||||
/**
|
||||
* Calling this function will block the current thread, until abs_time is reached,
|
||||
* or pred returns true.
|
||||
* @param lock A locked lock. The lock must be locked at call time, or this method will throw.
|
||||
* The lock will be atomically released and this thread will blocked.
|
||||
* @param abs_time The time until which this thread shall be blocked.
|
||||
* @param pred may be called in cased of spurious wakeups, but must be called every time
|
||||
* notify_one() was called. During the call to pred, the given lock will be locked.
|
||||
* This method will return, if pred returns true.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
wait_until(
|
||||
std::unique_lock<std::mutex> & lock,
|
||||
const rclcpp::Time & abs_time, const std::function<bool ()> & pred);
|
||||
|
||||
/**
|
||||
* Notify the blocked thread, that it should reevaluate the wakeup condition.
|
||||
* The given pred function in wait_until will be reevaluated and wait_until
|
||||
* will return if it evaluates to true.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
notify_one();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A synchronization primitive, similar to std::conditional_variable,
|
||||
* that works with the rclcpp::Clock.
|
||||
*
|
||||
* For more information on the API see https://en.cppreference.com/w/cpp/thread/condition_variable.
|
||||
*
|
||||
* This primitive will wake up if the context was shut down.
|
||||
*/
|
||||
class ClockConditionalVariable
|
||||
{
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS(ClockConditionalVariable)
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ClockConditionalVariable(
|
||||
const rclcpp::Clock::SharedPtr & clock,
|
||||
rclcpp::Context::SharedPtr context = rclcpp::contexts::get_global_default_context());
|
||||
RCLCPP_PUBLIC
|
||||
~ClockConditionalVariable();
|
||||
|
||||
/**
|
||||
* Calling this function will block the current thread, until abs_time is reached,
|
||||
* or pred returns true.
|
||||
* @param lock A locked lock. The lock must be locked at call time, or this method will throw.
|
||||
* The lock will be atomically released and this thread will blocked.
|
||||
* The given lock must be created using the mutex returned my mutex().
|
||||
* @param abs_time The time until which this thread shall be blocked.
|
||||
* @param pred may be called in cased of spurious wakeups, but must be called every time
|
||||
* notify_one() was called. During the call to pred, the given lock will be locked.
|
||||
* This method will return, if pred returns true.
|
||||
*
|
||||
* @return true if until was reached.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
wait_until(
|
||||
std::unique_lock<std::mutex> & lock, rclcpp::Time until,
|
||||
const std::function<bool ()> & pred);
|
||||
|
||||
/**
|
||||
* Notify the blocked thread, that is should reevaluate the wakeup condition.
|
||||
* E.g. the given pred function in wait_until shall be reevaluated.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
notify_one();
|
||||
|
||||
/**
|
||||
* Returns the internal mutex. In order to be race free with the context shutdown,
|
||||
* this mutex must be used for the wait_until call.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::mutex &
|
||||
mutex();
|
||||
};
|
||||
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__CLOCK_HPP_
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/event_callback.h"
|
||||
@@ -221,6 +222,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
@@ -278,15 +286,15 @@ public:
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Couldn't take event info: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
return nullptr;
|
||||
}
|
||||
return std::static_pointer_cast<void>(std::make_shared<EventCallbackInfoT>(callback_info));
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override
|
||||
take_data_by_entity_id([[maybe_unused]] size_t id) override
|
||||
{
|
||||
(void)id;
|
||||
return take_data();
|
||||
}
|
||||
|
||||
|
||||
@@ -287,6 +287,18 @@ public:
|
||||
* Adding subscriptions, timers, services, etc. with blocking or long running
|
||||
* callbacks may cause the function exceed the max_duration significantly.
|
||||
*
|
||||
* Work that is ready to be done is collected only once, and when collecting that work
|
||||
* entities which may have multiple pieces of work ready will only be executed at most
|
||||
* one time.
|
||||
* The reason for this is that it is not possible to tell if, for example, a ready
|
||||
* subscription has only one message ready or multiple without checking again.
|
||||
* Because, in order to find out if there are multiple messages, one message must
|
||||
* be taken and executed before checking again if that subscription is still ready.
|
||||
* However, this function only checks for ready entities to work on once,
|
||||
* and so it will never execute a single entity more than once per call to this function.
|
||||
* See spin_all() variants for a function that will repeatedly work on a single entity
|
||||
* in a single call.
|
||||
*
|
||||
* If there is no work to be done when this called, it will return immediately
|
||||
* because the collecting of available work is non-blocking.
|
||||
* Before each piece of ready work is executed this function checks if the
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/guard_condition.hpp"
|
||||
#include "rclcpp/waitable.hpp"
|
||||
@@ -41,7 +42,9 @@ public:
|
||||
* of this waitable has signaled the wait_set.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
explicit ExecutorNotifyWaitable(std::function<void(void)> on_execute_callback = {});
|
||||
explicit ExecutorNotifyWaitable(
|
||||
std::function<void(void)> on_execute_callback = {}, const rclcpp::Context::SharedPtr & context =
|
||||
rclcpp::contexts::get_global_default_context());
|
||||
|
||||
// Destructor
|
||||
RCLCPP_PUBLIC
|
||||
@@ -146,6 +149,14 @@ public:
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override;
|
||||
|
||||
/// Returns the number of used Timers
|
||||
/**
|
||||
* Will always return an empty vector.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
get_timers() const override;
|
||||
|
||||
private:
|
||||
/// Callback to run when waitable executes
|
||||
std::function<void(void)> execute_callback_;
|
||||
@@ -158,8 +169,17 @@ private:
|
||||
std::function<void(size_t)> on_ready_callback_;
|
||||
|
||||
/// The collection of guard conditions to be waited on.
|
||||
std::set<rclcpp::GuardCondition::WeakPtr,
|
||||
std::owner_less<rclcpp::GuardCondition::WeakPtr>> notify_guard_conditions_;
|
||||
std::set<rclcpp::GuardCondition::SharedPtr> notify_guard_conditions_;
|
||||
|
||||
/// The indixes were our guard conditions were stored in the
|
||||
/// rcl waitset
|
||||
std::vector<size_t> idxs_of_added_guard_condition_;
|
||||
|
||||
/// set to true, if we got a pending trigger
|
||||
bool needs_processing = false;
|
||||
|
||||
/// A guard condition needed to generate wakeups
|
||||
rclcpp::GuardCondition::SharedPtr guard_condition_;
|
||||
};
|
||||
|
||||
} // namespace executors
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rcl/wait.h"
|
||||
#include "rmw/impl/cpp/demangle.hpp"
|
||||
@@ -78,9 +79,8 @@ public:
|
||||
take_data() override = 0;
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override
|
||||
take_data_by_entity_id([[maybe_unused]] size_t id) override
|
||||
{
|
||||
(void)id;
|
||||
return take_data();
|
||||
}
|
||||
|
||||
@@ -180,6 +180,13 @@ public:
|
||||
on_new_message_callback_ = nullptr;
|
||||
}
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
std::recursive_mutex callback_mutex_;
|
||||
std::function<void(size_t)> on_new_message_callback_ {nullptr};
|
||||
|
||||
@@ -110,9 +110,8 @@ public:
|
||||
}
|
||||
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & wait_set) override
|
||||
is_ready([[maybe_unused]] const rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
(void) wait_set;
|
||||
return buffer_->has_data();
|
||||
}
|
||||
|
||||
|
||||
@@ -1430,7 +1430,7 @@ public:
|
||||
rclcpp::Clock::ConstSharedPtr
|
||||
get_clock() const;
|
||||
|
||||
/// Returns current time from the time source specified by clock_type.
|
||||
/// Returns current time from the node clock.
|
||||
/**
|
||||
* \sa rclcpp::Clock::now
|
||||
*/
|
||||
|
||||
@@ -143,34 +143,34 @@ public:
|
||||
post_init_setup(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
const std::string & topic,
|
||||
const rclcpp::QoS & qos,
|
||||
const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
|
||||
[[maybe_unused]] const rclcpp::QoS & qos,
|
||||
[[maybe_unused]] const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
|
||||
{
|
||||
// Topic is unused for now.
|
||||
(void)topic;
|
||||
(void)options;
|
||||
|
||||
// If needed, setup intra process communication.
|
||||
if (rclcpp::detail::resolve_use_intra_process(options_, *node_base)) {
|
||||
auto context = node_base->get_context();
|
||||
// Get the intra process manager instance for this context.
|
||||
auto ipm = context->get_sub_context<rclcpp::experimental::IntraProcessManager>();
|
||||
// Register the publisher with the intra process manager.
|
||||
if (qos.history() != rclcpp::HistoryPolicy::KeepLast) {
|
||||
// Check if the QoS is compatible with intra-process.
|
||||
auto qos_profile = get_actual_qos();
|
||||
if (qos_profile.history() != rclcpp::HistoryPolicy::KeepLast) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication allowed only with keep last history qos policy");
|
||||
"intraprocess communication on topic '" + topic +
|
||||
"' allowed only with keep last history qos policy");
|
||||
}
|
||||
if (qos.depth() == 0) {
|
||||
if (qos_profile.depth() == 0) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication is not allowed with a zero qos history depth value");
|
||||
"intraprocess communication on topic '" + topic +
|
||||
"' is not allowed with a zero qos history depth value");
|
||||
}
|
||||
if (qos.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
|
||||
if (qos_profile.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
|
||||
buffer_ = rclcpp::experimental::create_intra_process_buffer<
|
||||
ROSMessageType, ROSMessageTypeAllocator, ROSMessageTypeDeleter>(
|
||||
rclcpp::detail::resolve_intra_process_buffer_type(options_.intra_process_buffer_type),
|
||||
qos,
|
||||
qos_profile,
|
||||
std::make_shared<ROSMessageTypeAllocator>(ros_message_type_allocator_));
|
||||
}
|
||||
// Register the publisher with the intra process manager.
|
||||
uint64_t intra_process_publisher_id = ipm->add_publisher(this->shared_from_this(), buffer_);
|
||||
this->setup_intra_process(
|
||||
intra_process_publisher_id,
|
||||
@@ -232,8 +232,12 @@ public:
|
||||
// interprocess publish, resulting in lower publish-to-subscribe latency.
|
||||
// It's not possible to do that with an unique_ptr,
|
||||
// as do_intra_process_publish takes the ownership of the message.
|
||||
|
||||
// When durability is set to TransientLocal (i.e. there is a buffer),
|
||||
// inter process publish should always take place to ensure
|
||||
// late joiners receive past data.
|
||||
bool inter_process_publish_needed =
|
||||
get_subscription_count() > get_intra_process_subscription_count();
|
||||
get_subscription_count() > get_intra_process_subscription_count() || buffer_;
|
||||
|
||||
if (inter_process_publish_needed) {
|
||||
auto shared_msg =
|
||||
@@ -310,8 +314,11 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
// When durability is set to TransientLocal (i.e. there is a buffer),
|
||||
// inter process publish should always take place to ensure
|
||||
// late joiners receive past data.
|
||||
bool inter_process_publish_needed =
|
||||
get_subscription_count() > get_intra_process_subscription_count();
|
||||
get_subscription_count() > get_intra_process_subscription_count() || buffer_;
|
||||
|
||||
if (inter_process_publish_needed) {
|
||||
auto ros_msg_ptr = std::make_shared<ROSMessageType>();
|
||||
|
||||
@@ -146,10 +146,18 @@ public:
|
||||
QoS(size_t history_depth); // NOLINT(runtime/explicit): conversion constructor
|
||||
|
||||
/// Return the rmw qos profile.
|
||||
/**
|
||||
* The profile consists of various QoS policies such as history, reliability, and durability.
|
||||
* Use the corresponding getter functions to retrieve individual policies.
|
||||
*/
|
||||
rmw_qos_profile_t &
|
||||
get_rmw_qos_profile();
|
||||
|
||||
/// Return the rmw qos profile.
|
||||
/**
|
||||
* The profile consists of various QoS policies such as history, reliability, and durability.
|
||||
* Use the corresponding getter functions to retrieve individual policies.
|
||||
*/
|
||||
const rmw_qos_profile_t &
|
||||
get_rmw_qos_profile() const;
|
||||
|
||||
@@ -475,7 +483,7 @@ public:
|
||||
explicit
|
||||
RosoutQoS(
|
||||
const QoSInitialization & rosout_qos_initialization = (
|
||||
QoSInitialization::from_rmw(rcl_qos_profile_rosout_default)
|
||||
QoSInitialization::from_rmw(rmw_qos_profile_rosout_default)
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
@@ -504,6 +504,9 @@ public:
|
||||
* \param[in] clock clock to use to generate introspection timestamps
|
||||
* \param[in] qos_service_event_pub QoS settings to use when creating the introspection publisher
|
||||
* \param[in] introspection_state the state to set introspection to
|
||||
*
|
||||
* \throws anything rclcpp::exceptions::throw_from_rcl_error can throw if
|
||||
* it failed to configure introspection.
|
||||
*/
|
||||
void
|
||||
configure_introspection(
|
||||
|
||||
@@ -201,6 +201,7 @@ public:
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Couldn't add subscription to wait set: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -210,6 +211,7 @@ public:
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Couldn't add client to wait set: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -219,6 +221,7 @@ public:
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Couldn't add service to wait set: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -228,6 +231,7 @@ public:
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Couldn't add timer to wait set: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +114,9 @@ public:
|
||||
* all references.
|
||||
* \param[in] msg Shared pointer to the message to return.
|
||||
*/
|
||||
void return_message(std::shared_ptr<MessageT> & msg)
|
||||
void return_message([[maybe_unused]] std::shared_ptr<MessageT> & msg)
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -147,11 +147,13 @@ public:
|
||||
auto qos_profile = get_actual_qos();
|
||||
if (qos_profile.history() != rclcpp::HistoryPolicy::KeepLast) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication allowed only with keep last history qos policy");
|
||||
"intraprocess communication on topic '" + topic_name +
|
||||
"' allowed only with keep last history qos policy");
|
||||
}
|
||||
if (qos_profile.depth() == 0) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication is not allowed with 0 depth qos policy");
|
||||
"intraprocess communication on topic '" + topic_name +
|
||||
"' is not allowed with 0 depth qos policy");
|
||||
}
|
||||
|
||||
using SubscriptionIntraProcessT = rclcpp::experimental::SubscriptionIntraProcess<
|
||||
@@ -207,13 +209,11 @@ public:
|
||||
/// Called after construction to continue setup that requires shared_from_this().
|
||||
void
|
||||
post_init_setup(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
const rclcpp::QoS & qos,
|
||||
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options)
|
||||
[[maybe_unused]] rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
[[maybe_unused]] const rclcpp::QoS & qos,
|
||||
[[maybe_unused]] const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options)
|
||||
{
|
||||
(void)node_base;
|
||||
(void)qos;
|
||||
(void)options;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
/// Take the next message from the inter-process subscription.
|
||||
@@ -420,20 +420,17 @@ public:
|
||||
|
||||
void
|
||||
return_dynamic_message(
|
||||
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message) override
|
||||
[[maybe_unused]] rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message) override
|
||||
{
|
||||
(void) message;
|
||||
throw rclcpp::exceptions::UnimplementedError(
|
||||
"return_dynamic_message is not implemented for Subscription");
|
||||
}
|
||||
|
||||
void
|
||||
handle_dynamic_message(
|
||||
const rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message,
|
||||
const rclcpp::MessageInfo & message_info) override
|
||||
[[maybe_unused]] const rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message,
|
||||
[[maybe_unused]] const rclcpp::MessageInfo & message_info) override
|
||||
{
|
||||
(void) message;
|
||||
(void) message_info;
|
||||
throw rclcpp::exceptions::UnimplementedError(
|
||||
"handle_dynamic_message is not implemented for Subscription");
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "rcl/event_callback.h"
|
||||
#include "rcl/subscription.h"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <tuple>
|
||||
|
||||
#include "rcpputils/shared_library.hpp"
|
||||
#include "rosidl_runtime_cpp/action_type_support_decl.hpp"
|
||||
#include "rosidl_runtime_cpp/message_type_support_decl.hpp"
|
||||
#include "rosidl_runtime_cpp/service_type_support_decl.hpp"
|
||||
|
||||
@@ -57,7 +58,7 @@ get_message_typesupport_handle(
|
||||
|
||||
/// Extract the service type support handle from the library.
|
||||
/**
|
||||
* The library needs to match the topic type. The shared library must stay loaded for the lifetime of the result.
|
||||
* The library needs to match the service type. The shared library must stay loaded for the lifetime of the result.
|
||||
*
|
||||
* \param[in] type The service type, e.g. "std_srvs/srv/Empty"
|
||||
* \param[in] typesupport_identifier Type support identifier, typically "rosidl_typesupport_cpp"
|
||||
@@ -72,6 +73,24 @@ get_service_typesupport_handle(
|
||||
const std::string & typesupport_identifier,
|
||||
rcpputils::SharedLibrary & library);
|
||||
|
||||
/// Extract the action type support handle from the library.
|
||||
/**
|
||||
* The library needs to match the action type. The shared library must stay loaded for the lifetime
|
||||
* of the result.
|
||||
*
|
||||
* \param[in] type The action type, e.g. "example_interfaces/action/Fibonacci"
|
||||
* \param[in] typesupport_identifier Type support identifier, typically "rosidl_typesupport_cpp"
|
||||
* \param[in] library The shared type support library
|
||||
* \throws std::runtime_error if the symbol of type not found in the library.
|
||||
* \return A action type support handle
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
const rosidl_action_type_support_t *
|
||||
get_action_typesupport_handle(
|
||||
const std::string & type,
|
||||
const std::string & typesupport_identifier,
|
||||
rcpputils::SharedLibrary & library);
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__TYPESUPPORT_HELPERS_HPP_
|
||||
|
||||
@@ -176,7 +176,9 @@ public:
|
||||
for (; ii < wait_set.size_of_timers(); ++ii) {
|
||||
if (rcl_wait_set.timers[ii] != nullptr) {
|
||||
ret = wait_set.timers(ii);
|
||||
break;
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +219,9 @@ public:
|
||||
if (rcl_wait_set.subscriptions[ii] != nullptr) {
|
||||
ret = wait_set.subscriptions(ii);
|
||||
rcl_wait_set.subscriptions[ii] = nullptr;
|
||||
break;
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +241,9 @@ public:
|
||||
if (rcl_wait_set.services[ii] != nullptr) {
|
||||
ret = wait_set.services(ii);
|
||||
rcl_wait_set.services[ii] = nullptr;
|
||||
break;
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +263,9 @@ public:
|
||||
if (rcl_wait_set.clients[ii] != nullptr) {
|
||||
ret = wait_set.clients(ii);
|
||||
rcl_wait_set.clients[ii] = nullptr;
|
||||
break;
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +216,11 @@ public:
|
||||
shared_waitables_
|
||||
);
|
||||
|
||||
if (this->needs_pruning_) {
|
||||
this->storage_prune_deleted_entities();
|
||||
this->needs_pruning_ = false;
|
||||
}
|
||||
|
||||
this->storage_release_ownerships();
|
||||
}
|
||||
|
||||
@@ -455,59 +460,60 @@ public:
|
||||
|
||||
size_t size_of_subscriptions() const
|
||||
{
|
||||
return shared_subscriptions_.size();
|
||||
return subscriptions_.size();
|
||||
}
|
||||
|
||||
size_t size_of_timers() const
|
||||
{
|
||||
return shared_timers_.size();
|
||||
return timers_.size();
|
||||
}
|
||||
|
||||
size_t size_of_clients() const
|
||||
{
|
||||
return shared_clients_.size();
|
||||
return clients_.size();
|
||||
}
|
||||
|
||||
size_t size_of_services() const
|
||||
{
|
||||
return shared_services_.size();
|
||||
return services_.size();
|
||||
}
|
||||
|
||||
size_t size_of_waitables() const
|
||||
{
|
||||
return shared_waitables_.size();
|
||||
return waitables_.size();
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::SubscriptionBase>
|
||||
subscriptions(size_t ii) const
|
||||
{
|
||||
return shared_subscriptions_[ii].subscription;
|
||||
return subscriptions_[ii].lock();
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::TimerBase>
|
||||
timers(size_t ii) const
|
||||
{
|
||||
return shared_timers_[ii];
|
||||
return timers_[ii].lock();
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::ClientBase>
|
||||
clients(size_t ii) const
|
||||
{
|
||||
return shared_clients_[ii];
|
||||
return clients_[ii].lock();
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::ServiceBase>
|
||||
services(size_t ii) const
|
||||
{
|
||||
return shared_services_[ii];
|
||||
return services_[ii].lock();
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::Waitable>
|
||||
waitables(size_t ii) const
|
||||
{
|
||||
return shared_waitables_[ii].waitable;
|
||||
return waitables_[ii].lock();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t ownership_reference_counter_ = 0;
|
||||
|
||||
SequenceOfWeakSubscriptions subscriptions_;
|
||||
|
||||
@@ -160,6 +160,15 @@ public:
|
||||
services_,
|
||||
waitables_
|
||||
);
|
||||
|
||||
if(this->needs_pruning_) {
|
||||
// we need to throw here, as the indexing of the rcl_waitset is broken,
|
||||
// in case of invalid entries
|
||||
|
||||
throw std::runtime_error(
|
||||
"StaticStorage::storage_rebuild_rcl_wait_set(): entity weak_ptr "
|
||||
"unexpectedly expired in static entity storage");
|
||||
}
|
||||
}
|
||||
|
||||
// storage_add_subscription() explicitly not declared here
|
||||
|
||||
@@ -734,8 +734,6 @@ private:
|
||||
wait_result_dirty_ = false;
|
||||
// this method comes from the SynchronizationPolicy
|
||||
this->sync_wait_result_acquire();
|
||||
// this method comes from the StoragePolicy
|
||||
this->storage_acquire_ownerships();
|
||||
}
|
||||
|
||||
/// Called by the WaitResult's destructor to release resources.
|
||||
@@ -752,8 +750,6 @@ private:
|
||||
}
|
||||
wait_result_holding_ = false;
|
||||
wait_result_dirty_ = false;
|
||||
// this method comes from the StoragePolicy
|
||||
this->storage_release_ownerships();
|
||||
// this method comes from the SynchronizationPolicy
|
||||
this->sync_wait_result_release();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/visibility_control.hpp"
|
||||
@@ -27,6 +28,8 @@
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
class TimerBase;
|
||||
|
||||
class Waitable
|
||||
{
|
||||
public:
|
||||
@@ -258,6 +261,17 @@ public:
|
||||
void
|
||||
clear_on_ready_callback() = 0;
|
||||
|
||||
/// Returns all timers used by this waitable
|
||||
/**
|
||||
* Must return all timers used within the waitable.
|
||||
* Note, it is not supported, that timers are added
|
||||
* or removed over the lifetime of the waitable.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
get_timers() const = 0;
|
||||
|
||||
private:
|
||||
std::atomic<bool> in_use_by_wait_set_{false};
|
||||
}; // class Waitable
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="2">
|
||||
<name>rclcpp</name>
|
||||
<version>29.2.0</version>
|
||||
<version>29.4.0</version>
|
||||
<description>The ROS client library in C++.</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -367,4 +367,245 @@ Clock::create_jump_callback(
|
||||
// *INDENT-ON*
|
||||
}
|
||||
|
||||
class ClockWaiter::ClockWaiterImpl
|
||||
{
|
||||
private:
|
||||
std::condition_variable cv_;
|
||||
|
||||
rclcpp::Clock::SharedPtr clock_;
|
||||
bool time_source_changed_ = false;
|
||||
std::function<void(const rcl_time_jump_t &)> post_time_jump_callback;
|
||||
|
||||
bool
|
||||
wait_until_system_time(
|
||||
std::unique_lock<std::mutex> & lock,
|
||||
const rclcpp::Time & abs_time, const std::function<bool ()> & pred)
|
||||
{
|
||||
auto system_time = std::chrono::system_clock::time_point(
|
||||
// Cast because system clock resolution is too big for nanoseconds on some systems
|
||||
std::chrono::duration_cast<std::chrono::system_clock::duration>(
|
||||
std::chrono::nanoseconds(abs_time.nanoseconds())));
|
||||
|
||||
return cv_.wait_until(lock, system_time, pred);
|
||||
}
|
||||
|
||||
bool
|
||||
wait_until_steady_time(
|
||||
std::unique_lock<std::mutex> & lock,
|
||||
const rclcpp::Time & abs_time, const std::function<bool ()> & pred)
|
||||
{
|
||||
// Synchronize because RCL steady clock epoch might differ from chrono::steady_clock epoch
|
||||
const rclcpp::Time rcl_entry = clock_->now();
|
||||
const std::chrono::steady_clock::time_point chrono_entry = std::chrono::steady_clock::now();
|
||||
const rclcpp::Duration delta_t = abs_time - rcl_entry;
|
||||
const std::chrono::steady_clock::time_point chrono_until =
|
||||
chrono_entry + std::chrono::nanoseconds(delta_t.nanoseconds());
|
||||
|
||||
return cv_.wait_until(lock, chrono_until, pred);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
wait_until_ros_time(
|
||||
std::unique_lock<std::mutex> & lock,
|
||||
const rclcpp::Time & abs_time, const std::function<bool ()> & pred)
|
||||
{
|
||||
// Install jump handler for any amount of time change, for two purposes:
|
||||
// - if ROS time is active, check if time reached on each new clock sample
|
||||
// - Trigger via on_clock_change to detect if time source changes, to invalidate sleep
|
||||
rcl_jump_threshold_t threshold;
|
||||
threshold.on_clock_change = true;
|
||||
// 0 is disable, so -1 and 1 are smallest possible time changes
|
||||
threshold.min_backward.nanoseconds = -1;
|
||||
threshold.min_forward.nanoseconds = 1;
|
||||
|
||||
time_source_changed_ = false;
|
||||
|
||||
post_time_jump_callback = [this, &lock] (const rcl_time_jump_t & jump)
|
||||
{
|
||||
if (jump.clock_change != RCL_ROS_TIME_NO_CHANGE) {
|
||||
std::lock_guard<std::mutex> lk(*lock.mutex());
|
||||
time_source_changed_ = true;
|
||||
}
|
||||
cv_.notify_one();
|
||||
};
|
||||
|
||||
// Note this is a trade-off. Adding the callback for every call
|
||||
// is expensive for high frequency calls. For low frequency waits
|
||||
// its more overhead to have the callback being called all the time.
|
||||
// As we expect the use case to be low frequency calls to wait_until
|
||||
// with relative big pauses between the calls, we install it on demand.
|
||||
auto clock_handler = clock_->create_jump_callback(
|
||||
nullptr,
|
||||
post_time_jump_callback,
|
||||
threshold);
|
||||
|
||||
if (!clock_->ros_time_is_active()) {
|
||||
auto system_time = std::chrono::system_clock::time_point(
|
||||
// Cast because system clock resolution is too big for nanoseconds on some systems
|
||||
std::chrono::duration_cast<std::chrono::system_clock::duration>(
|
||||
std::chrono::nanoseconds(abs_time.nanoseconds())));
|
||||
|
||||
return cv_.wait_until(lock, system_time, [this, &pred] () {
|
||||
return time_source_changed_ || pred();
|
||||
});
|
||||
}
|
||||
|
||||
// RCL_ROS_TIME with ros_time_is_active.
|
||||
// Just wait without "until" because installed
|
||||
// jump callbacks wake the cv on every new sample.
|
||||
cv_.wait(lock, [this, &pred, &abs_time] () {
|
||||
return clock_->now() >= abs_time || time_source_changed_ || pred();
|
||||
});
|
||||
|
||||
return clock_->now() < abs_time;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit ClockWaiterImpl(const rclcpp::Clock::SharedPtr & clock)
|
||||
:clock_(clock)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
wait_until(
|
||||
std::unique_lock<std::mutex> & lock,
|
||||
const rclcpp::Time & abs_time, const std::function<bool ()> & pred)
|
||||
{
|
||||
switch(clock_->get_clock_type()) {
|
||||
case RCL_CLOCK_UNINITIALIZED:
|
||||
throw std::runtime_error("Error, wait on uninitialized clock called");
|
||||
case RCL_ROS_TIME:
|
||||
return wait_until_ros_time(lock, abs_time, pred);
|
||||
break;
|
||||
case RCL_STEADY_TIME:
|
||||
return wait_until_steady_time(lock, abs_time, pred);
|
||||
break;
|
||||
case RCL_SYSTEM_TIME:
|
||||
return wait_until_system_time(lock, abs_time, pred);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
notify_one()
|
||||
{
|
||||
cv_.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
ClockWaiter::ClockWaiter(const rclcpp::Clock::SharedPtr & clock)
|
||||
:impl_(std::make_unique<ClockWaiterImpl>(clock))
|
||||
{
|
||||
}
|
||||
|
||||
ClockWaiter::~ClockWaiter() = default;
|
||||
|
||||
bool
|
||||
ClockWaiter::wait_until(
|
||||
std::unique_lock<std::mutex> & lock,
|
||||
const rclcpp::Time & abs_time, const std::function<bool ()> & pred)
|
||||
{
|
||||
return impl_->wait_until(lock, abs_time, pred);
|
||||
}
|
||||
|
||||
void
|
||||
ClockWaiter::notify_one()
|
||||
{
|
||||
impl_->notify_one();
|
||||
}
|
||||
|
||||
class ClockConditionalVariable::Impl
|
||||
{
|
||||
std::mutex pred_mutex_;
|
||||
bool shutdown_ = false;
|
||||
rclcpp::Context::SharedPtr context_;
|
||||
rclcpp::OnShutdownCallbackHandle shutdown_cb_handle_;
|
||||
ClockWaiter::UniquePtr clock_;
|
||||
|
||||
public:
|
||||
Impl(const rclcpp::Clock::SharedPtr & clock, rclcpp::Context::SharedPtr context)
|
||||
:context_(context),
|
||||
clock_(std::make_unique<ClockWaiter>(clock))
|
||||
{
|
||||
if (!context_ || !context_->is_valid()) {
|
||||
throw std::runtime_error("context cannot be slept with because it's invalid");
|
||||
}
|
||||
// Wake this thread if the context is shutdown
|
||||
shutdown_cb_handle_ = context_->add_on_shutdown_callback(
|
||||
[this]() {
|
||||
{
|
||||
std::unique_lock lock(pred_mutex_);
|
||||
shutdown_ = true;
|
||||
}
|
||||
clock_->notify_one();
|
||||
});
|
||||
}
|
||||
|
||||
~Impl()
|
||||
{
|
||||
context_->remove_on_shutdown_callback(shutdown_cb_handle_);
|
||||
}
|
||||
|
||||
bool
|
||||
wait_until(
|
||||
std::unique_lock<std::mutex> & lock, rclcpp::Time until,
|
||||
const std::function<bool ()> & pred)
|
||||
{
|
||||
if(lock.mutex() != &pred_mutex_) {
|
||||
throw std::runtime_error(
|
||||
"ClockConditionalVariable::wait_until: Internal error, given lock does not use"
|
||||
" mutex returned by this->mutex()");
|
||||
}
|
||||
|
||||
clock_->wait_until(lock, until, [this, &pred] () -> bool {
|
||||
return shutdown_ || pred();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
notify_one()
|
||||
{
|
||||
clock_->notify_one();
|
||||
}
|
||||
|
||||
std::mutex &
|
||||
mutex()
|
||||
{
|
||||
return pred_mutex_;
|
||||
}
|
||||
};
|
||||
|
||||
ClockConditionalVariable::ClockConditionalVariable(
|
||||
const rclcpp::Clock::SharedPtr & clock,
|
||||
rclcpp::Context::SharedPtr context)
|
||||
:impl_(std::make_unique<Impl>(clock, context))
|
||||
{
|
||||
}
|
||||
|
||||
ClockConditionalVariable::~ClockConditionalVariable() = default;
|
||||
|
||||
void
|
||||
ClockConditionalVariable::notify_one()
|
||||
{
|
||||
impl_->notify_one();
|
||||
}
|
||||
|
||||
bool
|
||||
ClockConditionalVariable::wait_until(
|
||||
std::unique_lock<std::mutex> & lock, rclcpp::Time until,
|
||||
const std::function<bool ()> & pred)
|
||||
{
|
||||
return impl_->wait_until(lock, until, pred);
|
||||
}
|
||||
|
||||
std::mutex &
|
||||
ClockConditionalVariable::mutex()
|
||||
{
|
||||
return impl_->mutex();
|
||||
}
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -23,10 +23,9 @@ namespace detail
|
||||
|
||||
void
|
||||
RMWImplementationSpecificPublisherPayload::modify_rmw_publisher_options(
|
||||
rmw_publisher_options_t & rmw_publisher_options) const
|
||||
[[maybe_unused]] rmw_publisher_options_t & rmw_publisher_options) const
|
||||
{
|
||||
// By default, do not mutate the rmw publisher options.
|
||||
(void)rmw_publisher_options;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -23,10 +23,9 @@ namespace detail
|
||||
|
||||
void
|
||||
RMWImplementationSpecificSubscriptionPayload::modify_rmw_subscription_options(
|
||||
rmw_subscription_options_t & rmw_subscription_options) const
|
||||
[[maybe_unused]] rmw_subscription_options_t & rmw_subscription_options) const
|
||||
{
|
||||
// By default, do not mutate the rmw subscription options.
|
||||
(void)rmw_subscription_options;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -31,11 +31,10 @@ namespace detail
|
||||
|
||||
std::vector<std::string>
|
||||
get_unparsed_ros_arguments(
|
||||
int argc, char const * const * argv,
|
||||
[[maybe_unused]] int argc, char const * const * argv,
|
||||
rcl_arguments_t * arguments,
|
||||
rcl_allocator_t allocator)
|
||||
{
|
||||
(void)argc;
|
||||
std::vector<std::string> unparsed_ros_arguments;
|
||||
int unparsed_ros_args_count = rcl_arguments_get_count_unparsed_ros(arguments);
|
||||
if (unparsed_ros_args_count > 0) {
|
||||
|
||||
@@ -93,7 +93,9 @@ throw_from_rcl_error(
|
||||
RCLErrorBase::RCLErrorBase(rcl_ret_t ret, const rcl_error_state_t * error_state)
|
||||
: ret(ret), message(error_state->message), file(error_state->file), line(error_state->line_number),
|
||||
formatted_message(rcl_get_error_string().str)
|
||||
{}
|
||||
{
|
||||
rcl_reset_error();
|
||||
}
|
||||
|
||||
RCLError::RCLError(
|
||||
rcl_ret_t ret,
|
||||
|
||||
@@ -66,7 +66,7 @@ Executor::Executor(const rclcpp::ExecutorOptions & options)
|
||||
notify_waitable_(std::make_shared<rclcpp::executors::ExecutorNotifyWaitable>(
|
||||
[this]() {
|
||||
this->entities_need_rebuild_.store(true);
|
||||
})),
|
||||
}, options.context)),
|
||||
entities_need_rebuild_(true),
|
||||
collector_(notify_waitable_),
|
||||
wait_set_({}, {}, {}, {}, {}, {}, options.context),
|
||||
@@ -84,7 +84,9 @@ Executor::Executor(const rclcpp::ExecutorOptions & options)
|
||||
notify_waitable_->add_guard_condition(interrupt_guard_condition_);
|
||||
notify_waitable_->add_guard_condition(shutdown_guard_condition_);
|
||||
|
||||
wait_set_.add_waitable(notify_waitable_);
|
||||
// we need to initially rebuild the collection,
|
||||
// so that the notify_waitable_ is added
|
||||
collect_entities();
|
||||
}
|
||||
|
||||
Executor::~Executor()
|
||||
@@ -167,10 +169,9 @@ Executor::get_automatically_added_callback_groups_from_nodes()
|
||||
void
|
||||
Executor::add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
[[maybe_unused]] rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
bool notify)
|
||||
{
|
||||
(void) node_ptr;
|
||||
this->collector_.add_callback_group(group_ptr);
|
||||
|
||||
try {
|
||||
@@ -371,7 +372,14 @@ Executor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
|
||||
// both spin_some and spin_all wait for work at the beginning
|
||||
wait_result_.reset();
|
||||
wait_for_work(std::chrono::milliseconds(0));
|
||||
bool just_waited = true;
|
||||
bool entity_states_fully_polled = true;
|
||||
|
||||
if (entities_need_rebuild_) {
|
||||
// if the last wait triggered a collection rebuild, we need to call
|
||||
// wait_for_work once more, in order to do a collection rebuild and collect
|
||||
// events from the just added entities
|
||||
entity_states_fully_polled = false;
|
||||
}
|
||||
|
||||
// The logic of this while loop is as follows:
|
||||
//
|
||||
@@ -393,12 +401,14 @@ Executor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
|
||||
AnyExecutable any_exec;
|
||||
if (get_next_ready_executable(any_exec)) {
|
||||
execute_any_executable(any_exec);
|
||||
just_waited = false;
|
||||
// during the execution some entity might got ready therefore we need
|
||||
// to repoll the states of all entities
|
||||
entity_states_fully_polled = false;
|
||||
} else {
|
||||
// if nothing is ready, reset the result to clear it
|
||||
wait_result_.reset();
|
||||
|
||||
if (just_waited) {
|
||||
if (entity_states_fully_polled) {
|
||||
// there was no work after just waiting, always exit in this case
|
||||
// before the exhaustive condition can be checked
|
||||
break;
|
||||
@@ -408,7 +418,13 @@ Executor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
|
||||
// if exhaustive, wait for work again
|
||||
// this only happens for spin_all; spin_some only waits at the start
|
||||
wait_for_work(std::chrono::milliseconds(0));
|
||||
just_waited = true;
|
||||
entity_states_fully_polled = true;
|
||||
if (entities_need_rebuild_) {
|
||||
// if the last wait triggered a collection rebuild, we need to call
|
||||
// wait_for_work once more, in order to do a collection rebuild and
|
||||
// collect events from the just added entities
|
||||
entity_states_fully_polled = false;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -568,6 +584,7 @@ Executor::execute_subscription(rclcpp::SubscriptionBase::SharedPtr subscription)
|
||||
"rcl_return_loaned_message_from_subscription() failed for subscription on topic "
|
||||
"'%s': %s",
|
||||
subscription->get_topic_name(), rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
}
|
||||
loaned_msg = nullptr;
|
||||
}
|
||||
@@ -731,11 +748,14 @@ Executor::wait_for_work(std::chrono::nanoseconds timeout)
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {
|
||||
this->collect_entities();
|
||||
}
|
||||
}
|
||||
|
||||
this->wait_result_.emplace(wait_set_.wait(timeout));
|
||||
|
||||
if (!this->wait_result_ || this->wait_result_->kind() == WaitResultKind::Empty) {
|
||||
RCUTILS_LOG_WARN_NAMED(
|
||||
"rclcpp",
|
||||
|
||||
@@ -20,8 +20,11 @@ namespace rclcpp
|
||||
namespace executors
|
||||
{
|
||||
|
||||
ExecutorNotifyWaitable::ExecutorNotifyWaitable(std::function<void(void)> on_execute_callback)
|
||||
: execute_callback_(on_execute_callback)
|
||||
ExecutorNotifyWaitable::ExecutorNotifyWaitable(
|
||||
std::function<void(void)> on_execute_callback,
|
||||
const rclcpp::Context::SharedPtr & context)
|
||||
: execute_callback_(on_execute_callback),
|
||||
guard_condition_(std::make_shared<rclcpp::GuardCondition>(context))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -30,6 +33,9 @@ ExecutorNotifyWaitable::ExecutorNotifyWaitable(ExecutorNotifyWaitable & other)
|
||||
std::lock_guard<std::mutex> lock(other.guard_condition_mutex_);
|
||||
this->execute_callback_ = other.execute_callback_;
|
||||
this->notify_guard_conditions_ = other.notify_guard_conditions_;
|
||||
this->guard_condition_ = other.guard_condition_;
|
||||
this->idxs_of_added_guard_condition_ = other.idxs_of_added_guard_condition_;
|
||||
this->needs_processing = other.needs_processing;
|
||||
}
|
||||
|
||||
ExecutorNotifyWaitable & ExecutorNotifyWaitable::operator=(ExecutorNotifyWaitable & other)
|
||||
@@ -38,6 +44,9 @@ ExecutorNotifyWaitable & ExecutorNotifyWaitable::operator=(ExecutorNotifyWaitabl
|
||||
std::lock_guard<std::mutex> lock(other.guard_condition_mutex_);
|
||||
this->execute_callback_ = other.execute_callback_;
|
||||
this->notify_guard_conditions_ = other.notify_guard_conditions_;
|
||||
this->guard_condition_ = other.guard_condition_;
|
||||
this->idxs_of_added_guard_condition_ = other.idxs_of_added_guard_condition_;
|
||||
this->needs_processing = other.needs_processing;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -47,21 +56,42 @@ ExecutorNotifyWaitable::add_to_wait_set(rcl_wait_set_t & wait_set)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(guard_condition_mutex_);
|
||||
|
||||
// Note: no guard conditions need to be re-triggered, since the guard
|
||||
// conditions in this class are not tracking a stateful condition, but instead
|
||||
// only serve to interrupt the wait set when new information is available to
|
||||
// consider.
|
||||
for (auto weak_guard_condition : this->notify_guard_conditions_) {
|
||||
auto guard_condition = weak_guard_condition.lock();
|
||||
if (!guard_condition) {continue;}
|
||||
idxs_of_added_guard_condition_.clear();
|
||||
idxs_of_added_guard_condition_.reserve(notify_guard_conditions_.size());
|
||||
|
||||
rcl_guard_condition_t * cond = &guard_condition->get_rcl_guard_condition();
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(&wait_set, cond, NULL);
|
||||
if(needs_processing) {
|
||||
rcl_guard_condition_t * cond = &guard_condition_->get_rcl_guard_condition();
|
||||
size_t rcl_index;
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(&wait_set, cond, &rcl_index);
|
||||
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(
|
||||
ret, "failed to add guard condition to wait set");
|
||||
}
|
||||
|
||||
idxs_of_added_guard_condition_.push_back(rcl_index);
|
||||
|
||||
// we want to directly wake up any way, not need to add the other guard conditions
|
||||
guard_condition_->trigger();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: no guard conditions need to be re-triggered, since the guard
|
||||
// conditions in this class are not tracking a stateful condition, but instead
|
||||
// only serve to interrupt the wait set when new information is available to
|
||||
// consider.
|
||||
for (const auto & guard_condition : this->notify_guard_conditions_) {
|
||||
rcl_guard_condition_t * cond = &guard_condition->get_rcl_guard_condition();
|
||||
size_t rcl_index;
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(&wait_set, cond, &rcl_index);
|
||||
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(
|
||||
ret, "failed to add guard condition to wait set");
|
||||
}
|
||||
|
||||
idxs_of_added_guard_condition_.push_back(rcl_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,20 +101,23 @@ ExecutorNotifyWaitable::is_ready(const rcl_wait_set_t & wait_set)
|
||||
std::lock_guard<std::mutex> lock(guard_condition_mutex_);
|
||||
|
||||
bool any_ready = false;
|
||||
for (size_t ii = 0; ii < wait_set.size_of_guard_conditions; ++ii) {
|
||||
const auto * rcl_guard_condition = wait_set.guard_conditions[ii];
|
||||
for (size_t rcl_index : idxs_of_added_guard_condition_) {
|
||||
if(rcl_index >= wait_set.size_of_guard_conditions) {
|
||||
throw std::runtime_error(
|
||||
"ExecutorNotifyWaitable::is_ready: Internal error, got index out of range");
|
||||
}
|
||||
|
||||
const auto * rcl_guard_condition = wait_set.guard_conditions[rcl_index];
|
||||
|
||||
if (nullptr == rcl_guard_condition) {
|
||||
continue;
|
||||
}
|
||||
for (const auto & weak_guard_condition : this->notify_guard_conditions_) {
|
||||
auto guard_condition = weak_guard_condition.lock();
|
||||
if (guard_condition && &guard_condition->get_rcl_guard_condition() == rcl_guard_condition) {
|
||||
any_ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
any_ready = true;
|
||||
needs_processing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return any_ready;
|
||||
}
|
||||
|
||||
@@ -92,6 +125,9 @@ void
|
||||
ExecutorNotifyWaitable::execute(const std::shared_ptr<void> & /*data*/)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(execute_mutex_);
|
||||
|
||||
needs_processing = false;
|
||||
|
||||
this->execute_callback_();
|
||||
}
|
||||
|
||||
@@ -102,9 +138,8 @@ ExecutorNotifyWaitable::take_data()
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
ExecutorNotifyWaitable::take_data_by_entity_id(size_t id)
|
||||
ExecutorNotifyWaitable::take_data_by_entity_id([[maybe_unused]] size_t id)
|
||||
{
|
||||
(void) id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -122,11 +157,7 @@ ExecutorNotifyWaitable::set_on_ready_callback(std::function<void(size_t, int)> c
|
||||
std::lock_guard<std::mutex> lock(guard_condition_mutex_);
|
||||
|
||||
on_ready_callback_ = gc_callback;
|
||||
for (auto weak_gc : notify_guard_conditions_) {
|
||||
auto gc = weak_gc.lock();
|
||||
if (!gc) {
|
||||
continue;
|
||||
}
|
||||
for (const auto & gc : notify_guard_conditions_) {
|
||||
gc->set_on_trigger_callback(on_ready_callback_);
|
||||
}
|
||||
}
|
||||
@@ -138,11 +169,7 @@ ExecutorNotifyWaitable::clear_on_ready_callback()
|
||||
std::lock_guard<std::mutex> lock(guard_condition_mutex_);
|
||||
|
||||
on_ready_callback_ = nullptr;
|
||||
for (auto weak_gc : notify_guard_conditions_) {
|
||||
auto gc = weak_gc.lock();
|
||||
if (!gc) {
|
||||
continue;
|
||||
}
|
||||
for (const auto & gc : notify_guard_conditions_) {
|
||||
gc->set_on_trigger_callback(nullptr);
|
||||
}
|
||||
}
|
||||
@@ -159,9 +186,9 @@ void
|
||||
ExecutorNotifyWaitable::add_guard_condition(rclcpp::GuardCondition::WeakPtr weak_guard_condition)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(guard_condition_mutex_);
|
||||
auto guard_condition = weak_guard_condition.lock();
|
||||
if (guard_condition && notify_guard_conditions_.count(weak_guard_condition) == 0) {
|
||||
notify_guard_conditions_.insert(weak_guard_condition);
|
||||
const auto & guard_condition = weak_guard_condition.lock();
|
||||
if (guard_condition && notify_guard_conditions_.count(guard_condition) == 0) {
|
||||
notify_guard_conditions_.insert(guard_condition);
|
||||
if (on_ready_callback_) {
|
||||
guard_condition->set_on_trigger_callback(on_ready_callback_);
|
||||
}
|
||||
@@ -172,11 +199,17 @@ void
|
||||
ExecutorNotifyWaitable::remove_guard_condition(rclcpp::GuardCondition::WeakPtr weak_guard_condition)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(guard_condition_mutex_);
|
||||
if (notify_guard_conditions_.count(weak_guard_condition) != 0) {
|
||||
notify_guard_conditions_.erase(weak_guard_condition);
|
||||
auto guard_condition = weak_guard_condition.lock();
|
||||
const auto & guard_condition = weak_guard_condition.lock();
|
||||
if (!guard_condition) {
|
||||
// If the lock did not work, the guard condition can't be
|
||||
// saved in the sets, therefore we don't need to remove it
|
||||
return;
|
||||
}
|
||||
auto it = notify_guard_conditions_.find(guard_condition);
|
||||
if (it != notify_guard_conditions_.end()) {
|
||||
notify_guard_conditions_.erase(it);
|
||||
// If this notify waitable doesn't have an on_ready_callback, then there's nothing to unset
|
||||
if (guard_condition && on_ready_callback_) {
|
||||
if (on_ready_callback_) {
|
||||
guard_condition->set_on_trigger_callback(nullptr);
|
||||
}
|
||||
}
|
||||
@@ -189,5 +222,12 @@ ExecutorNotifyWaitable::get_number_of_ready_guard_conditions()
|
||||
return notify_guard_conditions_.size();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
ExecutorNotifyWaitable::get_timers() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
} // namespace executors
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -79,9 +79,8 @@ MultiThreadedExecutor::get_number_of_threads()
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedExecutor::run(size_t this_thread_number)
|
||||
MultiThreadedExecutor::run([[maybe_unused]] size_t this_thread_number)
|
||||
{
|
||||
(void)this_thread_number;
|
||||
while (rclcpp::ok(this->context_) && spinning.load()) {
|
||||
rclcpp::AnyExecutable any_exec;
|
||||
{
|
||||
|
||||
@@ -110,10 +110,29 @@ StaticSingleThreadedExecutor::spin_once_impl(std::chrono::nanoseconds timeout)
|
||||
std::optional<rclcpp::WaitResult<rclcpp::WaitSet>>
|
||||
StaticSingleThreadedExecutor::collect_and_wait(std::chrono::nanoseconds timeout)
|
||||
{
|
||||
// we need to make sure that callback groups don't get out of scope
|
||||
// during the wait. As in jazzy, they are not covered by the DynamicStorage,
|
||||
// we explicitly hold them here as a bugfix
|
||||
std::vector<rclcpp::CallbackGroup::SharedPtr> cbgs;
|
||||
|
||||
if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {
|
||||
this->collect_entities();
|
||||
}
|
||||
|
||||
auto callback_groups = this->collector_.get_all_callback_groups();
|
||||
cbgs.resize(callback_groups.size());
|
||||
for(const auto & w_ptr : callback_groups) {
|
||||
auto shr_ptr = w_ptr.lock();
|
||||
if(shr_ptr) {
|
||||
cbgs.push_back(std::move(shr_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
auto wait_result = wait_set_.wait(std::chrono::nanoseconds(timeout));
|
||||
|
||||
// drop references to the callback groups, before trying to execute anything
|
||||
cbgs.clear();
|
||||
|
||||
if (wait_result.kind() == WaitResultKind::Empty) {
|
||||
RCUTILS_LOG_WARN_NAMED(
|
||||
"rclcpp",
|
||||
|
||||
@@ -318,10 +318,8 @@ EventsExecutor::execute_event(const ExecutorEvent & event)
|
||||
}
|
||||
|
||||
void
|
||||
EventsExecutor::handle_updated_entities(bool notify)
|
||||
EventsExecutor::handle_updated_entities([[maybe_unused]] bool notify)
|
||||
{
|
||||
(void)notify;
|
||||
|
||||
// Do not rebuild if we don't need to.
|
||||
// A rebuild event could be generated, but then
|
||||
// this function could end up being called from somewhere else
|
||||
@@ -408,8 +406,16 @@ EventsExecutor::refresh_current_collection(
|
||||
[this](auto waitable) {
|
||||
waitable->set_on_ready_callback(
|
||||
this->create_waitable_callback(waitable.get()));
|
||||
for (const auto & t : waitable->get_timers()) {
|
||||
timers_manager_->add_timer(t);
|
||||
}
|
||||
},
|
||||
[](auto waitable) {waitable->clear_on_ready_callback();});
|
||||
[this](auto waitable) {
|
||||
waitable->clear_on_ready_callback();
|
||||
for (const auto & t : waitable->get_timers()) {
|
||||
timers_manager_->remove_timer(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::function<void(size_t)>
|
||||
|
||||
@@ -56,10 +56,9 @@ GenericSubscription::handle_serialized_message(
|
||||
|
||||
void
|
||||
GenericSubscription::handle_loaned_message(
|
||||
void * message, const rclcpp::MessageInfo & message_info)
|
||||
[[maybe_unused]] void * message,
|
||||
[[maybe_unused]] const rclcpp::MessageInfo & message_info)
|
||||
{
|
||||
(void) message;
|
||||
(void) message_info;
|
||||
throw rclcpp::exceptions::UnimplementedError(
|
||||
"handle_loaned_message is not implemented for GenericSubscription");
|
||||
}
|
||||
@@ -111,20 +110,17 @@ GenericSubscription::create_dynamic_message()
|
||||
|
||||
void
|
||||
GenericSubscription::return_dynamic_message(
|
||||
rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message)
|
||||
[[maybe_unused]] rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message)
|
||||
{
|
||||
(void) message;
|
||||
throw rclcpp::exceptions::UnimplementedError(
|
||||
"return_dynamic_message is not implemented for GenericSubscription");
|
||||
}
|
||||
|
||||
void
|
||||
GenericSubscription::handle_dynamic_message(
|
||||
const rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message,
|
||||
const rclcpp::MessageInfo & message_info)
|
||||
[[maybe_unused]] const rclcpp::dynamic_typesupport::DynamicMessage::SharedPtr & message,
|
||||
[[maybe_unused]] const rclcpp::MessageInfo & message_info)
|
||||
{
|
||||
(void) message;
|
||||
(void) message_info;
|
||||
throw rclcpp::exceptions::UnimplementedError(
|
||||
"handle_dynamic_message is not implemented for GenericSubscription");
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ NodeBase::NodeBase(
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Error in destruction of rosout publisher: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +140,7 @@ NodeBase::NodeBase(
|
||||
RCUTILS_LOG_ERROR_NAMED(
|
||||
"rclcpp",
|
||||
"Error in destruction of rcl node handle: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
}
|
||||
delete node;
|
||||
});
|
||||
|
||||
@@ -105,6 +105,7 @@ public:
|
||||
RCLCPP_ERROR(
|
||||
logger_, "Failed to initialize ~/get_type_description service: %s",
|
||||
rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
throw std::runtime_error(
|
||||
"Failed to initialize ~/get_type_description service.");
|
||||
}
|
||||
|
||||
@@ -367,10 +367,8 @@ PublisherBase::default_incompatible_qos_callback(
|
||||
|
||||
void
|
||||
PublisherBase::default_incompatible_type_callback(
|
||||
rclcpp::IncompatibleTypeInfo & event) const
|
||||
[[maybe_unused]] rclcpp::IncompatibleTypeInfo & event) const
|
||||
{
|
||||
(void)event;
|
||||
|
||||
RCLCPP_WARN(
|
||||
rclcpp::get_logger(rcl_node_get_logger_name(rcl_node_handle_.get())),
|
||||
"Incompatible type on topic '%s', no messages will be sent to it.", get_topic_name());
|
||||
|
||||
@@ -407,7 +407,7 @@ ParameterEventsQoS::ParameterEventsQoS(const QoSInitialization & qos_initializat
|
||||
{}
|
||||
|
||||
RosoutQoS::RosoutQoS(const QoSInitialization & rosout_initialization)
|
||||
: QoS(rosout_initialization, rcl_qos_profile_rosout_default)
|
||||
: QoS(rosout_initialization, rmw_qos_profile_rosout_default)
|
||||
{}
|
||||
|
||||
SystemDefaultsQoS::SystemDefaultsQoS(const QoSInitialization & qos_initialization)
|
||||
|
||||
@@ -122,6 +122,7 @@ SerializedMessage::~SerializedMessage()
|
||||
RCLCPP_ERROR(
|
||||
get_logger("rclcpp"),
|
||||
"Failed to destroy serialized message: %s", rcl_get_error_string().str);
|
||||
rcl_reset_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,10 +381,8 @@ SubscriptionBase::default_incompatible_qos_callback(
|
||||
|
||||
void
|
||||
SubscriptionBase::default_incompatible_type_callback(
|
||||
rclcpp::IncompatibleTypeInfo & event) const
|
||||
[[maybe_unused]] rclcpp::IncompatibleTypeInfo & event) const
|
||||
{
|
||||
(void)event;
|
||||
|
||||
RCLCPP_WARN(
|
||||
rclcpp::get_logger(rcl_node_get_logger_name(node_handle_.get())),
|
||||
"Incompatible type on topic '%s', no messages will be sent to it.", get_topic_name());
|
||||
|
||||
@@ -177,4 +177,19 @@ const rosidl_service_type_support_t * get_service_typesupport_handle(
|
||||
));
|
||||
}
|
||||
|
||||
const rosidl_action_type_support_t * get_action_typesupport_handle(
|
||||
const std::string & type,
|
||||
const std::string & typesupport_identifier,
|
||||
rcpputils::SharedLibrary & library)
|
||||
{
|
||||
static const std::string typesupport_name = "action";
|
||||
static const std::string symbol_part_name = "__get_action_type_support_handle__";
|
||||
static const std::string middle_module_additional = "action";
|
||||
|
||||
return static_cast<const rosidl_action_type_support_t *>(get_typesupport_handle_impl(
|
||||
type, typesupport_identifier, typesupport_name, symbol_part_name,
|
||||
middle_module_additional, library
|
||||
));
|
||||
}
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -1 +1 @@
|
||||
string data
|
||||
string data
|
||||
|
||||
@@ -62,6 +62,11 @@ ament_add_test_label(test_clock mimick)
|
||||
if(TARGET test_clock)
|
||||
target_link_libraries(test_clock ${PROJECT_NAME} mimick ${rcl_interfaces_TARGETS} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
ament_add_gtest(test_clock_conditional test_clock_conditional.cpp)
|
||||
ament_add_test_label(test_clock_conditional mimick)
|
||||
if(TARGET test_clock_conditional)
|
||||
target_link_libraries(test_clock_conditional ${PROJECT_NAME} mimick ${rcl_interfaces_TARGETS} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
ament_add_gtest(test_copy_all_parameter_values test_copy_all_parameter_values.cpp)
|
||||
if(TARGET test_copy_all_parameter_values)
|
||||
target_link_libraries(test_copy_all_parameter_values ${PROJECT_NAME})
|
||||
@@ -669,6 +674,14 @@ endif()
|
||||
function(test_on_all_rmws)
|
||||
set(rmw_implementation_env_var RMW_IMPLEMENTATION=${rmw_implementation})
|
||||
|
||||
# If the rmw_implementation is rmw_zenoh_cpp, run the tests with multicast discovery enabled.
|
||||
# Note: This is a temporary change that will be reverted before we branch into Kilted.
|
||||
if(rmw_implementation STREQUAL "rmw_zenoh_cpp")
|
||||
list(APPEND rmw_implementation_env_var
|
||||
ZENOH_CONFIG_OVERRIDE=scouting/multicast/enabled=true
|
||||
)
|
||||
endif()
|
||||
|
||||
ament_add_gmock_test(test_qos_event
|
||||
TEST_NAME test_qos_event${target_suffix}
|
||||
ENV ${rmw_implementation_env_var}
|
||||
|
||||
@@ -61,9 +61,8 @@ class ExecutorTypeNames
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
static std::string GetName(int idx)
|
||||
static std::string GetName([[maybe_unused]] int idx)
|
||||
{
|
||||
(void)idx;
|
||||
if (std::is_same<T, rclcpp::executors::SingleThreadedExecutor>()) {
|
||||
return "SingleThreadedExecutor";
|
||||
}
|
||||
|
||||
@@ -48,9 +48,8 @@ TEST_F(TestEventsExecutor, run_pub_sub)
|
||||
bool msg_received = false;
|
||||
auto subscription = node->create_subscription<test_msgs::msg::Empty>(
|
||||
"topic", rclcpp::SensorDataQoS(),
|
||||
[&msg_received](test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
[&msg_received]([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
{
|
||||
(void)msg;
|
||||
msg_received = true;
|
||||
});
|
||||
|
||||
@@ -115,8 +114,8 @@ TEST_F(TestEventsExecutor, run_clients_servers)
|
||||
auto request = std::make_shared<test_msgs::srv::Empty::Request>();
|
||||
client->async_send_request(
|
||||
request,
|
||||
[&response_received](rclcpp::Client<test_msgs::srv::Empty>::SharedFuture result_future) {
|
||||
(void)result_future;
|
||||
[&response_received]([[maybe_unused]] rclcpp::Client<test_msgs::srv::Empty>::SharedFuture
|
||||
result_future){
|
||||
response_received = true;
|
||||
});
|
||||
|
||||
@@ -497,3 +496,122 @@ TEST_F(TestEventsExecutor, test_default_incompatible_qos_callbacks)
|
||||
|
||||
rcutils_logging_set_output_handler(original_output_handler);
|
||||
}
|
||||
|
||||
class TestWaitableWithTimer : public rclcpp::Waitable
|
||||
{
|
||||
static constexpr int TimerCallbackType = 0;
|
||||
|
||||
public:
|
||||
explicit TestWaitableWithTimer(const rclcpp::Context::SharedPtr & context)
|
||||
{
|
||||
auto timer_callback = [this] () {
|
||||
timer_ready = true;
|
||||
if (ready_callback) {
|
||||
// inform executor that the waitable is ready to process a timer event
|
||||
ready_callback(1, TimerCallbackType);
|
||||
}
|
||||
};
|
||||
timer =
|
||||
std::make_shared<rclcpp::WallTimer<decltype (timer_callback)>>(std::chrono::milliseconds(10),
|
||||
std::move(timer_callback), context);
|
||||
}
|
||||
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & /*wait_set*/) override {}
|
||||
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & /*wait_set*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override
|
||||
{
|
||||
return std::shared_ptr<void>(nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override
|
||||
{
|
||||
if (id != TimerCallbackType) {
|
||||
throw std::runtime_error("Internal error, got wrong ID for take data");
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
execute(const std::shared_ptr<void> &) override
|
||||
{
|
||||
if (timer_ready) {
|
||||
timer_triggered_waitable_and_waitable_was_executed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
set_on_ready_callback(std::function<void(size_t, int)> callback) override
|
||||
{
|
||||
ready_callback = callback;
|
||||
}
|
||||
|
||||
void
|
||||
clear_on_ready_callback() override
|
||||
{
|
||||
ready_callback = std::function<void(size_t, int)>();
|
||||
}
|
||||
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {timer};
|
||||
}
|
||||
|
||||
bool timerTriggeredWaitable() const
|
||||
{
|
||||
return timer_triggered_waitable_and_waitable_was_executed;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_bool timer_triggered_waitable_and_waitable_was_executed = false;
|
||||
std::atomic_bool timer_ready = false;
|
||||
rclcpp::TimerBase::SharedPtr timer;
|
||||
std::function<void(size_t, int)> ready_callback;
|
||||
};
|
||||
|
||||
TEST_F(TestEventsExecutor, waitable_with_timer)
|
||||
{
|
||||
auto node = std::make_shared<rclcpp::Node>("node");
|
||||
|
||||
auto waitable =
|
||||
std::make_shared<TestWaitableWithTimer>(rclcpp::contexts::get_global_default_context());
|
||||
|
||||
EventsExecutor executor;
|
||||
executor.add_node(node);
|
||||
|
||||
node->get_node_waitables_interface()->add_waitable(waitable,
|
||||
node->get_node_base_interface()->get_default_callback_group());
|
||||
|
||||
std::thread spinner([&executor]() {executor.spin();});
|
||||
|
||||
size_t cnt = 0;
|
||||
while (!waitable->timerTriggeredWaitable()) {
|
||||
std::this_thread::sleep_for(10ms);
|
||||
cnt++;
|
||||
|
||||
// This should terminate after ~20 ms, so a timeout of 500ms should be plenty
|
||||
EXPECT_LT(cnt, 51);
|
||||
if (cnt > 50) {
|
||||
// timeout case
|
||||
break;
|
||||
}
|
||||
}
|
||||
executor.cancel();
|
||||
spinner.join();
|
||||
|
||||
EXPECT_TRUE(waitable->timerTriggeredWaitable());
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ TEST_F(TestExecutorNotifyWaitable, wait) {
|
||||
std::make_shared<rclcpp::executors::ExecutorNotifyWaitable>(on_execute_callback);
|
||||
|
||||
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
|
||||
auto notify_guard_condition =
|
||||
node->get_node_base_interface()->get_shared_notify_guard_condition();
|
||||
auto notify_guard_condition = std::make_shared<rclcpp::GuardCondition>();
|
||||
EXPECT_NO_THROW(waitable->add_guard_condition(notify_guard_condition));
|
||||
|
||||
auto default_cbg = node->get_node_base_interface()->get_default_callback_group();
|
||||
@@ -86,12 +85,15 @@ TEST_F(TestExecutorNotifyWaitable, wait) {
|
||||
auto waitables = node->get_node_waitables_interface();
|
||||
waitables->add_waitable(std::static_pointer_cast<rclcpp::Waitable>(waitable), default_cbg);
|
||||
|
||||
// notify the guard condition, this should trigger the on_execute_callback
|
||||
notify_guard_condition->trigger();
|
||||
|
||||
rclcpp::executors::SingleThreadedExecutor executor;
|
||||
executor.add_node(node);
|
||||
executor.spin_all(std::chrono::seconds(1));
|
||||
EXPECT_EQ(1u, on_execute_calls);
|
||||
|
||||
// on_execute_callback doesn't change if the topology doesn't change
|
||||
// no further trigger, therefore no further callback
|
||||
executor.spin_all(std::chrono::seconds(1));
|
||||
EXPECT_EQ(1u, on_execute_calls);
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ to_nanoseconds_helper(DurationT duration)
|
||||
// - works nominally (it can execute entities)
|
||||
// - it can execute multiple items at once
|
||||
// - it does not wait for work to be available before returning
|
||||
TYPED_TEST(TestExecutors, spin_some)
|
||||
TYPED_TEST(TestExecutors, spinSome)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
@@ -484,7 +484,7 @@ TYPED_TEST(TestExecutors, spin_some)
|
||||
// do not properly implement max_duration (it seems), so disable this test
|
||||
// for them in the meantime.
|
||||
// see: https://github.com/ros2/rclcpp/issues/2462
|
||||
TYPED_TEST(TestExecutorsStable, spin_some_max_duration)
|
||||
TYPED_TEST(TestExecutorsStable, spinSomeMaxDuration)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
@@ -714,6 +714,13 @@ TYPED_TEST(TestExecutors, notifyTwiceWhileSpinning)
|
||||
sub1_msg_count++;
|
||||
});
|
||||
|
||||
// Wait for the subscription to be matched
|
||||
size_t tries = 10000;
|
||||
while (this->publisher->get_subscription_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(this->publisher->get_subscription_count(), 2);
|
||||
|
||||
// Publish a message and verify it's received
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
@@ -731,12 +738,18 @@ TYPED_TEST(TestExecutors, notifyTwiceWhileSpinning)
|
||||
sub2_msg_count++;
|
||||
});
|
||||
|
||||
// Wait for the subscription to be matched
|
||||
tries = 10000;
|
||||
while (this->publisher->get_subscription_count() < 3 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(this->publisher->get_subscription_count(), 3);
|
||||
|
||||
// Publish a message and verify it's received by both subscriptions
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
start = std::chrono::steady_clock::now();
|
||||
while (
|
||||
sub1_msg_count == 1 &&
|
||||
sub2_msg_count == 0 &&
|
||||
(sub1_msg_count == 1 || sub2_msg_count == 0) &&
|
||||
(std::chrono::steady_clock::now() - start) < 10s)
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
@@ -820,7 +833,7 @@ TEST(TestExecutors, testSpinWithNonDefaultContext)
|
||||
rclcpp::shutdown(non_default_context);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, release_ownership_entity_after_spinning_cancel)
|
||||
TYPED_TEST(TestExecutors, releaseOwnershipEntityAfterSpinningCancel)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
@@ -843,3 +856,309 @@ TYPED_TEST(TestExecutors, release_ownership_entity_after_spinning_cancel)
|
||||
|
||||
EXPECT_EQ(server.use_count(), 1);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, testRaceDropCallbackGroupFromSecondThread)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// Create an executor
|
||||
ExecutorType executor;
|
||||
executor.add_node(this->node);
|
||||
|
||||
// Start spinning
|
||||
auto executor_thread = std::thread(
|
||||
[&executor]() {
|
||||
executor.spin();
|
||||
});
|
||||
|
||||
// As the problem is a race, we do this multiple times,
|
||||
// to raise our chances of hitting the problem
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
auto cg = this->node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive);
|
||||
auto timer = this->node->create_timer(1s, [] {}, cg);
|
||||
// sleep a bit, so that the spin thread can pick up the callback group
|
||||
// and add it to the executor
|
||||
std::this_thread::sleep_for(5ms);
|
||||
|
||||
// At this point the callbackgroup should be used within the waitset of the executor
|
||||
// as we leave the scope, the reference to cg will be dropped.
|
||||
// If the executor has a race, we will experience a segfault at this point.
|
||||
}
|
||||
|
||||
executor.cancel();
|
||||
executor_thread.join();
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, dropSomeTimer)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node");
|
||||
|
||||
bool timer1_works = false;
|
||||
bool timer2_works = false;
|
||||
|
||||
auto timer1 = node->create_timer(std::chrono::milliseconds(10), [&timer1_works]() {
|
||||
timer1_works = true;
|
||||
});
|
||||
auto timer2 = node->create_timer(std::chrono::milliseconds(10), [&timer2_works]() {
|
||||
timer2_works = true;
|
||||
});
|
||||
|
||||
executor.add_node(node);
|
||||
|
||||
// first let's make sure that both timers work
|
||||
auto max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!timer1_works || !timer2_works) {
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
// delete timer 2. Note, the executor uses an unordered map internally, to order
|
||||
// the entities added to the rcl waitset therefore the order is kind of undefined,
|
||||
// and this test may be flaky. In case it triggers, something is most likely
|
||||
// really broken.
|
||||
timer2.reset();
|
||||
|
||||
timer1_works = false;
|
||||
timer2_works = false;
|
||||
max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!timer1_works && !timer2_works) {
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(timer1_works || timer2_works);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, dropSomeNodeWithTimer)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
auto node1 = std::make_shared<rclcpp::Node>("test_node_1");
|
||||
auto node2 = std::make_shared<rclcpp::Node>("test_node_2");
|
||||
|
||||
bool timer1_works = false;
|
||||
bool timer2_works = false;
|
||||
|
||||
auto timer1 = node1->create_timer(std::chrono::milliseconds(10), [&timer1_works]() {
|
||||
timer1_works = true;
|
||||
});
|
||||
auto timer2 = node2->create_timer(std::chrono::milliseconds(10), [&timer2_works]() {
|
||||
timer2_works = true;
|
||||
});
|
||||
|
||||
executor.add_node(node1);
|
||||
executor.add_node(node2);
|
||||
|
||||
// first let's make sure that both timers work
|
||||
auto max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!timer1_works || !timer2_works) {
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
// delete node 1.
|
||||
node1 = nullptr;
|
||||
|
||||
timer2_works = false;
|
||||
max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!timer2_works) {
|
||||
// let the executor pick up the node and the timer
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(timer2_works);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, dropSomeSubscription)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node");
|
||||
|
||||
bool sub1_works = false;
|
||||
bool sub2_works = false;
|
||||
|
||||
auto sub1 = node->create_subscription<test_msgs::msg::Empty>("/test_drop", 10,
|
||||
[&sub1_works](const test_msgs::msg::Empty &) {
|
||||
sub1_works = true;
|
||||
});
|
||||
auto sub2 = node->create_subscription<test_msgs::msg::Empty>("/test_drop", 10,
|
||||
[&sub2_works](const test_msgs::msg::Empty &) {
|
||||
sub2_works = true;
|
||||
});
|
||||
|
||||
auto pub = node->create_publisher<test_msgs::msg::Empty>("/test_drop", 10);
|
||||
|
||||
executor.add_node(node);
|
||||
|
||||
// first let's make sure that both timers work
|
||||
auto max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!sub1_works || !sub2_works) {
|
||||
pub->publish(test_msgs::msg::Empty());
|
||||
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
// delete subscription 2. Note, the executor uses an unordered map internally, to order
|
||||
// the entities added to the rcl waitset therefore the order is kind of undefined,
|
||||
// and this test may be flaky. In case it triggers, something is most likely
|
||||
// really broken.
|
||||
sub2.reset();
|
||||
|
||||
sub1_works = false;
|
||||
sub2_works = false;
|
||||
max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!sub1_works && !sub2_works) {
|
||||
pub->publish(test_msgs::msg::Empty());
|
||||
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(sub1_works || sub2_works);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, dropSomeNodesWithSubscription)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node");
|
||||
auto node1 = std::make_shared<rclcpp::Node>("test_node_1");
|
||||
auto node2 = std::make_shared<rclcpp::Node>("test_node_2");
|
||||
|
||||
bool sub1_works = false;
|
||||
bool sub2_works = false;
|
||||
|
||||
auto sub1 = node1->create_subscription<test_msgs::msg::Empty>("/test_drop", 10,
|
||||
[&sub1_works](const test_msgs::msg::Empty &) {
|
||||
sub1_works = true;
|
||||
});
|
||||
auto sub2 = node2->create_subscription<test_msgs::msg::Empty>("/test_drop", 10,
|
||||
[&sub2_works](const test_msgs::msg::Empty &) {
|
||||
sub2_works = true;
|
||||
});
|
||||
|
||||
auto pub = node->create_publisher<test_msgs::msg::Empty>("/test_drop", 10);
|
||||
|
||||
executor.add_node(node);
|
||||
executor.add_node(node1);
|
||||
executor.add_node(node2);
|
||||
|
||||
// first let's make sure that both subscribers work
|
||||
auto max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!sub1_works || !sub2_works) {
|
||||
pub->publish(test_msgs::msg::Empty());
|
||||
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
// delete node 2.
|
||||
node2 = nullptr;
|
||||
|
||||
sub1_works = false;
|
||||
max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while(!sub1_works) {
|
||||
pub->publish(test_msgs::msg::Empty());
|
||||
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(sub1_works);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, dropSubscriptionDuringCallback)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
|
||||
auto node = std::make_shared<rclcpp::Node>("test_node");
|
||||
|
||||
bool sub1_works = false;
|
||||
bool sub2_works = false;
|
||||
|
||||
auto cbg = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, true);
|
||||
rclcpp::SubscriptionOptions sub_ops;
|
||||
sub_ops.callback_group = cbg;
|
||||
|
||||
rclcpp::SubscriptionBase::SharedPtr sub1;
|
||||
rclcpp::SubscriptionBase::SharedPtr sub2;
|
||||
|
||||
// Note, the executor uses an unordered map internally, to order
|
||||
// the entities added to the rcl waitset therefore the order of the subscriptions
|
||||
// is kind of undefined. Therefore each sub deletes the other one.
|
||||
sub1 = node->create_subscription<test_msgs::msg::Empty>("/test_drop", 10,
|
||||
[&sub1_works, &sub2](const test_msgs::msg::Empty &) {
|
||||
sub1_works = true;
|
||||
// delete the other subscriber
|
||||
sub2.reset();
|
||||
}, sub_ops);
|
||||
sub2 = node->create_subscription<test_msgs::msg::Empty>("/test_drop", 10,
|
||||
[&sub2_works, &sub1](const test_msgs::msg::Empty &) {
|
||||
sub2_works = true;
|
||||
// delete the other subscriber
|
||||
sub1.reset();
|
||||
}, sub_ops);
|
||||
|
||||
auto pub = node->create_publisher<test_msgs::msg::Empty>("/test_drop", 10);
|
||||
|
||||
// wait for both subs to be connected
|
||||
auto max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(1500);
|
||||
while ((sub1->get_publisher_count() == 0) || (sub2->get_publisher_count() == 0)) {
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
executor.add_node(node);
|
||||
|
||||
// publish some messages, until one subscriber fired. As both subscribers are
|
||||
// connected to the same topic, they should fire in the same wait.
|
||||
max_end_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
|
||||
while (!sub1_works && !sub2_works) {
|
||||
pub->publish(test_msgs::msg::Empty());
|
||||
|
||||
// let the executor pick up the node and the timers
|
||||
executor.spin_all(std::chrono::milliseconds(10));
|
||||
|
||||
const auto cur_time = std::chrono::steady_clock::now();
|
||||
ASSERT_LT(cur_time, max_end_time);
|
||||
}
|
||||
|
||||
// only one subscriber must have worked, as the other
|
||||
// one was deleted during the callback
|
||||
ASSERT_TRUE(!sub1_works || !sub2_works);
|
||||
}
|
||||
|
||||
@@ -267,12 +267,15 @@ public:
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
node.reset();
|
||||
executor.cancel();
|
||||
|
||||
// Clean up thread object
|
||||
if (standalone_thread.joinable()) {
|
||||
standalone_thread.join();
|
||||
}
|
||||
|
||||
node.reset();
|
||||
sim_clock_node.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<TimerNode> node;
|
||||
@@ -310,7 +313,6 @@ TYPED_TEST(TestTimerCancelBehavior, testTimer1CancelledWithExecutorSpin) {
|
||||
this->sim_clock_node->sleep_for(50ms);
|
||||
this->node->CancelTimer1();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
this->executor.cancel();
|
||||
|
||||
int t1_runs = this->node->GetTimer1Cnt();
|
||||
int t2_runs = this->node->GetTimer2Cnt();
|
||||
@@ -328,7 +330,6 @@ TYPED_TEST(TestTimerCancelBehavior, testTimer2CancelledWithExecutorSpin) {
|
||||
this->sim_clock_node->sleep_for(50ms);
|
||||
this->node->CancelTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
this->executor.cancel();
|
||||
|
||||
int t1_runs = this->node->GetTimer1Cnt();
|
||||
int t2_runs = this->node->GetTimer2Cnt();
|
||||
@@ -355,8 +356,6 @@ TYPED_TEST(TestTimerCancelBehavior, testHeadTimerCancelThenResetBehavior) {
|
||||
int t1_runs_final = this->node->GetTimer1Cnt();
|
||||
int t2_runs_final = this->node->GetTimer2Cnt();
|
||||
|
||||
this->executor.cancel();
|
||||
|
||||
// T1 should have been restarted, and execute about 15 additional times.
|
||||
// Check 10 greater than initial, to account for some timing jitter.
|
||||
EXPECT_LT(t1_runs_initial + 50, t1_runs_final);
|
||||
@@ -384,8 +383,6 @@ TYPED_TEST(TestTimerCancelBehavior, testBackTimerCancelThenResetBehavior) {
|
||||
int t1_runs_final = this->node->GetTimer1Cnt();
|
||||
int t2_runs_final = this->node->GetTimer2Cnt();
|
||||
|
||||
this->executor.cancel();
|
||||
|
||||
// T2 should have been restarted, and execute about 15 additional times.
|
||||
// Check 10 greater than initial, to account for some timing jitter.
|
||||
EXPECT_LT(t2_runs_initial + 50, t2_runs_final);
|
||||
@@ -419,8 +416,6 @@ TYPED_TEST(TestTimerCancelBehavior, testBothTimerCancelThenResetT1Behavior) {
|
||||
int t1_runs_final = this->node->GetTimer1Cnt();
|
||||
int t2_runs_final = this->node->GetTimer2Cnt();
|
||||
|
||||
this->executor.cancel();
|
||||
|
||||
// T1 and T2 should have the same initial count.
|
||||
EXPECT_LE(std::abs(t1_runs_initial - t2_runs_initial), 1);
|
||||
|
||||
@@ -458,8 +453,6 @@ TYPED_TEST(TestTimerCancelBehavior, testBothTimerCancelThenResetT2Behavior) {
|
||||
int t1_runs_final = this->node->GetTimer1Cnt();
|
||||
int t2_runs_final = this->node->GetTimer2Cnt();
|
||||
|
||||
this->executor.cancel();
|
||||
|
||||
// T1 and T2 should have the same initial count.
|
||||
EXPECT_LE(std::abs(t1_runs_initial - t2_runs_initial), 1);
|
||||
|
||||
|
||||
@@ -99,14 +99,6 @@ TYPED_TEST(TestExecutorsWarmup, spin_all_doesnt_require_warmup_with_cbgroup)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// TODO(alsora): Enable when https://github.com/ros2/rclcpp/pull/2595 gets merged
|
||||
if (
|
||||
std::is_same<ExecutorType, rclcpp::executors::SingleThreadedExecutor>() ||
|
||||
std::is_same<ExecutorType, rclcpp::executors::MultiThreadedExecutor>())
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
ExecutorType executor;
|
||||
|
||||
// Enable intra-process to guarantee deterministic and synchronous delivery of the message / event
|
||||
|
||||
@@ -64,9 +64,8 @@ TestWaitable::take_data()
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
TestWaitable::take_data_by_entity_id(size_t id)
|
||||
TestWaitable::take_data_by_entity_id([[maybe_unused]] size_t id)
|
||||
{
|
||||
(void) id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/waitable.hpp"
|
||||
#include "rclcpp/guard_condition.hpp"
|
||||
@@ -64,6 +65,11 @@ public:
|
||||
size_t
|
||||
get_is_ready_call_count() const;
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<size_t> trigger_count_ = 0;
|
||||
std::atomic<size_t> is_ready_count_ = 0;
|
||||
|
||||
@@ -38,6 +38,11 @@ public:
|
||||
void clear_on_ready_callback() override {}
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class TestNodeWaitables : public ::testing::Test
|
||||
|
||||
@@ -58,6 +58,11 @@ public:
|
||||
void clear_on_ready_callback() override {}
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct RclWaitSetSizes
|
||||
@@ -535,8 +540,8 @@ TEST_F(TestAllocatorMemoryStrategy, add_handles_to_wait_set_bad_arguments) {
|
||||
});
|
||||
allocator_memory_strategy()->collect_entities(weak_groups_to_nodes);
|
||||
EXPECT_FALSE(allocator_memory_strategy()->add_handles_to_wait_set(nullptr));
|
||||
EXPECT_TRUE(rcl_error_is_set());
|
||||
rcl_reset_error();
|
||||
// The error message is collected and already reset.
|
||||
EXPECT_FALSE(rcl_error_is_set());
|
||||
}
|
||||
|
||||
TEST_F(TestAllocatorMemoryStrategy, add_handles_to_wait_set_subscription) {
|
||||
|
||||
@@ -184,9 +184,8 @@ class ClientTypeNames
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
static std::string GetName(int idx)
|
||||
static std::string GetName([[maybe_unused]] int idx)
|
||||
{
|
||||
(void)idx;
|
||||
if (std::is_same_v<T, rclcpp::Client<test_msgs::srv::Empty>>) {
|
||||
return "Client";
|
||||
}
|
||||
@@ -431,7 +430,7 @@ TYPED_TEST(TestAllClientTypesWithServer, client_qos)
|
||||
|
||||
rclcpp::ServicesQoS qos_profile;
|
||||
qos_profile.liveliness(rclcpp::LivelinessPolicy::Automatic);
|
||||
rclcpp::Duration duration(std::chrono::nanoseconds(1));
|
||||
rclcpp::Duration duration(std::chrono::milliseconds(1));
|
||||
qos_profile.deadline(duration);
|
||||
qos_profile.lifespan(duration);
|
||||
qos_profile.liveliness_lease_duration(duration);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <rcpputils/compile_warnings.hpp>
|
||||
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/time.h"
|
||||
@@ -41,9 +42,10 @@ public:
|
||||
clock->sleep_until(clock->now() + std::chrono::seconds(3));
|
||||
thread_finished = true;
|
||||
});
|
||||
|
||||
RCPPUTILS_DEPRECATION_WARNING_OFF_START
|
||||
// notify the clock, that the sleep shall be interrupted
|
||||
clock->cancel_sleep_or_wait();
|
||||
RCPPUTILS_DEPRECATION_WARNING_OFF_STOP
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
@@ -72,8 +74,10 @@ public:
|
||||
// make sure the thread is already sleeping before we send the cancel
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
RCPPUTILS_DEPRECATION_WARNING_OFF_START
|
||||
// notify the clock, that the sleep shall be interrupted
|
||||
clock->cancel_sleep_or_wait();
|
||||
RCPPUTILS_DEPRECATION_WARNING_OFF_STOP
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
@@ -159,8 +163,10 @@ TEST_F(TestClockWakeup, no_wakeup_on_sim_time) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
EXPECT_FALSE(thread_finished);
|
||||
|
||||
RCPPUTILS_DEPRECATION_WARNING_OFF_START
|
||||
// notify the clock, that the sleep shall be interrupted
|
||||
clock->cancel_sleep_or_wait();
|
||||
RCPPUTILS_DEPRECATION_WARNING_OFF_STOP
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
|
||||
246
rclcpp/test/rclcpp/test_clock_conditional.cpp
Normal file
246
rclcpp/test/rclcpp/test_clock_conditional.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2025 Cellumation GmbH
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/time.h"
|
||||
#include "rclcpp/clock.hpp"
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
#include "rclcpp/time_source.hpp"
|
||||
|
||||
#include "../utils/rclcpp_gtest_macros.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class TestClockWakeup : public ::testing::TestWithParam<rcl_clock_type_e>
|
||||
{
|
||||
public:
|
||||
void test_wakeup_before_sleep(const rclcpp::Clock::SharedPtr & clock)
|
||||
{
|
||||
std::atomic_bool thread_finished = false;
|
||||
rclcpp::ClockConditionalVariable cond(clock);
|
||||
|
||||
bool stopSleeping = false;
|
||||
|
||||
std::thread wait_thread = std::thread(
|
||||
[&cond, &clock, &stopSleeping, &thread_finished]()
|
||||
{
|
||||
// make sure the thread starts sleeping late
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::unique_lock lk(cond.mutex());
|
||||
cond.wait_until(lk, clock->now() + std::chrono::seconds(3),
|
||||
[&stopSleeping] () {return stopSleeping;});
|
||||
thread_finished = true;
|
||||
});
|
||||
|
||||
{
|
||||
std::lock_guard lk(cond.mutex());
|
||||
// stop sleeping after next notification
|
||||
stopSleeping = true;
|
||||
}
|
||||
// notify the conditional, to recheck it pred
|
||||
cond.notify_one();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
cur_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
wait_thread.join();
|
||||
|
||||
EXPECT_TRUE(thread_finished);
|
||||
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
void test_wakeup_after_sleep(const rclcpp::Clock::SharedPtr & clock)
|
||||
{
|
||||
std::atomic_bool thread_finished = false;
|
||||
rclcpp::ClockConditionalVariable cond(clock);
|
||||
|
||||
bool stopSleeping = false;
|
||||
|
||||
std::thread wait_thread = std::thread(
|
||||
[&cond, &clock, &stopSleeping, &thread_finished]()
|
||||
{
|
||||
std::unique_lock lk(cond.mutex());
|
||||
cond.wait_until(lk, clock->now() + std::chrono::seconds(3),
|
||||
[&stopSleeping] () {return stopSleeping;});
|
||||
thread_finished = true;
|
||||
});
|
||||
|
||||
// make sure the thread is already sleeping before we send the cancel
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
{
|
||||
std::lock_guard lk(cond.mutex());
|
||||
// stop sleeping after next notification
|
||||
stopSleeping = true;
|
||||
}
|
||||
// notify the conditional, to recheck it pred
|
||||
cond.notify_one();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
cur_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
wait_thread.join();
|
||||
|
||||
EXPECT_TRUE(thread_finished);
|
||||
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
protected:
|
||||
static void SetUpTestCase()
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
}
|
||||
|
||||
static void TearDownTestCase()
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void SetUp()
|
||||
{
|
||||
node = std::make_shared<rclcpp::Node>("my_node");
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
node.reset();
|
||||
}
|
||||
|
||||
rclcpp::Node::SharedPtr node;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
ClockConditionalVariable,
|
||||
TestClockWakeup,
|
||||
::testing::Values(
|
||||
RCL_SYSTEM_TIME, RCL_ROS_TIME, RCL_STEADY_TIME
|
||||
));
|
||||
|
||||
TEST_P(TestClockWakeup, wakeup_sleep) {
|
||||
auto clock = std::make_shared<rclcpp::Clock>(GetParam());
|
||||
test_wakeup_after_sleep(clock);
|
||||
test_wakeup_before_sleep(clock);
|
||||
}
|
||||
|
||||
TEST_F(TestClockWakeup, wakeup_sleep_ros_time_active) {
|
||||
node->set_parameter({"use_sim_time", true});
|
||||
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
|
||||
rclcpp::TimeSource time_source(node);
|
||||
time_source.attachClock(clock);
|
||||
|
||||
EXPECT_TRUE(clock->ros_time_is_active());
|
||||
|
||||
test_wakeup_after_sleep(clock);
|
||||
test_wakeup_before_sleep(clock);
|
||||
}
|
||||
|
||||
TEST_F(TestClockWakeup, no_wakeup_on_sim_time) {
|
||||
node->set_parameter({"use_sim_time", true});
|
||||
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
|
||||
EXPECT_FALSE(clock->ros_time_is_active());
|
||||
|
||||
rclcpp::TimeSource time_source(node);
|
||||
time_source.attachClock(clock);
|
||||
EXPECT_TRUE(clock->ros_time_is_active());
|
||||
|
||||
std::atomic_bool thread_finished = false;
|
||||
rclcpp::ClockConditionalVariable cond(clock);
|
||||
|
||||
bool stopSleeping = false;
|
||||
|
||||
std::thread wait_thread = std::thread(
|
||||
[&cond, &clock, &stopSleeping, &thread_finished]()
|
||||
{
|
||||
std::unique_lock lk(cond.mutex());
|
||||
// only sleep for an short period
|
||||
cond.wait_until(lk, clock->now() + std::chrono::milliseconds(10),
|
||||
[&stopSleeping] () {return stopSleeping;});
|
||||
thread_finished = true;
|
||||
});
|
||||
|
||||
// make sure, that the sim time clock does not wakeup, as no clock is provided
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
EXPECT_FALSE(thread_finished);
|
||||
|
||||
{
|
||||
std::lock_guard lk(cond.mutex());
|
||||
// stop sleeping after next notification
|
||||
stopSleeping = true;
|
||||
}
|
||||
// notify the conditional, to recheck it pred
|
||||
cond.notify_one();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
cur_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
wait_thread.join();
|
||||
|
||||
EXPECT_TRUE(thread_finished);
|
||||
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
TEST_F(TestClockWakeup, wakeup_on_ros_shutdown) {
|
||||
auto clock = std::make_shared<rclcpp::Clock>(RCL_ROS_TIME);
|
||||
|
||||
std::atomic_bool thread_finished = false;
|
||||
rclcpp::ClockConditionalVariable cond(clock);
|
||||
|
||||
bool stopSleeping = false;
|
||||
|
||||
std::thread wait_thread = std::thread(
|
||||
[&cond, &clock, &stopSleeping, &thread_finished]()
|
||||
{
|
||||
std::unique_lock lk(cond.mutex());
|
||||
// only sleep for an short period
|
||||
cond.wait_until(lk, clock->now() + std::chrono::seconds(10),
|
||||
[&stopSleeping] () {return stopSleeping;});
|
||||
thread_finished = true;
|
||||
});
|
||||
|
||||
// wait a bit to be sure the thread is sleeping
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
EXPECT_FALSE(thread_finished);
|
||||
|
||||
rclcpp::shutdown();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
auto cur_time = start_time;
|
||||
while (!thread_finished && start_time + std::chrono::seconds(1) > cur_time) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
cur_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
EXPECT_TRUE(thread_finished);
|
||||
|
||||
wait_thread.join();
|
||||
|
||||
EXPECT_LT(cur_time, start_time + std::chrono::seconds(1));
|
||||
}
|
||||
@@ -92,3 +92,19 @@ TEST_F(TestCreateSubscription, create_with_statistics) {
|
||||
ASSERT_NE(nullptr, subscription);
|
||||
EXPECT_STREQ("/ns/topic_name", subscription->get_topic_name());
|
||||
}
|
||||
|
||||
TEST_F(TestCreateSubscription, create_with_intra_process_com) {
|
||||
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns");
|
||||
auto options = rclcpp::SubscriptionOptions();
|
||||
options.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
|
||||
auto callback = [](test_msgs::msg::Empty::ConstSharedPtr) {};
|
||||
rclcpp::Subscription<test_msgs::msg::Empty>::SharedPtr subscription;
|
||||
ASSERT_NO_THROW(
|
||||
{
|
||||
subscription = rclcpp::create_subscription<test_msgs::msg::Empty>(
|
||||
node, "topic_name", rclcpp::SystemDefaultsQoS(), callback, options);
|
||||
});
|
||||
ASSERT_NE(nullptr, subscription);
|
||||
EXPECT_STREQ("/ns/topic_name", subscription->get_topic_name());
|
||||
}
|
||||
|
||||
@@ -74,29 +74,23 @@ struct FunctionObjectOneIntOneChar
|
||||
|
||||
struct ObjectMember
|
||||
{
|
||||
int callback_one_bool(bool a)
|
||||
int callback_one_bool([[maybe_unused]] bool a)
|
||||
{
|
||||
(void)a;
|
||||
return 7;
|
||||
}
|
||||
|
||||
int callback_one_bool_const(bool a) const
|
||||
int callback_one_bool_const([[maybe_unused]] bool a) const
|
||||
{
|
||||
(void)a;
|
||||
return 7;
|
||||
}
|
||||
|
||||
int callback_two_bools(bool a, bool b)
|
||||
int callback_two_bools([[maybe_unused]] bool a, [[maybe_unused]] bool b)
|
||||
{
|
||||
(void)a;
|
||||
(void)b;
|
||||
return 8;
|
||||
}
|
||||
|
||||
int callback_one_bool_one_float(bool a, float b)
|
||||
int callback_one_bool_one_float([[maybe_unused]] bool a, [[maybe_unused]] float b)
|
||||
{
|
||||
(void)a;
|
||||
(void)b;
|
||||
return 9;
|
||||
}
|
||||
};
|
||||
@@ -212,20 +206,15 @@ TEST(TestFunctionTraits, arity) {
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto lambda_one_int = [](int one) {
|
||||
(void)one;
|
||||
auto lambda_one_int = []([[maybe_unused]] int one) {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto lambda_two_ints = [](int one, int two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_two_ints = []([[maybe_unused]] int one, [[maybe_unused]] int two) {
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto lambda_one_int_one_char = [](int one, char two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_one_int_one_char = []([[maybe_unused]] int one, [[maybe_unused]] char two) {
|
||||
return 3;
|
||||
};
|
||||
|
||||
@@ -303,20 +292,15 @@ TEST(TestFunctionTraits, argument_types) {
|
||||
>::value, "Functor accepts a char as second argument");
|
||||
|
||||
// Test lambdas
|
||||
auto lambda_one_int = [](int one) {
|
||||
(void)one;
|
||||
auto lambda_one_int = []([[maybe_unused]] int one) {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto lambda_two_ints = [](int one, int two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_two_ints = []([[maybe_unused]] int one, [[maybe_unused]] int two) {
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto lambda_one_int_one_char = [](int one, char two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_one_int_one_char = []([[maybe_unused]] int one, [[maybe_unused]] char two) {
|
||||
return 3;
|
||||
};
|
||||
|
||||
@@ -533,22 +517,17 @@ TEST(TestFunctionTraits, check_arguments) {
|
||||
"Functor accepts an int and a char as arguments");
|
||||
|
||||
// Test lambdas
|
||||
auto lambda_one_int = [](int one) {
|
||||
(void)one;
|
||||
auto lambda_one_int = []([[maybe_unused]] int one) {
|
||||
return 1;
|
||||
};
|
||||
(void)lambda_one_int; // to quiet clang
|
||||
|
||||
auto lambda_two_ints = [](int one, int two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_two_ints = []([[maybe_unused]] int one, [[maybe_unused]] int two) {
|
||||
return 2;
|
||||
};
|
||||
(void)lambda_two_ints; // to quiet clang
|
||||
|
||||
auto lambda_one_int_one_char = [](int one, char two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_one_int_one_char = []([[maybe_unused]] int one, [[maybe_unused]] char two) {
|
||||
return 3;
|
||||
};
|
||||
(void)lambda_one_int_one_char; // to quiet clang
|
||||
@@ -603,14 +582,11 @@ TEST(TestFunctionTraits, check_arguments) {
|
||||
Tests that same_arguments work.
|
||||
*/
|
||||
TEST(TestFunctionTraits, same_arguments) {
|
||||
auto lambda_one_int = [](int one) {
|
||||
(void)one;
|
||||
auto lambda_one_int = []([[maybe_unused]] int one) {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto lambda_two_ints = [](int one, int two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_two_ints = []([[maybe_unused]] int one, [[maybe_unused]] int two) {
|
||||
return 1;
|
||||
};
|
||||
|
||||
@@ -658,8 +634,7 @@ TEST(TestFunctionTraits, return_type) {
|
||||
"Functor return ints");
|
||||
|
||||
// Test lambda
|
||||
auto lambda_one_int_return_double = [](int one) -> double {
|
||||
(void)one;
|
||||
auto lambda_one_int_return_double = []([[maybe_unused]] int one) -> double {
|
||||
return 1.0;
|
||||
};
|
||||
|
||||
@@ -697,20 +672,15 @@ TEST(TestFunctionTraits, sfinae_match) {
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto lambda_one_int = [](int one) {
|
||||
(void)one;
|
||||
auto lambda_one_int = []([[maybe_unused]] int one) {
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto lambda_two_ints = [](int one, int two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_two_ints = []([[maybe_unused]] int one, [[maybe_unused]] int two) {
|
||||
return 2;
|
||||
};
|
||||
|
||||
auto lambda_one_int_one_char = [](int one, char two) {
|
||||
(void)one;
|
||||
(void)two;
|
||||
auto lambda_one_int_one_char = []([[maybe_unused]] int one, [[maybe_unused]] char two) {
|
||||
return 3;
|
||||
};
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ TEST_F(TestGenericService, rcl_service_request_subscription_get_actual_qos_error
|
||||
TEST_F(TestGenericService, generic_service_qos) {
|
||||
rclcpp::ServicesQoS qos_profile;
|
||||
qos_profile.liveliness(rclcpp::LivelinessPolicy::Automatic);
|
||||
rclcpp::Duration duration(std::chrono::nanoseconds(1));
|
||||
rclcpp::Duration duration(std::chrono::milliseconds(1));
|
||||
qos_profile.deadline(duration);
|
||||
qos_profile.lifespan(duration);
|
||||
qos_profile.liveliness_lease_duration(duration);
|
||||
|
||||
@@ -193,16 +193,14 @@ public:
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const rmw_gid_t & gid) const
|
||||
operator==([[maybe_unused]] const rmw_gid_t & gid) const
|
||||
{
|
||||
(void)gid;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(const rmw_gid_t * gid) const
|
||||
operator==([[maybe_unused]] const rmw_gid_t * gid) const
|
||||
{
|
||||
(void)gid;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -260,12 +258,12 @@ public:
|
||||
RCLCPP_SMART_PTR_ALIASES_ONLY(SubscriptionIntraProcessBase)
|
||||
|
||||
explicit SubscriptionIntraProcessBase(
|
||||
rclcpp::Context::SharedPtr context,
|
||||
[[maybe_unused]] rclcpp::Context::SharedPtr context,
|
||||
const std::string & topic,
|
||||
const rclcpp::QoS & qos)
|
||||
: topic_name(topic), qos_profile(qos)
|
||||
{
|
||||
(void)context;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
virtual ~SubscriptionIntraProcessBase() {}
|
||||
|
||||
@@ -61,9 +61,8 @@ public:
|
||||
return static_cast<T *>(std::malloc(size * sizeof(T)));
|
||||
}
|
||||
|
||||
void deallocate(T * ptr, size_t size)
|
||||
void deallocate(T * ptr, [[maybe_unused]] size_t size)
|
||||
{
|
||||
(void)size;
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ public:
|
||||
void clear_on_ready_callback() override {}
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class TestMemoryStrategy : public ::testing::Test
|
||||
|
||||
@@ -34,9 +34,9 @@ using namespace std::chrono_literals;
|
||||
class TestParameterClient : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void OnMessage(rcl_interfaces::msg::ParameterEvent::ConstSharedPtr event)
|
||||
void OnMessage([[maybe_unused]] rcl_interfaces::msg::ParameterEvent::ConstSharedPtr event)
|
||||
{
|
||||
(void)event;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -187,6 +188,21 @@ TEST_F(TestPublisher, various_creation_signatures) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Testing publisher with intraprocess enabled and SystemDefaultQoS
|
||||
*/
|
||||
TEST_F(TestPublisher, test_publisher_with_system_default_qos) {
|
||||
initialize(rclcpp::NodeOptions().use_intra_process_comms(false));
|
||||
// explicitly enable intra-process comm with publisher option
|
||||
auto options = rclcpp::PublisherOptions();
|
||||
options.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
using test_msgs::msg::Empty;
|
||||
ASSERT_NO_THROW(
|
||||
{
|
||||
auto publisher = node->create_publisher<Empty>("topic", rclcpp::SystemDefaultsQoS());
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Testing publisher with intraprocess enabled and invalid QoS
|
||||
*/
|
||||
@@ -432,11 +448,10 @@ TEST_F(TestPublisher, intra_process_publish_failures) {
|
||||
publisher->get_publisher_handle().get(), msg.get()));
|
||||
}
|
||||
}
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
node->create_publisher<test_msgs::msg::Empty>(
|
||||
"topic", rclcpp::QoS(0), options),
|
||||
std::invalid_argument(
|
||||
"intraprocess communication is not allowed with a zero qos history depth value"));
|
||||
// a zero depth with KEEP_LAST doesn't make sense,
|
||||
// this will be interpreted as SystemDefaultQoS by rclcpp.
|
||||
EXPECT_NO_THROW(
|
||||
node->create_publisher<test_msgs::msg::Empty>("topic", rclcpp::QoS(0), options));
|
||||
}
|
||||
|
||||
TEST_F(TestPublisher, inter_process_publish_failures) {
|
||||
@@ -711,11 +726,7 @@ TEST_F(TestPublisher, intra_process_transient_local) {
|
||||
auto do_nothing = [](std::shared_ptr<const test_msgs::msg::Empty>) {};
|
||||
struct IntraProcessCallback
|
||||
{
|
||||
void callback_fun(size_t s)
|
||||
{
|
||||
(void) s;
|
||||
called = true;
|
||||
}
|
||||
void callback_fun(size_t) {called = true;}
|
||||
bool called = false;
|
||||
};
|
||||
rclcpp::SubscriptionOptions sub_options_ipm_disabled;
|
||||
@@ -772,3 +783,44 @@ TEST_F(TestPublisher, intra_process_transient_local) {
|
||||
EXPECT_FALSE(callback3.called);
|
||||
EXPECT_FALSE(callback4.called);
|
||||
}
|
||||
|
||||
TEST_F(TestPublisher, intra_process_inter_process_mix_transient_local) {
|
||||
constexpr auto history_depth = 10u;
|
||||
initialize(rclcpp::NodeOptions().use_intra_process_comms(true));
|
||||
|
||||
rclcpp::PublisherOptionsWithAllocator<std::allocator<void>> pub_options_ipm_enabled;
|
||||
pub_options_ipm_enabled.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
|
||||
auto pub_ipm_enabled_transient_local_enabled = node->create_publisher<test_msgs::msg::Empty>(
|
||||
"topic1",
|
||||
rclcpp::QoS(rclcpp::KeepLast(history_depth)).transient_local(), pub_options_ipm_enabled);
|
||||
|
||||
test_msgs::msg::Empty msg;
|
||||
pub_ipm_enabled_transient_local_enabled->publish(msg);
|
||||
|
||||
auto do_nothing = [](std::shared_ptr<const test_msgs::msg::Empty>) {};
|
||||
struct SubscriptionCallback
|
||||
{
|
||||
void callback_fun(size_t) {called.set_value();}
|
||||
std::promise<void> called;
|
||||
};
|
||||
rclcpp::SubscriptionOptions sub_options_ipm_disabled;
|
||||
sub_options_ipm_disabled.use_intra_process_comm = rclcpp::IntraProcessSetting::Disable;
|
||||
SubscriptionCallback intra_callback, inter_callback;
|
||||
std::future<void> intra_callback_future = intra_callback.called.get_future(),
|
||||
inter_callback_future = inter_callback.called.get_future();
|
||||
auto sub_ipm_disabled_transient_local_enabled = node->create_subscription<test_msgs::msg::Empty>(
|
||||
"topic1",
|
||||
rclcpp::QoS(rclcpp::KeepLast(history_depth)).transient_local(),
|
||||
do_nothing, sub_options_ipm_disabled);
|
||||
sub_ipm_disabled_transient_local_enabled->set_on_new_intra_process_message_callback(
|
||||
std::bind(&SubscriptionCallback::callback_fun, &intra_callback, std::placeholders::_1));
|
||||
sub_ipm_disabled_transient_local_enabled->set_on_new_message_callback(
|
||||
std::bind(&SubscriptionCallback::callback_fun, &inter_callback, std::placeholders::_1));
|
||||
rclcpp::executors::SingleThreadedExecutor executor;
|
||||
executor.add_node(node);
|
||||
EXPECT_EQ(executor.spin_until_future_complete(inter_callback_future,
|
||||
std::chrono::milliseconds(100)), rclcpp::FutureReturnCode::SUCCESS);
|
||||
EXPECT_EQ(executor.spin_until_future_complete(intra_callback_future,
|
||||
std::chrono::milliseconds(100)), rclcpp::FutureReturnCode::TIMEOUT);
|
||||
}
|
||||
|
||||
@@ -122,9 +122,9 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
static void OnMessage(test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
static void OnMessage([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
std::chrono::milliseconds offset{2000};
|
||||
|
||||
@@ -88,21 +88,18 @@ struct TypeAdapter<int, rclcpp::msg::String>
|
||||
|
||||
static void
|
||||
convert_to_ros_message(
|
||||
const custom_type & source,
|
||||
ros_message_type & destination)
|
||||
[[maybe_unused]] const custom_type & source,
|
||||
[[maybe_unused]] ros_message_type & destination)
|
||||
{
|
||||
(void) source;
|
||||
(void) destination;
|
||||
throw std::runtime_error("This should not happen");
|
||||
}
|
||||
|
||||
static void
|
||||
convert_to_custom(
|
||||
const ros_message_type & source,
|
||||
custom_type & destination)
|
||||
[[maybe_unused]] const ros_message_type & source,
|
||||
[[maybe_unused]] custom_type & destination)
|
||||
{
|
||||
(void) source;
|
||||
(void) destination;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,9 +162,9 @@ TEST_F(TestPublisher, conversion_exception_is_passed_up) {
|
||||
options.use_intra_process_comms(is_intra_process);
|
||||
|
||||
auto callback =
|
||||
[](const rclcpp::msg::String::ConstSharedPtr msg) -> void
|
||||
[]([[maybe_unused]] const rclcpp::msg::String::ConstSharedPtr msg) -> void
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
|
||||
auto node = std::make_shared<rclcpp::Node>("my_node", "/ns", options);
|
||||
|
||||
@@ -184,7 +184,7 @@ TEST(TestQoS, DerivedTypes) {
|
||||
EXPECT_EQ(rmw_qos_profile_parameter_events, parameter_events_qos.get_rmw_qos_profile());
|
||||
|
||||
rclcpp::RosoutQoS rosout_qos;
|
||||
EXPECT_EQ(rcl_qos_profile_rosout_default, rosout_qos.get_rmw_qos_profile());
|
||||
EXPECT_EQ(rmw_qos_profile_rosout_default, rosout_qos.get_rmw_qos_profile());
|
||||
|
||||
rclcpp::SystemDefaultsQoS system_default_qos;
|
||||
const rclcpp::KeepLast expected_initialization(RMW_QOS_POLICY_DEPTH_SYSTEM_DEFAULT);
|
||||
|
||||
@@ -73,7 +73,9 @@ TEST_F(TestQosEvent, test_publisher_constructor)
|
||||
auto publisher = node->create_publisher<test_msgs::msg::Empty>(
|
||||
topic_name, 10, options);
|
||||
|
||||
if (rmw_implementation_str != "rmw_zenoh_cpp") {
|
||||
if (rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED) &&
|
||||
rmw_event_type_is_supported(RMW_EVENT_LIVELINESS_LOST))
|
||||
{
|
||||
// options arg with one of the callbacks
|
||||
options.event_callbacks.deadline_callback =
|
||||
[node = node.get()](rclcpp::QOSDeadlineOfferedInfo & event) {
|
||||
@@ -121,7 +123,9 @@ TEST_F(TestQosEvent, test_subscription_constructor)
|
||||
auto subscription = node->create_subscription<test_msgs::msg::Empty>(
|
||||
topic_name, 10, message_callback, options);
|
||||
|
||||
if (rmw_implementation_str != "rmw_zenoh_cpp") {
|
||||
if (rmw_event_type_is_supported(RMW_EVENT_REQUESTED_DEADLINE_MISSED) &&
|
||||
rmw_event_type_is_supported(RMW_EVENT_LIVELINESS_CHANGED))
|
||||
{
|
||||
// options arg with one of the callbacks
|
||||
options.event_callbacks.deadline_callback =
|
||||
[node = node.get()](rclcpp::QOSDeadlineRequestedInfo & event) {
|
||||
@@ -211,10 +215,9 @@ TEST_F(TestQosEvent, test_default_incompatible_qos_callbacks)
|
||||
const auto timeout = std::chrono::seconds(10);
|
||||
ex.spin_until_future_complete(log_msgs_future, timeout);
|
||||
|
||||
if (rmw_implementation_str == "rmw_zenoh_cpp") {
|
||||
EXPECT_EQ(rclcpp::QoSCompatibility::Ok,
|
||||
qos_check_compatible(qos_profile_publisher, qos_profile_subscription).compatibility);
|
||||
} else {
|
||||
if (qos_check_compatible(qos_profile_publisher,
|
||||
qos_profile_subscription).compatibility != rclcpp::QoSCompatibility::Ok)
|
||||
{
|
||||
EXPECT_EQ(
|
||||
"New subscription discovered on topic '/ns/test_topic', requesting incompatible QoS. "
|
||||
"No messages will be sent to it. Last incompatible policy: DURABILITY_QOS_POLICY",
|
||||
@@ -235,7 +238,8 @@ TEST_F(TestQosEvent, construct_destruct_rcl_error) {
|
||||
|
||||
// This callback requires some type of parameter, but it could be anything
|
||||
auto callback = [](int) {};
|
||||
const rcl_publisher_event_type_t event_type = rmw_implementation_str == "rmw_zenoh_cpp" ?
|
||||
const rcl_publisher_event_type_t event_type =
|
||||
!rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED) ?
|
||||
RCL_PUBLISHER_MATCHED : RCL_PUBLISHER_OFFERED_DEADLINE_MISSED;
|
||||
|
||||
{
|
||||
@@ -273,9 +277,10 @@ TEST_F(TestQosEvent, construct_destruct_rcl_error) {
|
||||
}
|
||||
|
||||
TEST_F(TestQosEvent, execute) {
|
||||
if (rmw_implementation_str == "rmw_zenoh_cpp") {
|
||||
if (!rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED)) {
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
auto publisher = node->create_publisher<test_msgs::msg::Empty>(topic_name, 10);
|
||||
auto rcl_handle = publisher->get_publisher_handle();
|
||||
|
||||
@@ -308,9 +313,11 @@ TEST_F(TestQosEvent, add_to_wait_set) {
|
||||
// This callback requires some type of parameter, but it could be anything
|
||||
auto callback = [](int) {};
|
||||
|
||||
const rcl_publisher_event_type_t event_type = rmw_implementation_str == "rmw_zenoh_cpp" ?
|
||||
const rcl_publisher_event_type_t event_type =
|
||||
!rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED) ?
|
||||
RCL_PUBLISHER_MATCHED : RCL_PUBLISHER_OFFERED_DEADLINE_MISSED;
|
||||
rclcpp::EventHandler<decltype(callback), decltype(rcl_handle)> handler(
|
||||
|
||||
rclcpp::EventHandler<decltype(callback), decltype(rcl_handle)> handler(
|
||||
callback, rcl_publisher_event_init, rcl_handle, event_type);
|
||||
|
||||
EXPECT_EQ(1u, handler.get_number_of_ready_events());
|
||||
@@ -331,7 +338,9 @@ TEST_F(TestQosEvent, add_to_wait_set) {
|
||||
|
||||
TEST_F(TestQosEvent, test_on_new_event_callback)
|
||||
{
|
||||
if (rmw_implementation_str == "rmw_zenoh_cpp") {
|
||||
if (!rmw_event_type_is_supported(RMW_EVENT_REQUESTED_DEADLINE_MISSED) ||
|
||||
!rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED))
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
@@ -380,7 +389,9 @@ TEST_F(TestQosEvent, test_invalid_on_new_event_callback)
|
||||
auto sub = node->create_subscription<test_msgs::msg::Empty>(topic_name, 10, message_callback);
|
||||
auto dummy_cb = [](size_t count_events) {(void)count_events;};
|
||||
|
||||
if (rmw_implementation_str != "rmw_zenoh_cpp") {
|
||||
if (rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED) &&
|
||||
rmw_event_type_is_supported(RMW_EVENT_LIVELINESS_LOST))
|
||||
{
|
||||
EXPECT_NO_THROW(
|
||||
pub->set_on_new_qos_event_callback(dummy_cb, RCL_PUBLISHER_OFFERED_DEADLINE_MISSED));
|
||||
|
||||
@@ -405,7 +416,9 @@ TEST_F(TestQosEvent, test_invalid_on_new_event_callback)
|
||||
EXPECT_NO_THROW(
|
||||
pub->clear_on_new_qos_event_callback(RCL_PUBLISHER_MATCHED));
|
||||
|
||||
if (rmw_implementation_str == "rmw_zenoh_cpp") {
|
||||
if (rmw_event_type_is_supported(RMW_EVENT_REQUESTED_DEADLINE_MISSED) &&
|
||||
rmw_event_type_is_supported(RMW_EVENT_LIVELINESS_CHANGED))
|
||||
{
|
||||
EXPECT_NO_THROW(
|
||||
sub->set_on_new_qos_event_callback(dummy_cb, RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED));
|
||||
|
||||
@@ -430,7 +443,9 @@ TEST_F(TestQosEvent, test_invalid_on_new_event_callback)
|
||||
EXPECT_NO_THROW(
|
||||
sub->clear_on_new_qos_event_callback(RCL_SUBSCRIPTION_MATCHED));
|
||||
|
||||
if (rmw_implementation_str != "rmw_zenoh_cpp") {
|
||||
if (rmw_event_type_is_supported(RMW_EVENT_REQUESTED_DEADLINE_MISSED) &&
|
||||
rmw_event_type_is_supported(RMW_EVENT_OFFERED_DEADLINE_MISSED))
|
||||
{
|
||||
std::function<void(size_t)> invalid_cb;
|
||||
|
||||
rclcpp::SubscriptionOptions sub_options;
|
||||
|
||||
@@ -58,8 +58,8 @@ TEST(TestRosoutQoS, test_rosout_qos_with_default_value) {
|
||||
rclcpp::NodeOptions node_options;
|
||||
rclcpp::QoS rosout_qos_profile = node_options.rosout_qos();
|
||||
rmw_qos_profile_t rmw_qos_profile = rosout_qos_profile.get_rmw_qos_profile();
|
||||
EXPECT_EQ(rcl_qos_profile_rosout_default, rmw_qos_profile);
|
||||
EXPECT_EQ(rcl_qos_profile_rosout_default, node_options.get_rcl_node_options()->rosout_qos);
|
||||
EXPECT_EQ(rmw_qos_profile_rosout_default, rmw_qos_profile);
|
||||
EXPECT_EQ(rmw_qos_profile_rosout_default, node_options.get_rcl_node_options()->rosout_qos);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -335,7 +335,7 @@ TEST_F(TestService, rcl_service_request_subscription_get_actual_qos_error) {
|
||||
TEST_F(TestService, server_qos) {
|
||||
rclcpp::ServicesQoS qos_profile;
|
||||
qos_profile.liveliness(rclcpp::LivelinessPolicy::Automatic);
|
||||
rclcpp::Duration duration(std::chrono::nanoseconds(1));
|
||||
rclcpp::Duration duration(std::chrono::milliseconds(1));
|
||||
qos_profile.deadline(duration);
|
||||
qos_profile.lifespan(duration);
|
||||
qos_profile.liveliness_lease_duration(duration);
|
||||
|
||||
@@ -63,7 +63,6 @@ protected:
|
||||
|
||||
auto callback = [this](const std::shared_ptr<const BasicTypes::Event> & msg) {
|
||||
events.push_back(msg);
|
||||
(void)msg;
|
||||
};
|
||||
|
||||
client = node->create_client<BasicTypes>("service");
|
||||
@@ -92,9 +91,16 @@ TEST_F(TestServiceIntrospection, service_introspection_nominal)
|
||||
request->set__int64_value(42);
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
size_t tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
auto future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -163,9 +169,11 @@ TEST_F(TestServiceIntrospection, service_introspection_nominal)
|
||||
TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
{
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
|
||||
ASSERT_EQ(sub->get_publisher_count(), 0);
|
||||
|
||||
auto request = std::make_shared<BasicTypes::Request>();
|
||||
request->set__bool_value(true);
|
||||
@@ -183,9 +191,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
size_t tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 1 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 1u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -200,9 +215,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_OFF);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 1 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 1u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -217,9 +239,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -235,9 +264,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_events)
|
||||
TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_content)
|
||||
{
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
size_t tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
auto request = std::make_shared<BasicTypes::Request>();
|
||||
request->set__bool_value(true);
|
||||
@@ -259,9 +295,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_cont
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -292,9 +335,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_cont
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_METADATA);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
@@ -325,9 +375,16 @@ TEST_F(TestServiceIntrospection, service_introspection_enable_disable_event_cont
|
||||
events.clear();
|
||||
|
||||
client->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
service->configure_introspection(
|
||||
node->get_clock(), rclcpp::SystemDefaultsQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
node->get_clock(), rclcpp::ServicesQoS(), RCL_SERVICE_INTROSPECTION_CONTENTS);
|
||||
|
||||
// Wait for the introspection to attach to our subscription
|
||||
tries = 1000;
|
||||
while (this->sub->get_publisher_count() < 2 && tries-- > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_EQ(sub->get_publisher_count(), 2u);
|
||||
|
||||
future = client->async_send_request(request);
|
||||
ASSERT_EQ(
|
||||
|
||||
@@ -36,9 +36,9 @@ using namespace std::chrono_literals;
|
||||
class TestSubscription : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void on_message(test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
void on_message([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -66,8 +66,8 @@ protected:
|
||||
TEST_F(TestSubscription, construction_and_destruction) {
|
||||
initialize();
|
||||
using test_msgs::msg::Empty;
|
||||
auto callback = [](Empty::ConstSharedPtr msg) {
|
||||
(void)msg;
|
||||
auto callback = []([[maybe_unused]] Empty::ConstSharedPtr msg) {
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
{
|
||||
constexpr size_t depth = 10u;
|
||||
@@ -156,9 +156,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void on_message(test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
void on_message([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,9 +178,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void on_message(test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
void on_message([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -502,8 +502,8 @@ TEST_F(TestSubscription, on_new_intra_process_message_callback) {
|
||||
TEST_F(TestSubscription, get_network_flow_endpoints_errors) {
|
||||
initialize();
|
||||
const rclcpp::QoS subscription_qos(1);
|
||||
auto subscription_callback = [](test_msgs::msg::Empty::ConstSharedPtr msg) {
|
||||
(void)msg;
|
||||
auto subscription_callback = []([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg) {
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
auto subscription = node_->create_subscription<test_msgs::msg::Empty>(
|
||||
"topic", subscription_qos, subscription_callback);
|
||||
@@ -560,8 +560,8 @@ protected:
|
||||
Testing subscription construction and destruction for subnodes.
|
||||
*/
|
||||
TEST_F(TestSubscriptionSub, construction_and_destruction) {
|
||||
auto callback = [](test_msgs::msg::Empty::ConstSharedPtr msg) {
|
||||
(void)msg;
|
||||
auto callback = []([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg) {
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
{
|
||||
auto sub = subnode_->create_subscription<test_msgs::msg::Empty>("topic", 1, callback);
|
||||
|
||||
@@ -108,9 +108,9 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
static void OnMessage(test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
static void OnMessage([[maybe_unused]] test_msgs::msg::Empty::ConstSharedPtr msg)
|
||||
{
|
||||
(void)msg;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
std::chrono::milliseconds offset{2000};
|
||||
|
||||
@@ -57,21 +57,17 @@ struct TypeAdapter<std::string, rclcpp::msg::String>
|
||||
|
||||
static void
|
||||
convert_to_ros_message(
|
||||
const custom_type & source,
|
||||
ros_message_type & destination)
|
||||
[[maybe_unused]] const custom_type & source,
|
||||
[[maybe_unused]] ros_message_type & destination)
|
||||
{
|
||||
(void) source;
|
||||
(void) destination;
|
||||
throw std::runtime_error("This should not happen");
|
||||
}
|
||||
|
||||
static void
|
||||
convert_to_custom(
|
||||
const ros_message_type & source,
|
||||
custom_type & destination)
|
||||
[[maybe_unused]] const ros_message_type & source,
|
||||
[[maybe_unused]] custom_type & destination)
|
||||
{
|
||||
(void) source;
|
||||
(void) destination;
|
||||
throw std::runtime_error("This should not happen");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,41 +26,44 @@
|
||||
|
||||
#include "test_msgs/msg/empty.hpp"
|
||||
|
||||
void serialized_callback_copy(rcl_serialized_message_t unused)
|
||||
void serialized_callback_copy([[maybe_unused]] rcl_serialized_message_t unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
void serialized_callback_shared_ptr(std::shared_ptr<rcl_serialized_message_t> unused)
|
||||
void serialized_callback_shared_ptr(
|
||||
[[maybe_unused]] std::shared_ptr<rcl_serialized_message_t> unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
void not_serialized_callback(char * unused)
|
||||
void not_serialized_callback([[maybe_unused]] char * unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
void not_serialized_shared_ptr_callback(std::shared_ptr<char> unused)
|
||||
void not_serialized_shared_ptr_callback([[maybe_unused]] std::shared_ptr<char> unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
void not_serialized_unique_ptr_callback(
|
||||
[[maybe_unused]]
|
||||
test_msgs::msg::Empty::UniquePtrWithDeleter<rclcpp::allocator::Deleter<std::allocator<void>,
|
||||
test_msgs::msg::Empty>> unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
void rclcpp_serialized_callback_copy(rclcpp::SerializedMessage unused)
|
||||
void rclcpp_serialized_callback_copy([[maybe_unused]] rclcpp::SerializedMessage unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
void rclcpp_serialized_callback_shared_ptr(std::shared_ptr<rclcpp::SerializedMessage> unused)
|
||||
void rclcpp_serialized_callback_shared_ptr(
|
||||
[[maybe_unused]] std::shared_ptr<rclcpp::SerializedMessage> unused)
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
}
|
||||
|
||||
TEST(TestSubscriptionTraits, is_serialized_callback) {
|
||||
@@ -85,9 +88,9 @@ TEST(TestSubscriptionTraits, is_serialized_callback) {
|
||||
rclcpp::subscription_traits::is_serialized_callback<decltype(cb4)>::value == false,
|
||||
"passing a std::shared_tr<char> is not a serialized callback");
|
||||
|
||||
auto cb5 = [](rcl_serialized_message_t unused) -> void
|
||||
auto cb5 = []([[maybe_unused]] rcl_serialized_message_t unused) -> void
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
static_assert(
|
||||
rclcpp::subscription_traits::is_serialized_callback<decltype(cb5)>::value == false,
|
||||
@@ -96,9 +99,10 @@ TEST(TestSubscriptionTraits, is_serialized_callback) {
|
||||
using MessageT = test_msgs::msg::Empty;
|
||||
using MessageTAllocator = std::allocator<void>;
|
||||
using MessageTDeallocator = rclcpp::allocator::Deleter<MessageTAllocator, MessageT>;
|
||||
auto cb6 = [](MessageT::UniquePtrWithDeleter<MessageTDeallocator> unique_msg_ptr) -> void
|
||||
auto cb6 = [](
|
||||
[[maybe_unused]] MessageT::UniquePtrWithDeleter<MessageTDeallocator> unique_msg_ptr) -> void
|
||||
{
|
||||
(void) unique_msg_ptr;
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
static_assert(
|
||||
rclcpp::subscription_traits::is_serialized_callback<decltype(cb6)>::value == false,
|
||||
@@ -167,9 +171,9 @@ TEST(TestSubscriptionTraits, callback_messages) {
|
||||
rclcpp::subscription_traits::has_message_type<decltype(cb4)>::type>::value,
|
||||
"not serialized shared_ptr callback message type is std::shared_ptr<char>");
|
||||
|
||||
auto cb5 = [](rcl_serialized_message_t unused) -> void
|
||||
auto cb5 = []([[maybe_unused]] rcl_serialized_message_t unused) -> void
|
||||
{
|
||||
(void) unused;
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
static_assert(
|
||||
std::is_same<
|
||||
@@ -180,9 +184,10 @@ TEST(TestSubscriptionTraits, callback_messages) {
|
||||
using MessageT = test_msgs::msg::Empty;
|
||||
using MessageTAllocator = std::allocator<MessageT>;
|
||||
using MessageTDeallocator = rclcpp::allocator::Deleter<MessageTAllocator, MessageT>;
|
||||
auto cb6 = [](std::unique_ptr<MessageT, MessageTDeallocator> unique_msg_ptr) -> void
|
||||
auto cb6 = [](
|
||||
[[maybe_unused]] std::unique_ptr<MessageT, MessageTDeallocator> unique_msg_ptr) -> void
|
||||
{
|
||||
(void) unique_msg_ptr;
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
static_assert(
|
||||
std::is_same<
|
||||
|
||||
@@ -212,6 +212,9 @@ TEST_F(TestTimeSource, ROS_time_valid_attach_detach) {
|
||||
ts.attachNode(node);
|
||||
EXPECT_FALSE(ros_clock->ros_time_is_active());
|
||||
|
||||
ts.attachClock(ros_clock);
|
||||
EXPECT_FALSE(ros_clock->ros_time_is_active());
|
||||
|
||||
ts.detachClock(ros_clock);
|
||||
EXPECT_FALSE(ros_clock->ros_time_is_active());
|
||||
}
|
||||
|
||||
@@ -106,6 +106,36 @@ TEST(TypesupportHelpersTest, returns_service_type_info_for_valid_library) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TypesupportHelpersTest, returns_action_type_info_for_valid_legacy_library) {
|
||||
try {
|
||||
auto library = rclcpp::get_typesupport_library(
|
||||
"test_msgs/NestedMessage", "rosidl_typesupport_cpp");
|
||||
auto nestedmessage_typesupport = rclcpp::get_action_typesupport_handle(
|
||||
"test_msgs/NestedMessage", "rosidl_typesupport_cpp", *library);
|
||||
|
||||
EXPECT_THAT(
|
||||
std::string(nestedmessage_typesupport->goal_service_type_support->typesupport_identifier),
|
||||
ContainsRegex("rosidl_typesupport"));
|
||||
} catch (const std::runtime_error & e) {
|
||||
FAIL() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TypesupportHelpersTest, returns_action_type_info_for_valid_library) {
|
||||
try {
|
||||
auto library = rclcpp::get_typesupport_library(
|
||||
"test_msgs/action/NestedMessage", "rosidl_typesupport_cpp");
|
||||
auto nestedmessage_typesupport = rclcpp::get_action_typesupport_handle(
|
||||
"test_msgs/action/NestedMessage", "rosidl_typesupport_cpp", *library);
|
||||
|
||||
EXPECT_THAT(
|
||||
std::string(nestedmessage_typesupport->goal_service_type_support->typesupport_identifier),
|
||||
ContainsRegex("rosidl_typesupport"));
|
||||
} catch (const std::runtime_error & e) {
|
||||
FAIL() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TypesupportHelpersTest, test_throw_exception_with_invalid_type) {
|
||||
// message
|
||||
std::string invalid_type = "test_msgs/msg/InvalidType";
|
||||
@@ -113,9 +143,6 @@ TEST(TypesupportHelpersTest, test_throw_exception_with_invalid_type) {
|
||||
EXPECT_THROW(
|
||||
rclcpp::get_message_typesupport_handle(invalid_type, "rosidl_typesupport_cpp", *library),
|
||||
std::runtime_error);
|
||||
EXPECT_THROW(
|
||||
rclcpp::get_service_typesupport_handle(invalid_type, "rosidl_typesupport_cpp", *library),
|
||||
std::runtime_error);
|
||||
|
||||
// service
|
||||
invalid_type = "test_msgs/srv/InvalidType";
|
||||
@@ -123,4 +150,11 @@ TEST(TypesupportHelpersTest, test_throw_exception_with_invalid_type) {
|
||||
EXPECT_THROW(
|
||||
rclcpp::get_service_typesupport_handle(invalid_type, "rosidl_typesupport_cpp", *library),
|
||||
std::runtime_error);
|
||||
|
||||
// action
|
||||
invalid_type = "test_msgs/action/InvalidType";
|
||||
library = rclcpp::get_typesupport_library(invalid_type, "rosidl_typesupport_cpp");
|
||||
EXPECT_THROW(
|
||||
rclcpp::get_action_typesupport_handle(invalid_type, "rosidl_typesupport_cpp", *library),
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ public:
|
||||
options.topic_stats_options.state = rclcpp::TopicStatisticsState::Enable;
|
||||
options.topic_stats_options.publish_period = publish_period;
|
||||
|
||||
auto callback = [](typename MessageT::UniquePtr msg) {
|
||||
(void) msg;
|
||||
auto callback = []([[maybe_unused]] typename MessageT::UniquePtr msg) {
|
||||
// This function is intentionally left empty.
|
||||
};
|
||||
subscription_ = create_subscription<MessageT,
|
||||
std::function<void(typename MessageT::UniquePtr)>>(
|
||||
|
||||
@@ -64,6 +64,11 @@ public:
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_ready_;
|
||||
};
|
||||
|
||||
@@ -64,6 +64,11 @@ public:
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_ready_;
|
||||
};
|
||||
|
||||
@@ -72,6 +72,11 @@ public:
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_ready_;
|
||||
bool add_to_wait_set_;
|
||||
|
||||
@@ -64,6 +64,11 @@ public:
|
||||
|
||||
std::shared_ptr<void> take_data_by_entity_id(size_t) override {return nullptr;}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>> get_timers() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_ready_;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,21 @@ Changelog for package rclcpp_action
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
29.4.0 (2025-04-04)
|
||||
-------------------
|
||||
* Remove warning (`#2790 <https://github.com/ros2/rclcpp/issues/2790>`_)
|
||||
* Harden rclcpp_action::convert(). (`#2786 <https://github.com/ros2/rclcpp/issues/2786>`_)
|
||||
* Add new interfaces to enable introspection for action (`#2743 <https://github.com/ros2/rclcpp/issues/2743>`_)
|
||||
* use maybe_unused attribute for the portability. (`#2758 <https://github.com/ros2/rclcpp/issues/2758>`_)
|
||||
* fix: Expose timers used by rclcpp::Waitables (`#2699 <https://github.com/ros2/rclcpp/issues/2699>`_)
|
||||
* Collect log messages from rcl, and reset. (`#2720 <https://github.com/ros2/rclcpp/issues/2720>`_)
|
||||
* Contributors: Alejandro Hernández Cordero, Barry Xu, Janosch Machowinski, Tomoya Fujita
|
||||
|
||||
29.3.0 (2024-12-20)
|
||||
-------------------
|
||||
* Make ament_cmake a buildtool dependency (`#2689 <https://github.com/ros2/rclcpp/issues/2689>`_)
|
||||
* Contributors: Nathan Wiebe Neufeldt
|
||||
|
||||
29.2.0 (2024-11-25)
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}
|
||||
src/client.cpp
|
||||
src/client_base.cpp
|
||||
src/create_generic_client.cpp
|
||||
src/generic_client.cpp
|
||||
src/generic_client_goal_handle.cpp
|
||||
src/qos.cpp
|
||||
src/server.cpp
|
||||
src/server_goal_handle.cpp
|
||||
@@ -93,6 +96,20 @@ if(BUILD_TESTING)
|
||||
)
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_generic_client test/test_generic_client.cpp TIMEOUT 180)
|
||||
ament_add_test_label(test_generic_client mimick)
|
||||
if(TARGET test_generic_client)
|
||||
target_link_libraries(test_generic_client
|
||||
${PROJECT_NAME}
|
||||
mimick
|
||||
rcl::rcl
|
||||
rcl_action::rcl_action
|
||||
rclcpp::rclcpp
|
||||
rcutils::rcutils
|
||||
${test_msgs_TARGETS}
|
||||
)
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_server test/test_server.cpp TIMEOUT 180)
|
||||
ament_add_test_label(test_server mimick)
|
||||
if(TARGET test_server)
|
||||
|
||||
@@ -25,305 +25,32 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "rcl/event_callback.h"
|
||||
|
||||
#include "rclcpp/exceptions.hpp"
|
||||
#include "rclcpp/clock.hpp"
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/node_interfaces/node_base_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_logging_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_graph_interface.hpp"
|
||||
#include "rclcpp/logger.hpp"
|
||||
#include "rclcpp/qos.hpp"
|
||||
#include "rclcpp/time.hpp"
|
||||
#include "rclcpp/waitable.hpp"
|
||||
|
||||
#include "rosidl_runtime_c/action_type_support_struct.h"
|
||||
#include "rosidl_typesupport_cpp/action_type_support.hpp"
|
||||
|
||||
#include "rclcpp_action/client_base.hpp"
|
||||
#include "rclcpp_action/client_goal_handle.hpp"
|
||||
#include "rclcpp_action/exceptions.hpp"
|
||||
#include "rclcpp_action/types.hpp"
|
||||
#include "rclcpp_action/visibility_control.hpp"
|
||||
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
// Forward declaration
|
||||
class ClientBaseImpl;
|
||||
|
||||
/// Base Action Client implementation
|
||||
/// \internal
|
||||
/**
|
||||
* This class should not be used directly by users wanting to create an aciton client.
|
||||
* Instead users should use `rclcpp_action::Client<>`.
|
||||
*
|
||||
* Internally, this class is responsible for interfacing with the `rcl_action` API.
|
||||
*/
|
||||
class ClientBase : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS_NOT_COPYABLE(ClientBase)
|
||||
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual ~ClientBase();
|
||||
|
||||
/// Enum to identify entities belonging to the action client
|
||||
enum class EntityType : std::size_t
|
||||
{
|
||||
GoalClient,
|
||||
ResultClient,
|
||||
CancelClient,
|
||||
FeedbackSubscription,
|
||||
StatusSubscription,
|
||||
};
|
||||
|
||||
/// Return true if there is an action server that is ready to take goal requests.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
action_server_is_ready() const;
|
||||
|
||||
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
|
||||
template<typename RepT = int64_t, typename RatioT = std::milli>
|
||||
bool
|
||||
wait_for_action_server(
|
||||
std::chrono::duration<RepT, RatioT> timeout = std::chrono::duration<RepT, RatioT>(-1))
|
||||
{
|
||||
return wait_for_action_server_nanoseconds(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
|
||||
);
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Waitables API
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_subscriptions() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_timers() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_clients() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_services() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_ptr<void>
|
||||
take_data() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
execute(const std::shared_ptr<void> & data) override;
|
||||
|
||||
/// \internal
|
||||
/// Set a callback to be called when action client entities have an event
|
||||
/**
|
||||
* The callback receives a size_t which is the number of messages received
|
||||
* since the last time this callback was called.
|
||||
* Normally this is 1, but can be > 1 if messages were received before any
|
||||
* callback was set.
|
||||
*
|
||||
* The callback also receives an int identifier argument, which identifies
|
||||
* the action client entity which is ready.
|
||||
* This implies that the provided callback can use the identifier to behave
|
||||
* differently depending on which entity triggered the waitable to become ready.
|
||||
*
|
||||
* Calling it again will clear any previously set callback.
|
||||
*
|
||||
* An exception will be thrown if the callback is not callable.
|
||||
*
|
||||
* This function is thread-safe.
|
||||
*
|
||||
* If you want more information available in the callback, like the subscription
|
||||
* or other information, you may use a lambda with captures or std::bind.
|
||||
*
|
||||
* \param[in] callback functor to be called when a new message is received.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
set_on_ready_callback(std::function<void(size_t, int)> callback) override;
|
||||
|
||||
/// Unset the callback registered for new events, if any.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
clear_on_ready_callback() override;
|
||||
|
||||
// End Waitables API
|
||||
// -----------------
|
||||
|
||||
protected:
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
ClientBase(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
|
||||
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
|
||||
const std::string & action_name,
|
||||
const rosidl_action_type_support_t * type_support,
|
||||
const rcl_action_client_options_t & options);
|
||||
|
||||
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
wait_for_action_server_nanoseconds(std::chrono::nanoseconds timeout);
|
||||
|
||||
// -----------------------------------------------------
|
||||
// API for communication between ClientBase and Client<>
|
||||
using ResponseCallback = std::function<void (std::shared_ptr<void> response)>;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
rclcpp::Logger get_logger();
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
GoalUUID
|
||||
generate_goal_id();
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
send_goal_request(
|
||||
std::shared_ptr<void> request,
|
||||
ResponseCallback callback);
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
send_result_request(
|
||||
std::shared_ptr<void> request,
|
||||
ResponseCallback callback);
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
send_cancel_request(
|
||||
std::shared_ptr<void> request,
|
||||
ResponseCallback callback);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_goal_response() const = 0;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
handle_goal_response(
|
||||
const rmw_request_id_t & response_header,
|
||||
std::shared_ptr<void> goal_response);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_result_response() const = 0;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
handle_result_response(
|
||||
const rmw_request_id_t & response_header,
|
||||
std::shared_ptr<void> result_response);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_cancel_response() const = 0;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
handle_cancel_response(
|
||||
const rmw_request_id_t & response_header,
|
||||
std::shared_ptr<void> cancel_response);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_feedback_message() const = 0;
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
void
|
||||
handle_feedback_message(std::shared_ptr<void> message) = 0;
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_status_message() const = 0;
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
void
|
||||
handle_status_message(std::shared_ptr<void> message) = 0;
|
||||
|
||||
// End API for communication between ClientBase and Client<>
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// \internal
|
||||
/// Set a callback to be called when the specified entity is ready
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
set_on_ready_callback(
|
||||
EntityType entity_type,
|
||||
rcl_event_callback_t callback,
|
||||
const void * user_data);
|
||||
|
||||
// Mutex to protect the callbacks storage.
|
||||
std::recursive_mutex listener_mutex_;
|
||||
// Storage for std::function callbacks to keep them in scope
|
||||
std::unordered_map<EntityType, std::function<void(size_t)>> entity_type_to_on_ready_callback_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ClientBaseImpl> pimpl_;
|
||||
|
||||
/// Set a std::function callback to be called when the specified entity is ready
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
set_callback_to_entity(
|
||||
EntityType entity_type,
|
||||
std::function<void(size_t, int)> callback);
|
||||
|
||||
bool on_ready_callback_set_{false};
|
||||
};
|
||||
|
||||
/// Action Client
|
||||
/**
|
||||
* This class creates an action client.
|
||||
|
||||
339
rclcpp_action/include/rclcpp_action/client_base.hpp
Normal file
339
rclcpp_action/include/rclcpp_action/client_base.hpp
Normal file
@@ -0,0 +1,339 @@
|
||||
// Copyright 2018 Open Source Robotics Foundation, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef RCLCPP_ACTION__CLIENT_BASE_HPP_
|
||||
#define RCLCPP_ACTION__CLIENT_BASE_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rcl_action/action_client.h"
|
||||
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/node_interfaces/node_base_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_logging_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_graph_interface.hpp"
|
||||
#include "rclcpp/logger.hpp"
|
||||
#include "rclcpp/waitable.hpp"
|
||||
|
||||
#include "rclcpp_action/visibility_control.hpp"
|
||||
#include "rclcpp_action/types.hpp"
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
// Forward declaration
|
||||
class ClientBaseImpl;
|
||||
|
||||
/// Base Action Client implementation
|
||||
/// \internal
|
||||
/**
|
||||
* This class should not be used directly by users wanting to create an aciton client.
|
||||
* Instead users should use `rclcpp_action::Client<>`.
|
||||
*
|
||||
* Internally, this class is responsible for interfacing with the `rcl_action` API.
|
||||
*/
|
||||
class ClientBase : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS_NOT_COPYABLE(ClientBase)
|
||||
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual ~ClientBase();
|
||||
|
||||
/// Enum to identify entities belonging to the action client
|
||||
enum class EntityType : std::size_t
|
||||
{
|
||||
GoalClient,
|
||||
ResultClient,
|
||||
CancelClient,
|
||||
FeedbackSubscription,
|
||||
StatusSubscription,
|
||||
};
|
||||
|
||||
/// Return true if there is an action server that is ready to take goal requests.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
action_server_is_ready() const;
|
||||
|
||||
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
|
||||
template<typename RepT = int64_t, typename RatioT = std::milli>
|
||||
bool
|
||||
wait_for_action_server(
|
||||
std::chrono::duration<RepT, RatioT> timeout = std::chrono::duration<RepT, RatioT>(-1))
|
||||
{
|
||||
return wait_for_action_server_nanoseconds(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
|
||||
);
|
||||
}
|
||||
|
||||
// -------------
|
||||
// Waitables API
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_subscriptions() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_timers() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_clients() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_services() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_ptr<void>
|
||||
take_data() override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id) override;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
execute(const std::shared_ptr<void> & data) override;
|
||||
|
||||
/// \internal
|
||||
/// Set a callback to be called when action client entities have an event
|
||||
/**
|
||||
* The callback receives a size_t which is the number of messages received
|
||||
* since the last time this callback was called.
|
||||
* Normally this is 1, but can be > 1 if messages were received before any
|
||||
* callback was set.
|
||||
*
|
||||
* The callback also receives an int identifier argument, which identifies
|
||||
* the action client entity which is ready.
|
||||
* This implies that the provided callback can use the identifier to behave
|
||||
* differently depending on which entity triggered the waitable to become ready.
|
||||
*
|
||||
* Calling it again will clear any previously set callback.
|
||||
*
|
||||
* An exception will be thrown if the callback is not callable.
|
||||
*
|
||||
* This function is thread-safe.
|
||||
*
|
||||
* If you want more information available in the callback, like the subscription
|
||||
* or other information, you may use a lambda with captures or std::bind.
|
||||
*
|
||||
* \param[in] callback functor to be called when a new message is received.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
set_on_ready_callback(std::function<void(size_t, int)> callback) override;
|
||||
|
||||
/// Unset the callback registered for new events, if any.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
clear_on_ready_callback() override;
|
||||
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
get_timers() const override;
|
||||
|
||||
// End Waitables API
|
||||
// -----------------
|
||||
|
||||
/// Configure action client introspection.
|
||||
/**
|
||||
* \param[in] clock clock to use to generate introspection timestamps
|
||||
* \param[in] qos_service_event_pub QoS settings to use when creating the introspection publisher
|
||||
* \param[in] introspection_state the state to set introspection to
|
||||
*
|
||||
* \throws std::invalid_argument if clock is nullptr
|
||||
* \throws rclcpp::exceptions::throw_from_rcl_error if rcl error occurs.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
configure_introspection(
|
||||
rclcpp::Clock::SharedPtr clock,
|
||||
const rclcpp::QoS & qos_service_event_pub,
|
||||
rcl_service_introspection_state_t introspection_state);
|
||||
|
||||
protected:
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
ClientBase(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
|
||||
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
|
||||
const std::string & action_name,
|
||||
const rosidl_action_type_support_t * type_support,
|
||||
const rcl_action_client_options_t & options);
|
||||
|
||||
/// Wait for action_server_is_ready() to become true, or until the given timeout is reached.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
wait_for_action_server_nanoseconds(std::chrono::nanoseconds timeout);
|
||||
|
||||
// -----------------------------------------------------
|
||||
// API for communication between ClientBase and Client<>
|
||||
using ResponseCallback = std::function<void (std::shared_ptr<void> response)>;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
rclcpp::Logger get_logger();
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
GoalUUID
|
||||
generate_goal_id();
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
send_goal_request(
|
||||
std::shared_ptr<void> request,
|
||||
ResponseCallback callback);
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
send_result_request(
|
||||
std::shared_ptr<void> request,
|
||||
ResponseCallback callback);
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
send_cancel_request(
|
||||
std::shared_ptr<void> request,
|
||||
ResponseCallback callback);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_goal_response() const = 0;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
handle_goal_response(
|
||||
const rmw_request_id_t & response_header,
|
||||
std::shared_ptr<void> goal_response);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_result_response() const = 0;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
handle_result_response(
|
||||
const rmw_request_id_t & response_header,
|
||||
std::shared_ptr<void> result_response);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_cancel_response() const = 0;
|
||||
|
||||
/// \internal
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
void
|
||||
handle_cancel_response(
|
||||
const rmw_request_id_t & response_header,
|
||||
std::shared_ptr<void> cancel_response);
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_feedback_message() const = 0;
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
void
|
||||
handle_feedback_message(std::shared_ptr<void> message) = 0;
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
std::shared_ptr<void>
|
||||
create_status_message() const = 0;
|
||||
|
||||
/// \internal
|
||||
virtual
|
||||
void
|
||||
handle_status_message(std::shared_ptr<void> message) = 0;
|
||||
|
||||
// End API for communication between ClientBase and Client<>
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/// \internal
|
||||
/// Set a callback to be called when the specified entity is ready
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
set_on_ready_callback(
|
||||
EntityType entity_type,
|
||||
rcl_event_callback_t callback,
|
||||
const void * user_data);
|
||||
|
||||
// Mutex to protect the callbacks storage.
|
||||
std::recursive_mutex listener_mutex_;
|
||||
// Storage for std::function callbacks to keep them in scope
|
||||
std::unordered_map<EntityType, std::function<void(size_t)>> entity_type_to_on_ready_callback_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ClientBaseImpl> pimpl_;
|
||||
|
||||
/// Set a std::function callback to be called when the specified entity is ready
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
set_callback_to_entity(
|
||||
EntityType entity_type,
|
||||
std::function<void(size_t, int)> callback);
|
||||
|
||||
bool on_ready_callback_set_{false};
|
||||
};
|
||||
} // namespace rclcpp_action
|
||||
|
||||
#endif // RCLCPP_ACTION__CLIENT_BASE_HPP_
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2025 Sony Group Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef RCLCPP_ACTION__CREATE_GENERIC_CLIENT_HPP_
|
||||
#define RCLCPP_ACTION__CREATE_GENERIC_CLIENT_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "rclcpp_action/generic_client.hpp"
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
/// Create an action generic client.
|
||||
/**
|
||||
* This function is equivalent to \sa create_generic_client()` however is using the individual
|
||||
* node interfaces to create the client.
|
||||
*
|
||||
* \param[in] node_base_interface The node base interface of the corresponding node.
|
||||
* \param[in] node_graph_interface The node graph interface of the corresponding node.
|
||||
* \param[in] node_logging_interface The node logging interface of the corresponding node.
|
||||
* \param[in] node_waitables_interface The node waitables interface of the corresponding node.
|
||||
* \param[in] name The action name.
|
||||
* \param[in] type The action type.
|
||||
* \param[in] group The action client will be added to this callback group.
|
||||
* If `nullptr`, then the action client is added to the default callback group.
|
||||
* \param[in] options Options to pass to the underlying `rcl_action_client_t`.
|
||||
* \return newly created generic client.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
typename GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_interface,
|
||||
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
|
||||
rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface,
|
||||
const std::string & name,
|
||||
const std::string & type,
|
||||
rclcpp::CallbackGroup::SharedPtr group = nullptr,
|
||||
const rcl_action_client_options_t & options = rcl_action_client_get_default_options());
|
||||
|
||||
/// Create an action generic client.
|
||||
/**
|
||||
* \param[in] node The action client will be added to this node.
|
||||
* \param[in] name The action name.
|
||||
* \param[in] type The action type.
|
||||
* \param[in] group The action client will be added to this callback group.
|
||||
* If `nullptr`, then the action client is added to the default callback group.
|
||||
* \param[in] options Options to pass to the underlying `rcl_action_client_t`.
|
||||
* \return newly created generic client.
|
||||
*/
|
||||
template<typename NodeT>
|
||||
typename GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
NodeT node,
|
||||
const std::string & name,
|
||||
const std::string & type,
|
||||
rclcpp::CallbackGroup::SharedPtr group = nullptr,
|
||||
const rcl_action_client_options_t & options = rcl_action_client_get_default_options())
|
||||
{
|
||||
return rclcpp_action::create_generic_client(
|
||||
node->get_node_base_interface(),
|
||||
node->get_node_graph_interface(),
|
||||
node->get_node_logging_interface(),
|
||||
node->get_node_waitables_interface(),
|
||||
name,
|
||||
type,
|
||||
group,
|
||||
options);
|
||||
}
|
||||
} // namespace rclcpp_action
|
||||
|
||||
#endif // RCLCPP_ACTION__CREATE_GENERIC_CLIENT_HPP_
|
||||
333
rclcpp_action/include/rclcpp_action/generic_client.hpp
Normal file
333
rclcpp_action/include/rclcpp_action/generic_client.hpp
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright 2025 Sony Group Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef RCLCPP_ACTION__GENERIC_CLIENT_HPP_
|
||||
#define RCLCPP_ACTION__GENERIC_CLIENT_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "action_msgs/srv/cancel_goal.hpp"
|
||||
#include "action_msgs/msg/goal_info.hpp"
|
||||
#include "action_msgs/msg/goal_status_array.hpp"
|
||||
|
||||
#include "rclcpp_action/client_base.hpp"
|
||||
#include "rclcpp_action/generic_client_goal_handle.hpp"
|
||||
#include "rcpputils/shared_library.hpp"
|
||||
#include "rosidl_typesupport_introspection_cpp/identifier.hpp"
|
||||
#include "rosidl_typesupport_introspection_cpp/message_introspection.hpp"
|
||||
|
||||
#include "unique_identifier_msgs/msg/uuid.hpp"
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
/// Action Generic Client
|
||||
/**
|
||||
* This class creates an action generic client.
|
||||
*
|
||||
* To create an instance of an action client use `rclcpp_action::create_generic_client()`.
|
||||
*/
|
||||
class GenericClient : public ClientBase
|
||||
{
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS_NOT_COPYABLE(GenericClient)
|
||||
|
||||
using Goal = void *; // Deserialized data pointer of goal
|
||||
using GoalRequest = void *; // Deserialized data pointer of goal request (uuid + Goal)
|
||||
using CancelRequest = typename action_msgs::srv::CancelGoal_Request;
|
||||
using CancelResponse = typename action_msgs::srv::CancelGoal_Response;
|
||||
using WrappedResult = typename GenericClientGoalHandle::WrappedResult;
|
||||
using GoalResponseCallback = std::function<void (typename GenericClientGoalHandle::SharedPtr)>;
|
||||
using FeedbackCallback = typename GenericClientGoalHandle::FeedbackCallback;
|
||||
using ResultCallback = typename GenericClientGoalHandle::ResultCallback;
|
||||
using CancelCallback = std::function<void (CancelResponse::SharedPtr)>;
|
||||
|
||||
using IntrospectionMessageMembersPtr =
|
||||
const rosidl_typesupport_introspection_cpp::MessageMembers *;
|
||||
|
||||
/// Options for sending a goal.
|
||||
/**
|
||||
* This struct is used to pass parameters to the function `async_send_goal`.
|
||||
*/
|
||||
struct SendGoalOptions
|
||||
{
|
||||
SendGoalOptions()
|
||||
: goal_response_callback(nullptr),
|
||||
feedback_callback(nullptr),
|
||||
result_callback(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/// Function called when the goal is accepted or rejected.
|
||||
/**
|
||||
* Takes a single argument that is a goal handle shared pointer.
|
||||
* If the goal is accepted, then the pointer points to a valid goal handle.
|
||||
* If the goal is rejected, then pointer has the value `nullptr`.
|
||||
*/
|
||||
GoalResponseCallback goal_response_callback;
|
||||
|
||||
/// Function called whenever feedback is received for the goal.
|
||||
FeedbackCallback feedback_callback;
|
||||
|
||||
/// Function called when the result for the goal is received.
|
||||
ResultCallback result_callback;
|
||||
};
|
||||
|
||||
/// Construct an action generic client.
|
||||
/**
|
||||
* This constructs an action generic client, but it will not work until it has been added to a
|
||||
* node.
|
||||
* Use `rclcpp_action::create_generic_client()` to both construct and add to a node.
|
||||
*
|
||||
* \param[in] node_base A pointer to the base interface of a node.
|
||||
* \param[in] node_graph A pointer to an interface that allows getting graph information about
|
||||
* a node.
|
||||
* \param[in] node_logging A pointer to an interface that allows getting a node's logger.
|
||||
* \param[in] action_name The action name.
|
||||
* \param[in] typesupport_lib A pointer to type support library for the action type
|
||||
* \param[in] action_typesupport_handle the action type support handle
|
||||
* \param[in] client_options Options to pass to the underlying `rcl_action::rcl_action_client_t`.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
GenericClient(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
|
||||
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
|
||||
const std::string & action_name,
|
||||
std::shared_ptr<rcpputils::SharedLibrary> typesupport_lib,
|
||||
const rosidl_action_type_support_t * action_typesupport_handle,
|
||||
const rcl_action_client_options_t & client_options = rcl_action_client_get_default_options());
|
||||
|
||||
/// Send an action goal and asynchronously get the result.
|
||||
/**
|
||||
* If the goal is accepted by an action server, the returned future is set to a `GenericClientGoalHandle::SharedPtr`.
|
||||
* If the goal is rejected by an action server, then the future is set to a `nullptr`.
|
||||
*
|
||||
* The goal handle in the future is used to monitor the status of the goal and get the final result.
|
||||
*
|
||||
* If callbacks were set in @param options, you will receive callbacks, as long as you hold a reference
|
||||
* to the shared pointer contained in the returned future, or rclcpp_action::GenericClient is destroyed. Dropping
|
||||
* the shared pointer to the goal handle will not cancel the goal. In order to cancel it, you must explicitly
|
||||
* call async_cancel_goal.
|
||||
*
|
||||
* WARNING this method has inconsistent behaviour and a memory leak bug.
|
||||
* If you set the result callback in @param options, the handle will be self referencing, and you will receive
|
||||
* callbacks even though you do not hold a reference to the shared pointer. In this case, the self reference will
|
||||
* be deleted if the result callback was received. If there is no result callback, there will be a memory leak.
|
||||
*
|
||||
* To prevent the memory leak, you may call stop_callbacks() explicit. This will delete the self reference.
|
||||
*
|
||||
* \param[in] goal The goal.
|
||||
* \param[in] goal_size The size of goal.
|
||||
* \param[in] options Options for sending the goal request. Contains references to callbacks for
|
||||
* the goal response (accepted/rejected), feedback, and the final result.
|
||||
* \return A future that completes when the goal has been accepted or rejected.
|
||||
* If the goal is rejected, then the result will be a `nullptr`.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_future<typename GenericClientGoalHandle::SharedPtr>
|
||||
async_send_goal(
|
||||
const Goal goal,
|
||||
size_t goal_size,
|
||||
const SendGoalOptions & options = SendGoalOptions());
|
||||
|
||||
/// Send an action goal request and asynchronously get the result.
|
||||
/**
|
||||
* \param[in] goal_request The goal request (uuid+goal).
|
||||
* \param[in] options Options for sending the goal request. Contains references to callbacks for
|
||||
* the goal response (accepted/rejected), feedback, and the final result.
|
||||
* \return A future that completes when the goal has been accepted or rejected.
|
||||
* If the goal is rejected, then the result will be a `nullptr`.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_future<typename GenericClientGoalHandle::SharedPtr>
|
||||
async_send_goal(
|
||||
const GoalRequest goal_request,
|
||||
const SendGoalOptions & options = SendGoalOptions());
|
||||
|
||||
/// Asynchronously get the result for an active goal.
|
||||
/**
|
||||
* \throws exceptions::UnknownGoalHandleError If the goal unknown or already reached a terminal
|
||||
* state, or if there was an error requesting the result.
|
||||
* \param[in] goal_handle The goal handle for which to get the result.
|
||||
* \param[in] result_callback Optional callback that is called when the result is received.
|
||||
* \return A future that is set to the goal result when the goal is finished.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_future<WrappedResult>
|
||||
async_get_result(
|
||||
typename GenericClientGoalHandle::SharedPtr goal_handle,
|
||||
ResultCallback result_callback = nullptr);
|
||||
|
||||
/// Asynchronously request a goal be canceled.
|
||||
/**
|
||||
* \throws exceptions::UnknownGoalHandleError If the goal is unknown or already reached a
|
||||
* terminal state.
|
||||
* \param[in] goal_handle The goal handle requesting to be canceled.
|
||||
* \param[in] cancel_callback Optional callback that is called when the response is received.
|
||||
* The callback takes one parameter: a shared pointer to the CancelResponse message.
|
||||
* \return A future to a CancelResponse message that is set when the request has been
|
||||
* acknowledged by an action server.
|
||||
* See
|
||||
* <a href="https://github.com/ros2/rcl_interfaces/blob/master/action_msgs/srv/CancelGoal.srv">
|
||||
* action_msgs/CancelGoal.srv</a>.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_future<typename CancelResponse::SharedPtr>
|
||||
async_cancel_goal(
|
||||
typename GenericClientGoalHandle::SharedPtr goal_handle,
|
||||
CancelCallback cancel_callback = nullptr);
|
||||
|
||||
/// Asynchronously request for all goals to be canceled.
|
||||
/**
|
||||
* \param[in] cancel_callback Optional callback that is called when the response is received.
|
||||
* The callback takes one parameter: a shared pointer to the CancelResponse message.
|
||||
* \return A future to a CancelResponse message that is set when the request has been
|
||||
* acknowledged by an action server.
|
||||
* See
|
||||
* <a href="https://github.com/ros2/rcl_interfaces/blob/master/action_msgs/srv/CancelGoal.srv">
|
||||
* action_msgs/CancelGoal.srv</a>.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_future<typename CancelResponse::SharedPtr>
|
||||
async_cancel_all_goals(CancelCallback cancel_callback = nullptr);
|
||||
|
||||
/// Stops the callbacks for the goal in a thread safe way
|
||||
/**
|
||||
* This will NOT cancel the goal, it will only stop the callbacks.
|
||||
*
|
||||
* After the call to this function, it is guaranteed that there
|
||||
* will be no more callbacks from the goal. This is not guaranteed
|
||||
* if multiple threads are involved, and the goal_handle is just
|
||||
* dropped.
|
||||
*
|
||||
* \param[in] goal_handle The goal were the callbacks shall be stopped
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
stop_callbacks(typename GenericClientGoalHandle::SharedPtr goal_handle);
|
||||
|
||||
/// Stops the callbacks for the goal in a thread safe way
|
||||
/**
|
||||
* For further information see stop_callbacks(typename GenericGoalHandle::SharedPtr goal_handle)
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
stop_callbacks(const GoalUUID & goal_id);
|
||||
|
||||
/// Asynchronously request all goals at or before a specified time be canceled.
|
||||
/**
|
||||
* \param[in] stamp The timestamp for the cancel goal request.
|
||||
* \param[in] cancel_callback Optional callback that is called when the response is received.
|
||||
* The callback takes one parameter: a shared pointer to the CancelResponse message.
|
||||
* \return A future to a CancelResponse message that is set when the request has been
|
||||
* acknowledged by an action server.
|
||||
* See
|
||||
* <a href="https://github.com/ros2/rcl_interfaces/blob/master/action_msgs/srv/CancelGoal.srv">
|
||||
* action_msgs/CancelGoal.srv</a>.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::shared_future<typename CancelResponse::SharedPtr>
|
||||
async_cancel_goals_before(
|
||||
const rclcpp::Time & stamp,
|
||||
CancelCallback cancel_callback = nullptr);
|
||||
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
~GenericClient();
|
||||
|
||||
private:
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_message(IntrospectionMessageMembersPtr message_members) const;
|
||||
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_goal_response() const override
|
||||
{
|
||||
return create_message(goal_service_response_type_members_);
|
||||
}
|
||||
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_result_request() const
|
||||
{
|
||||
return create_message(result_service_request_type_members_);
|
||||
}
|
||||
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_result_response() const override
|
||||
{
|
||||
return create_message(result_service_response_type_members_);
|
||||
}
|
||||
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_cancel_response() const override
|
||||
{
|
||||
return create_message(cancel_service_response_type_members_);
|
||||
}
|
||||
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_feedback_message() const override
|
||||
{
|
||||
return create_message(feedback_type_members_);
|
||||
}
|
||||
|
||||
/// \internal
|
||||
void
|
||||
handle_feedback_message(std::shared_ptr<void> message) override;
|
||||
|
||||
/// \internal
|
||||
std::shared_ptr<void>
|
||||
create_status_message() const override
|
||||
{
|
||||
using GoalStatusMessage = action_msgs::msg::GoalStatusArray;
|
||||
return std::shared_ptr<void>(new GoalStatusMessage());
|
||||
}
|
||||
|
||||
/// \internal
|
||||
void
|
||||
handle_status_message(std::shared_ptr<void> message) override;
|
||||
|
||||
/// \internal
|
||||
void
|
||||
make_result_aware(typename GenericClientGoalHandle::SharedPtr goal_handle);
|
||||
|
||||
/// \internal
|
||||
std::shared_future<typename CancelResponse::SharedPtr>
|
||||
async_cancel(
|
||||
typename CancelRequest::SharedPtr cancel_request,
|
||||
CancelCallback cancel_callback = nullptr);
|
||||
|
||||
std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
|
||||
IntrospectionMessageMembersPtr goal_service_request_type_members_;
|
||||
IntrospectionMessageMembersPtr goal_service_response_type_members_;
|
||||
IntrospectionMessageMembersPtr result_service_request_type_members_;
|
||||
IntrospectionMessageMembersPtr result_service_response_type_members_;
|
||||
IntrospectionMessageMembersPtr cancel_service_response_type_members_;
|
||||
IntrospectionMessageMembersPtr feedback_type_members_;
|
||||
|
||||
std::map<GoalUUID, typename GenericClientGoalHandle::WeakPtr> goal_handles_;
|
||||
std::recursive_mutex goal_handles_mutex_;
|
||||
};
|
||||
} // namespace rclcpp_action
|
||||
#endif // RCLCPP_ACTION__GENERIC_CLIENT_HPP_
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright 2025 Sony Group Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef RCLCPP_ACTION__GENERIC_CLIENT_GOAL_HANDLE_HPP_
|
||||
#define RCLCPP_ACTION__GENERIC_CLIENT_GOAL_HANDLE_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "action_msgs/msg/goal_status.hpp"
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/time.hpp"
|
||||
|
||||
#include "rclcpp_action/exceptions.hpp"
|
||||
#include "rclcpp_action/types.hpp"
|
||||
#include "rclcpp_action/visibility_control.hpp"
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
// Forward declarations
|
||||
class GenericClient;
|
||||
|
||||
class GenericClientGoalHandle
|
||||
{
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS_NOT_COPYABLE(GenericClientGoalHandle)
|
||||
|
||||
/// The possible statuses that an action goal can finish with.
|
||||
enum class ResultCode : int8_t
|
||||
{
|
||||
UNKNOWN = action_msgs::msg::GoalStatus::STATUS_UNKNOWN,
|
||||
SUCCEEDED = action_msgs::msg::GoalStatus::STATUS_SUCCEEDED,
|
||||
CANCELED = action_msgs::msg::GoalStatus::STATUS_CANCELED,
|
||||
ABORTED = action_msgs::msg::GoalStatus::STATUS_ABORTED
|
||||
};
|
||||
|
||||
// A wrapper that defines the result of an action
|
||||
struct WrappedResult
|
||||
{
|
||||
/// The unique identifier of the goal
|
||||
GoalUUID goal_id;
|
||||
/// A status to indicate if the goal was canceled, aborted, or succeeded
|
||||
ResultCode code;
|
||||
/// User defined fields sent back with an action
|
||||
const void * result;
|
||||
/// hold shared pointer for result response message.
|
||||
std::shared_ptr<void> result_response;
|
||||
};
|
||||
|
||||
using FeedbackCallback =
|
||||
std::function<void (
|
||||
typename GenericClientGoalHandle::SharedPtr,
|
||||
const void *)>;
|
||||
using ResultCallback = std::function<void (const WrappedResult & result)>;
|
||||
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
virtual
|
||||
~GenericClientGoalHandle();
|
||||
|
||||
/// Get the unique ID for the goal.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
const GoalUUID &
|
||||
get_goal_id() const;
|
||||
|
||||
/// Get the time when the goal was accepted.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
rclcpp::Time
|
||||
get_goal_stamp() const;
|
||||
|
||||
/// Get the goal status code.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
int8_t
|
||||
get_status();
|
||||
|
||||
/// Check if an action client has subscribed to feedback for the goal.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
is_feedback_aware();
|
||||
|
||||
/// Check if an action client has requested the result for the goal.
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
bool
|
||||
is_result_aware();
|
||||
|
||||
private:
|
||||
// The templated Client creates goal handles
|
||||
friend class GenericClient;
|
||||
|
||||
GenericClientGoalHandle(
|
||||
const GoalInfo & info,
|
||||
FeedbackCallback feedback_callback,
|
||||
ResultCallback result_callback);
|
||||
|
||||
void
|
||||
set_feedback_callback(FeedbackCallback callback);
|
||||
|
||||
void
|
||||
set_result_callback(ResultCallback callback);
|
||||
|
||||
void
|
||||
call_feedback_callback(
|
||||
GenericClientGoalHandle::SharedPtr shared_this,
|
||||
const void * feedback_message);
|
||||
|
||||
/// Get a future to the goal result.
|
||||
/**
|
||||
* This method should not be called if the `ignore_result` flag was set when
|
||||
* sending the original goal request (see Client::async_send_goal).
|
||||
*
|
||||
* `is_result_aware()` can be used to check if it is safe to call this method.
|
||||
*
|
||||
* \throws exceptions::UnawareGoalHandleError If the the goal handle is unaware of the result.
|
||||
* \return A future to the result.
|
||||
*/
|
||||
std::shared_future<WrappedResult>
|
||||
async_get_result();
|
||||
|
||||
/// Returns the previous value of awareness
|
||||
bool
|
||||
set_result_awareness(bool awareness);
|
||||
|
||||
void
|
||||
set_status(int8_t status);
|
||||
|
||||
void
|
||||
set_result(const WrappedResult & wrapped_result);
|
||||
|
||||
void
|
||||
invalidate(const exceptions::UnawareGoalHandleError & ex);
|
||||
|
||||
bool
|
||||
is_invalidated() const;
|
||||
|
||||
GoalInfo info_;
|
||||
|
||||
std::exception_ptr invalidate_exception_{nullptr};
|
||||
|
||||
bool is_result_aware_{false};
|
||||
std::promise<WrappedResult> result_promise_;
|
||||
std::shared_future<WrappedResult> result_future_;
|
||||
|
||||
FeedbackCallback feedback_callback_{nullptr};
|
||||
ResultCallback result_callback_{nullptr};
|
||||
int8_t status_{GoalStatus::STATUS_ACCEPTED};
|
||||
|
||||
std::recursive_mutex handle_mutex_;
|
||||
};
|
||||
} // namespace rclcpp_action
|
||||
#endif // RCLCPP_ACTION__GENERIC_CLIENT_GOAL_HANDLE_HPP_
|
||||
@@ -21,15 +21,18 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "action_msgs/srv/cancel_goal.hpp"
|
||||
#include "rcl/event_callback.h"
|
||||
#include "rcl_action/action_server.h"
|
||||
#include "rosidl_runtime_c/action_type_support_struct.h"
|
||||
#include "rosidl_typesupport_cpp/action_type_support.hpp"
|
||||
#include "rclcpp/clock.hpp"
|
||||
#include "rclcpp/node_interfaces/node_base_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_clock_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_logging_interface.hpp"
|
||||
#include "rclcpp/qos.hpp"
|
||||
#include "rclcpp/waitable.hpp"
|
||||
|
||||
#include "rclcpp_action/visibility_control.hpp"
|
||||
@@ -178,9 +181,30 @@ public:
|
||||
void
|
||||
clear_on_ready_callback() override;
|
||||
|
||||
/// Returns all timers used by this waitable
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
get_timers() const override;
|
||||
|
||||
// End Waitables API
|
||||
// -----------------
|
||||
|
||||
/// Configure action server introspection
|
||||
/**
|
||||
* \param[in] clock clock to use to generate introspection timestamps
|
||||
* \param[in] qos_service_event_pub QoS settings to use when creating the introspection publisher
|
||||
* \param[in] introspection_state the state to set introspection to
|
||||
*
|
||||
* \throw std::invalid_argument if clock is nullptr
|
||||
* \throw rclcpp::exceptions::throw_from_rcl_error if rcl error occurs.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
configure_introspection(
|
||||
rclcpp::Clock::SharedPtr clock,
|
||||
const rclcpp::QoS & qos_service_event_pub,
|
||||
rcl_service_introspection_state_t introspection_state);
|
||||
|
||||
protected:
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
ServerBase(
|
||||
@@ -495,13 +519,12 @@ protected:
|
||||
};
|
||||
|
||||
std::function<void(const GoalUUID &)> on_executing =
|
||||
[weak_this](const GoalUUID & goal_uuid)
|
||||
[weak_this]([[maybe_unused]] const GoalUUID & goal_uuid)
|
||||
{
|
||||
std::shared_ptr<Server<ActionT>> shared_this = weak_this.lock();
|
||||
if (!shared_this) {
|
||||
return;
|
||||
}
|
||||
(void)goal_uuid;
|
||||
// Publish a status message any time a goal handle changes state
|
||||
shared_this->publish_status();
|
||||
};
|
||||
|
||||
@@ -39,12 +39,22 @@ RCLCPP_ACTION_PUBLIC
|
||||
std::string
|
||||
to_string(const GoalUUID & goal_id);
|
||||
|
||||
// Convert C++ GoalID to rcl_action_goal_info_t
|
||||
/// Convert C++ GoalID to rcl_action_goal_info_t
|
||||
/**
|
||||
* \param[in] goal_id C++ GoalUUID reference to be converted.
|
||||
* \param[inout] info rcl_action_goal_info_t structure to be filled.
|
||||
* \throws std::runtime_error if info is null.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
convert(const GoalUUID & goal_id, rcl_action_goal_info_t * info);
|
||||
|
||||
// Convert rcl_action_goal_info_t to C++ GoalID
|
||||
/// Convert rcl_action_goal_info_t to C++ GoalID
|
||||
/**
|
||||
* \param[in] info rcl_action_goal_info_t reference to be converted.
|
||||
* \param[inout] goal_id C++ GoalUUID structure to be filled.
|
||||
* \throws std::runtime_error if goal_id is null.
|
||||
*/
|
||||
RCLCPP_ACTION_PUBLIC
|
||||
void
|
||||
convert(const rcl_action_goal_info_t & info, GoalUUID * goal_id);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="2">
|
||||
<name>rclcpp_action</name>
|
||||
<version>29.2.0</version>
|
||||
<version>29.4.0</version>
|
||||
<description>Adds action APIs for C++.</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
@@ -26,8 +26,6 @@
|
||||
<depend>rcl</depend>
|
||||
<depend>rcpputils</depend>
|
||||
|
||||
<depend>ament_cmake</depend>
|
||||
|
||||
<test_depend>ament_cmake_gtest</test_depend>
|
||||
<test_depend>ament_lint_auto</test_depend>
|
||||
<test_depend>ament_lint_common</test_depend>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
@@ -26,8 +25,7 @@
|
||||
#include "rclcpp/node_interfaces/node_base_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_logging_interface.hpp"
|
||||
|
||||
#include "rclcpp_action/client.hpp"
|
||||
#include "rclcpp_action/exceptions.hpp"
|
||||
#include "rclcpp_action/client_base.hpp"
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
@@ -105,6 +103,7 @@ public:
|
||||
const rcl_action_client_options_t & client_options)
|
||||
: node_graph_(node_graph),
|
||||
node_handle(node_base->get_shared_rcl_node_handle()),
|
||||
action_type_support_(type_support),
|
||||
logger(node_logging->get_logger().get_child("rclcpp_action")),
|
||||
random_bytes_generator(std::random_device{}())
|
||||
{
|
||||
@@ -167,6 +166,7 @@ public:
|
||||
// node_handle must be destroyed after client_handle to prevent memory leak
|
||||
std::shared_ptr<rcl_node_t> node_handle{nullptr};
|
||||
std::shared_ptr<rcl_action_client_t> client_handle{nullptr};
|
||||
const rosidl_action_type_support_t * action_type_support_;
|
||||
rclcpp::Logger logger;
|
||||
|
||||
using ResponseCallback = std::function<void (std::shared_ptr<void> response)>;
|
||||
@@ -643,6 +643,12 @@ ClientBase::clear_on_ready_callback()
|
||||
entity_type_to_on_ready_callback_.clear();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<rclcpp::TimerBase>>
|
||||
ClientBase::get_timers() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
ClientBase::take_data()
|
||||
{
|
||||
@@ -797,4 +803,31 @@ ClientBase::execute(const std::shared_ptr<void> & data_in)
|
||||
}, data_ptr->data);
|
||||
}
|
||||
|
||||
void
|
||||
ClientBase::configure_introspection(
|
||||
rclcpp::Clock::SharedPtr clock,
|
||||
const rclcpp::QoS & qos_service_event_pub,
|
||||
rcl_service_introspection_state_t introspection_state)
|
||||
{
|
||||
rcl_publisher_options_t pub_opts = rcl_publisher_get_default_options();
|
||||
pub_opts.qos = qos_service_event_pub.get_rmw_qos_profile();
|
||||
|
||||
if (clock == nullptr) {
|
||||
throw std::invalid_argument("clock is nullptr");
|
||||
}
|
||||
|
||||
rcl_ret_t ret = rcl_action_client_configure_action_introspection(
|
||||
pimpl_->client_handle.get(),
|
||||
pimpl_->node_handle.get(),
|
||||
clock->get_clock_handle(),
|
||||
pimpl_->action_type_support_,
|
||||
pub_opts,
|
||||
introspection_state);
|
||||
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(
|
||||
ret, "failed to configure action client introspection");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rclcpp_action
|
||||
85
rclcpp_action/src/create_generic_client.cpp
Normal file
85
rclcpp_action/src/create_generic_client.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2025 Sony Group Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "rclcpp/node.hpp"
|
||||
#include "rclcpp/typesupport_helpers.hpp"
|
||||
|
||||
#include "rclcpp_action/create_generic_client.hpp"
|
||||
#include "rclcpp_action/generic_client.hpp"
|
||||
|
||||
namespace rclcpp_action
|
||||
{
|
||||
typename GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph_interface,
|
||||
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
|
||||
rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface,
|
||||
const std::string & name,
|
||||
const std::string & type,
|
||||
rclcpp::CallbackGroup::SharedPtr group,
|
||||
const rcl_action_client_options_t & options)
|
||||
{
|
||||
std::weak_ptr<rclcpp::node_interfaces::NodeWaitablesInterface> weak_node =
|
||||
node_waitables_interface;
|
||||
std::weak_ptr<rclcpp::CallbackGroup> weak_group = group;
|
||||
bool group_is_null = (nullptr == group.get());
|
||||
|
||||
auto deleter = [weak_node, weak_group, group_is_null](GenericClient * ptr)
|
||||
{
|
||||
if (nullptr == ptr) {
|
||||
return;
|
||||
}
|
||||
auto shared_node = weak_node.lock();
|
||||
if (shared_node) {
|
||||
// API expects a shared pointer, give it one with a deleter that does nothing.
|
||||
std::shared_ptr<GenericClient> fake_shared_ptr(ptr, [](GenericClient *) {});
|
||||
|
||||
if (group_is_null) {
|
||||
// Was added to default group
|
||||
shared_node->remove_waitable(fake_shared_ptr, nullptr);
|
||||
} else {
|
||||
// Was added to a specific group
|
||||
auto shared_group = weak_group.lock();
|
||||
if (shared_group) {
|
||||
shared_node->remove_waitable(fake_shared_ptr, shared_group);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete ptr;
|
||||
};
|
||||
|
||||
auto typesupport_lib = rclcpp::get_typesupport_library(type, "rosidl_typesupport_cpp");
|
||||
auto action_typesupport_handle = rclcpp::get_action_typesupport_handle(
|
||||
type, "rosidl_typesupport_cpp", *typesupport_lib);
|
||||
|
||||
std::shared_ptr<GenericClient> action_client(
|
||||
new GenericClient(
|
||||
node_base_interface,
|
||||
node_graph_interface,
|
||||
node_logging_interface,
|
||||
name,
|
||||
typesupport_lib,
|
||||
action_typesupport_handle,
|
||||
options),
|
||||
deleter);
|
||||
|
||||
node_waitables_interface->add_waitable(action_client, group);
|
||||
return action_client;
|
||||
}
|
||||
|
||||
} // namespace rclcpp_action
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user