Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8230d15ef7 | ||
|
|
7d68b9096f | ||
|
|
eeaa5222a1 | ||
|
|
a13dc0f157 | ||
|
|
9f1de85079 | ||
|
|
5149a095c1 | ||
|
|
16795dd8bf | ||
|
|
f4923c6f43 | ||
|
|
7c096888ca | ||
|
|
d8d83a0ee6 | ||
|
|
3bc364a10b | ||
|
|
22df1d593a | ||
|
|
343b29b617 | ||
|
|
42b0b5775b | ||
|
|
f7185dc129 | ||
|
|
5f912eb58e | ||
|
|
2cd8900dd2 | ||
|
|
cf6141330a | ||
|
|
de666d2bf4 | ||
|
|
55939613a0 | ||
|
|
3d58f0fb70 | ||
|
|
3fd0831af2 | ||
|
|
d648a7c926 | ||
|
|
6bb9407b90 | ||
|
|
535d56f690 | ||
|
|
1351737f34 | ||
|
|
839348c601 | ||
|
|
90e451154c | ||
|
|
dec22a296f | ||
|
|
634cb873a3 | ||
|
|
ddc8a9c847 | ||
|
|
dd81ef26a0 | ||
|
|
bfa7efac46 | ||
|
|
63c2e2ef6f | ||
|
|
04ea0bb002 | ||
|
|
ea4c98c43f | ||
|
|
fa596801c4 | ||
|
|
3cdb25934e | ||
|
|
9171835c35 | ||
|
|
f7a7954ae7 | ||
|
|
f9c4894f96 | ||
|
|
f510db1529 | ||
|
|
c5bc4b6528 | ||
|
|
5632a09af2 | ||
|
|
9d5aaf5bae | ||
|
|
bedc547f42 | ||
|
|
1e34abd4b5 | ||
|
|
6c7764e968 | ||
|
|
42810ee3be | ||
|
|
b50f9ab418 | ||
|
|
198c82ca15 | ||
|
|
3e470d5ac2 | ||
|
|
1c350d0d7f | ||
|
|
5e14a283b6 | ||
|
|
51a4d2ea39 | ||
|
|
3df73f0e38 | ||
|
|
b007204fd8 | ||
|
|
726f2039b5 | ||
|
|
64f9aa158b | ||
|
|
e10728cde9 | ||
|
|
1981e1f12a | ||
|
|
10252e9f66 | ||
|
|
b4571076a6 | ||
|
|
091f29fcd3 | ||
|
|
38bcda4b76 | ||
|
|
c500695e21 | ||
|
|
e1f84baa11 | ||
|
|
c10764f432 | ||
|
|
99f0fc938b | ||
|
|
265f5ec297 | ||
|
|
a29f58edcb | ||
|
|
676897df9d | ||
|
|
6602fcf7bc | ||
|
|
a5221336f6 | ||
|
|
7901bb9da4 | ||
|
|
6f6b5f2e36 | ||
|
|
126d517193 |
@@ -8,7 +8,7 @@ rclcpp provides the standard C++ API for interacting with ROS 2.
|
||||
|
||||
`#include "rclcpp/rclcpp.hpp"` allows use of the most common elements of the ROS 2 system.
|
||||
|
||||
The link to the latest API documentation can be found on the rclcpp package info page, at the [ROS Index](https://index.ros.org/p/rclcpp/).
|
||||
The link to the latest API documentation can be found on the [rclcpp package info page](https://docs.ros.org/en/rolling/p/rclcpp).
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -2,6 +2,231 @@
|
||||
Changelog for package rclcpp
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
28.3.1 (2024-06-25)
|
||||
-------------------
|
||||
* Remove unnecessary msg includes in tests (`#2566 <https://github.com/ros2/rclcpp/issues/2566>`_)
|
||||
* Fix copy-paste errors in function docs (`#2565 <https://github.com/ros2/rclcpp/issues/2565>`_)
|
||||
* Fix typo in function doc (`#2563 <https://github.com/ros2/rclcpp/issues/2563>`_)
|
||||
* Contributors: Christophe Bedard
|
||||
|
||||
28.3.0 (2024-06-17)
|
||||
-------------------
|
||||
* Add test creating two content filter topics with the same topic name (`#2546 <https://github.com/ros2/rclcpp/issues/2546>`_) (`#2549 <https://github.com/ros2/rclcpp/issues/2549>`_)
|
||||
* add impl pointer for ExecutorOptions (`#2523 <https://github.com/ros2/rclcpp/issues/2523>`_)
|
||||
* Fixup Executor::spin_all() regression fix (`#2517 <https://github.com/ros2/rclcpp/issues/2517>`_)
|
||||
* Add 'mimick' label to tests which use Mimick (`#2516 <https://github.com/ros2/rclcpp/issues/2516>`_)
|
||||
* Contributors: Alejandro Hernández Cordero, Scott K Logan, William Woodall
|
||||
|
||||
28.2.0 (2024-04-26)
|
||||
-------------------
|
||||
* Check for negative time in rclcpp::Time(int64_t nanoseconds, ...) constructor (`#2510 <https://github.com/ros2/rclcpp/issues/2510>`_)
|
||||
* Revise the description of service configure_introspection() (`#2511 <https://github.com/ros2/rclcpp/issues/2511>`_)
|
||||
* Contributors: Barry Xu, Sharmin Ramli
|
||||
|
||||
28.1.0 (2024-04-16)
|
||||
-------------------
|
||||
* Remove references to index.ros.org. (`#2504 <https://github.com/ros2/rclcpp/issues/2504>`_)
|
||||
* Reduce overhead for inheriting from rclcpp::Executor when base functionality is not reused (`#2506 <https://github.com/ros2/rclcpp/issues/2506>`_)
|
||||
* Contributors: Chris Lalancette, William Woodall, jmachowinski
|
||||
|
||||
28.0.1 (2024-04-16)
|
||||
-------------------
|
||||
* [wjwwood] Updated "Data race fixes" (`#2500 <https://github.com/ros2/rclcpp/issues/2500>`_)
|
||||
* Fix callback group logic in executor
|
||||
* fix: Fixed unnecessary copy of wait_set
|
||||
* fix(executor): Fixed race conditions with rebuild of wait_sets
|
||||
Before this change, the rebuild of wait set would be triggered
|
||||
after the wait set was waken up. With bad timing, this could
|
||||
lead to the rebuild not happening with multi threaded executor.
|
||||
* fix(Executor): Fixed lost of entities rebuild request
|
||||
* chore: Added assert for not set callback_group in execute_any_executable
|
||||
* Add test for cbg getting reset
|
||||
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
|
||||
* chore: renamed test cases to snake_case
|
||||
* style
|
||||
* fixup test to avoid polling and short timeouts
|
||||
* fix: Use correct notify_waitable\_ instance
|
||||
* fix(StaticSingleThreadedExecutor): Added missing special case handling for current_notify_waitable\_
|
||||
* fix(TestCallbackGroup): Fixed test after change to timers
|
||||
---------
|
||||
Co-authored-by: Janosch Machowinski <j.machowinski@cellumation.com>
|
||||
Co-authored-by: Michael Carroll <mjcarroll@intrinsic.ai>
|
||||
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
|
||||
* fixup var names to snake case (`#2501 <https://github.com/ros2/rclcpp/issues/2501>`_)
|
||||
* Added optional TimerInfo to timer callback (`#2343 <https://github.com/ros2/rclcpp/issues/2343>`_)
|
||||
Co-authored-by: Alexis Tsogias <a.tsogias@cellumation.com>
|
||||
Co-authored-by: Janosch Machowinski <J.Machowinski@cellumation.com>
|
||||
* Fix uninitialized memory in test (`#2498 <https://github.com/ros2/rclcpp/issues/2498>`_)
|
||||
When I added in the tests for large messages, I made a mistake and reserved space in the strings, but didn't actually expand it. Thus, we were writing into uninitialized memory. Fix this by just using the correct constructor for string, which will allocate and initialize the memory properly.
|
||||
* Ensure waitables handle guard condition retriggering (`#2483 <https://github.com/ros2/rclcpp/issues/2483>`_)
|
||||
Co-authored-by: Michael Carroll <mjcarroll@intrinsic.ai>
|
||||
* fix: init concatenated_vector with begin() & end() (`#2492 <https://github.com/ros2/rclcpp/issues/2492>`_)
|
||||
* this commit will fix the warning [-Wstringop-overflow=] `#2461 <https://github.com/ros2/rclcpp/issues/2461>`_
|
||||
* Use the same context for the specified node in rclcpp::spin functions (`#2433 <https://github.com/ros2/rclcpp/issues/2433>`_)
|
||||
* Use the same conext for the specified node in rclcpp::spin_xx functions
|
||||
* Add test for spinning with non-default-context
|
||||
* Format code
|
||||
---------
|
||||
* Disable compare-function-pointers in test_utilities (`#2489 <https://github.com/ros2/rclcpp/issues/2489>`_)
|
||||
* address ambiguous auto variable. (`#2481 <https://github.com/ros2/rclcpp/issues/2481>`_)
|
||||
* Increase the cppcheck timeout to 1200 seconds (`#2484 <https://github.com/ros2/rclcpp/issues/2484>`_)
|
||||
* Removed test_timers_manager clang warning (`#2479 <https://github.com/ros2/rclcpp/issues/2479>`_)
|
||||
* Flaky timer test fix (`#2469 <https://github.com/ros2/rclcpp/issues/2469>`_)
|
||||
* fix(time_source): Fixed possible race condition
|
||||
* fix(test_executors_time_cancel_behaviour): Fixed multiple race conditions
|
||||
---------
|
||||
Co-authored-by: Janosch Machowinski <j.machowinski@nospam.org>
|
||||
* Add tracepoint for generic publisher/subscriber (`#2448 <https://github.com/ros2/rclcpp/issues/2448>`_)
|
||||
* update rclcpp::Waitable API to use references and const (`#2467 <https://github.com/ros2/rclcpp/issues/2467>`_)
|
||||
* Utilize rclcpp::WaitSet as part of the executors (`#2142 <https://github.com/ros2/rclcpp/issues/2142>`_)
|
||||
* Deprecate callback_group call taking context
|
||||
* Add base executor objects that can be used by implementors
|
||||
* Template common operations
|
||||
* Address reviewer feedback:
|
||||
* Add callback to EntitiesCollector constructor
|
||||
* Make function to check automatically added callback groups take a list
|
||||
* Lint
|
||||
* Address reviewer feedback and fix templates
|
||||
* Lint and docs
|
||||
* Make executor own the notify waitable
|
||||
* Add pending queue to collector, remove from waitable
|
||||
Also change node's get_guard_condition to return shared_ptr
|
||||
* Change interrupt guard condition to shared_ptr
|
||||
Check if guard condition is valid before adding it to the waitable
|
||||
* Lint and docs
|
||||
* Utilize rclcpp::WaitSet as part of the executors
|
||||
* Don't exchange atomic twice
|
||||
* Fix add_node and add more tests
|
||||
* Make get_notify_guard_condition follow API tick-tock
|
||||
* Improve callback group tick-tocking
|
||||
* Don't lock twice
|
||||
* Address reviewer feedback
|
||||
* Add thread safety annotations and make locks consistent
|
||||
* @wip
|
||||
* Reset callback groups for multithreaded executor
|
||||
* Avoid many small function calls when building executables
|
||||
* Re-trigger guard condition if buffer has data
|
||||
* Address reviewer feedback
|
||||
* Trace points
|
||||
* Remove tracepoints
|
||||
* Reducing diff
|
||||
* Reduce diff
|
||||
* Uncrustify
|
||||
* Restore tests
|
||||
* Back to weak_ptr and reduce test time
|
||||
* reduce diff and lint
|
||||
* Restore static single threaded tests that weren't working before
|
||||
* Restore more tests
|
||||
* Fix multithreaded test
|
||||
* Fix assert
|
||||
* Fix constructor test
|
||||
* Change ready_executables signature back
|
||||
* Don't enforce removing callback groups before nodes
|
||||
* Remove the "add_valid_node" API
|
||||
* Only notify if the trigger condition is valid
|
||||
* Only trigger if valid and needed
|
||||
* Fix spin_some/spin_all implementation
|
||||
* Restore single threaded executor
|
||||
* Picking ABI-incompatible executor changes
|
||||
* Add PIMPL
|
||||
* Additional waitset prune
|
||||
* Fix bad merge
|
||||
* Expand test timeout
|
||||
* Introduce method to clear expired entities from a collection
|
||||
* Make sure to call remove_expired_entities().
|
||||
* Prune queued work when callback group is removed
|
||||
* Prune subscriptions from dynamic storage
|
||||
* Styles fixes.
|
||||
* Re-trigger guard conditions
|
||||
* Condense to just use watiable.take_data
|
||||
* Lint
|
||||
* Address reviewer comments (nits)
|
||||
* Lock mutex when copying
|
||||
* Refactors to static single threaded based on reviewers
|
||||
* More small refactoring
|
||||
* Lint
|
||||
* Lint
|
||||
* Add ready executable accessors to WaitResult
|
||||
* Make use of accessors from wait_set
|
||||
* Fix tests
|
||||
* Fix more tests
|
||||
* Tidy up single threaded executor implementation
|
||||
* Don't null out timer, rely on call
|
||||
* change how timers are checked from wait result in executors
|
||||
* peak -> peek
|
||||
* fix bug in next_waitable logic
|
||||
* fix bug in StaticSTE that broke the add callback groups to executor tests
|
||||
* style
|
||||
---------
|
||||
Co-authored-by: Chris Lalancette <clalancette@gmail.com>
|
||||
Co-authored-by: William Woodall <william@osrfoundation.org>
|
||||
* fix flakiness in TestTimersManager unit-test (`#2468 <https://github.com/ros2/rclcpp/issues/2468>`_)
|
||||
the previous version of the test was relying on the assumption that a timer with 1ms period gets called at least 6 times if the main thread waits 15ms. this is true most of the times, but it's not guaranteed, especially when running the test on windows CI servers. the new version of the test makes no assumptions on how much time it takes for the timers manager to invoke the timers, but rather focuses on ensuring that they are called the right amount of times, which is what's important for the purpose of the test
|
||||
* Contributors: Alberto Soragna, Alejandro Hernández Cordero, Chris Lalancette, Homalozoa X, Kotaro Yoshimoto, Michael Carroll, Tomoya Fujita, William Woodall, h-suzuki-isp, jmachowinski
|
||||
|
||||
28.0.0 (2024-03-28)
|
||||
-------------------
|
||||
* fix spin_some_max_duration unit-test for events-executor (`#2465 <https://github.com/ros2/rclcpp/issues/2465>`_)
|
||||
* refactor and improve the parameterized spin_some tests for executors (`#2460 <https://github.com/ros2/rclcpp/issues/2460>`_)
|
||||
* refactor and improve the spin_some parameterized tests for executors
|
||||
* disable spin_some_max_duration for the StaticSingleThreadedExecutor and EventsExecutor
|
||||
* fixup and clarify the docstring for Executor::spin_some()
|
||||
* style
|
||||
* review comments
|
||||
---------
|
||||
* enable simulation clock for timer canceling test. (`#2458 <https://github.com/ros2/rclcpp/issues/2458>`_)
|
||||
* enable simulation clock for timer canceling test.
|
||||
* move MainExecutorTypes to test_executors_timer_cancel_behavior.cpp.
|
||||
---------
|
||||
* Revert "relax the test simulation rate for timer canceling tests. (`#2453 <https://github.com/ros2/rclcpp/issues/2453>`_)" (`#2456 <https://github.com/ros2/rclcpp/issues/2456>`_)
|
||||
This reverts commit 1c350d0d7fb9c7158e0a39057112486ddbd38e9a.
|
||||
* relax the test simulation rate for timer canceling tests. (`#2453 <https://github.com/ros2/rclcpp/issues/2453>`_)
|
||||
* Fix TypeAdapted publishing with large messages. (`#2443 <https://github.com/ros2/rclcpp/issues/2443>`_)
|
||||
Mostly by ensuring we aren't attempting to store
|
||||
large messages on the stack. Also add in tests.
|
||||
I verified that before these changes, the tests failed,
|
||||
while after them they succeed.
|
||||
* Implement generic client (`#2358 <https://github.com/ros2/rclcpp/issues/2358>`_)
|
||||
* Implement generic client
|
||||
* Fix the incorrect parameter declaration
|
||||
* Deleted copy constructor and assignment for FutureAndRequestId
|
||||
* Update codes after rebase
|
||||
* Address review comments
|
||||
* Address review comments from iuhilnehc-ynos
|
||||
* Correct an error in a description
|
||||
* Fix window build errors
|
||||
* Address review comments from William
|
||||
* Add doc strings to create_generic_client
|
||||
---------
|
||||
* Rule of five: implement move operators (`#2425 <https://github.com/ros2/rclcpp/issues/2425>`_)
|
||||
* Various cleanups to deal with uncrustify 0.78. (`#2439 <https://github.com/ros2/rclcpp/issues/2439>`_)
|
||||
These should also work with uncrustify 0.72.
|
||||
* Remove the set_deprecated signatures in any_subscription_callback. (`#2431 <https://github.com/ros2/rclcpp/issues/2431>`_)
|
||||
These have been deprecated since April 2021, so it is safe
|
||||
to remove them now.
|
||||
* fix doxygen syntax for NodeInterfaces (`#2428 <https://github.com/ros2/rclcpp/issues/2428>`_)
|
||||
* Set hints to find the python version we actually want. (`#2426 <https://github.com/ros2/rclcpp/issues/2426>`_)
|
||||
The comment in the commit explains the reasoning behind it.
|
||||
* Update quality declaration documents (`#2427 <https://github.com/ros2/rclcpp/issues/2427>`_)
|
||||
* feat: add/minus for msg::Time and rclcpp::Duration (`#2419 <https://github.com/ros2/rclcpp/issues/2419>`_)
|
||||
* feat: add/minus for msg::Time and rclcpp::Duration
|
||||
* Contributors: Alberto Soragna, Barry Xu, Chris Lalancette, Christophe Bedard, HuaTsai, Jonas Otto, Tim Clephas, Tomoya Fujita, William Woodall
|
||||
|
||||
27.0.0 (2024-02-07)
|
||||
-------------------
|
||||
* Split test_executors up into smaller chunks. (`#2421 <https://github.com/ros2/rclcpp/issues/2421>`_)
|
||||
* [events executor] - Fix Behavior with Timer Cancel (`#2375 <https://github.com/ros2/rclcpp/issues/2375>`_)
|
||||
* Removed deprecated header (`#2413 <https://github.com/ros2/rclcpp/issues/2413>`_)
|
||||
* Make sure to mark RingBuffer methods as 'override'. (`#2410 <https://github.com/ros2/rclcpp/issues/2410>`_)
|
||||
* Contributors: Alejandro Hernández Cordero, Chris Lalancette, Matt Condino
|
||||
|
||||
26.0.0 (2024-01-24)
|
||||
-------------------
|
||||
* Increase the cppcheck timeout to 600 seconds. (`#2409 <https://github.com/ros2/rclcpp/issues/2409>`_)
|
||||
* Add transient local durability support to publisher and subscriptions when using intra-process communication (`#2303 <https://github.com/ros2/rclcpp/issues/2303>`_)
|
||||
* Stop storing the context in the guard condition. (`#2400 <https://github.com/ros2/rclcpp/issues/2400>`_)
|
||||
* Contributors: Chris Lalancette, Jeffery Hsu
|
||||
|
||||
25.0.0 (2023-12-26)
|
||||
-------------------
|
||||
* Updated GenericSubscription to AnySubscriptionCallback (`#1928 <https://github.com/ros2/rclcpp/issues/1928>`_)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(rclcpp)
|
||||
|
||||
@@ -45,7 +45,9 @@ set(${PROJECT_NAME}_SRCS
|
||||
src/rclcpp/clock.cpp
|
||||
src/rclcpp/context.cpp
|
||||
src/rclcpp/contexts/default_context.cpp
|
||||
src/rclcpp/create_generic_client.cpp
|
||||
src/rclcpp/detail/add_guard_condition_to_rcl_wait_set.cpp
|
||||
src/rclcpp/detail/resolve_intra_process_buffer_type.cpp
|
||||
src/rclcpp/detail/resolve_parameter_overrides.cpp
|
||||
src/rclcpp/detail/rmw_implementation_specific_payload.cpp
|
||||
src/rclcpp/detail/rmw_implementation_specific_publisher_payload.cpp
|
||||
@@ -61,18 +63,19 @@ set(${PROJECT_NAME}_SRCS
|
||||
src/rclcpp/exceptions/exceptions.cpp
|
||||
src/rclcpp/executable_list.cpp
|
||||
src/rclcpp/executor.cpp
|
||||
src/rclcpp/executor_options.cpp
|
||||
src/rclcpp/executors.cpp
|
||||
src/rclcpp/executors/executor_entities_collection.cpp
|
||||
src/rclcpp/executors/executor_entities_collector.cpp
|
||||
src/rclcpp/executors/executor_notify_waitable.cpp
|
||||
src/rclcpp/executors/multi_threaded_executor.cpp
|
||||
src/rclcpp/executors/single_threaded_executor.cpp
|
||||
src/rclcpp/executors/static_executor_entities_collector.cpp
|
||||
src/rclcpp/executors/static_single_threaded_executor.cpp
|
||||
src/rclcpp/expand_topic_or_service_name.cpp
|
||||
src/rclcpp/experimental/executors/events_executor/events_executor.cpp
|
||||
src/rclcpp/experimental/timers_manager.cpp
|
||||
src/rclcpp/future_return_code.cpp
|
||||
src/rclcpp/generic_client.cpp
|
||||
src/rclcpp/generic_publisher.cpp
|
||||
src/rclcpp/generic_subscription.cpp
|
||||
src/rclcpp/graph_listener.cpp
|
||||
@@ -126,6 +129,21 @@ set(${PROJECT_NAME}_SRCS
|
||||
src/rclcpp/waitable.cpp
|
||||
)
|
||||
|
||||
# By default, without the settings below, find_package(Python3) will attempt
|
||||
# to find the newest python version it can, and additionally will find the
|
||||
# most specific version. For instance, on a system that has
|
||||
# /usr/bin/python3.10, /usr/bin/python3.11, and /usr/bin/python3, it will find
|
||||
# /usr/bin/python3.11, even if /usr/bin/python3 points to /usr/bin/python3.10.
|
||||
# The behavior we want is to prefer the "system" installed version unless the
|
||||
# user specifically tells us othewise through the Python3_EXECUTABLE hint.
|
||||
# Setting CMP0094 to NEW means that the search will stop after the first
|
||||
# python version is found. Setting Python3_FIND_UNVERSIONED_NAMES means that
|
||||
# the search will prefer /usr/bin/python3 over /usr/bin/python3.11. And that
|
||||
# latter functionality is only available in CMake 3.20 or later, so we need
|
||||
# at least that version.
|
||||
cmake_policy(SET CMP0094 NEW)
|
||||
set(Python3_FIND_UNVERSIONED_NAMES FIRST)
|
||||
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
|
||||
# "watch" template for changes
|
||||
@@ -281,7 +299,7 @@ install(
|
||||
|
||||
if(TEST cppcheck)
|
||||
# must set the property after ament_package()
|
||||
set_tests_properties(cppcheck PROPERTIES TIMEOUT 500)
|
||||
set_tests_properties(cppcheck PROPERTIES TIMEOUT 1200)
|
||||
endif()
|
||||
|
||||
if(TEST cpplint)
|
||||
|
||||
@@ -4,7 +4,7 @@ This document is a declaration of software quality for the `rclcpp` package, bas
|
||||
|
||||
The package `rclcpp` claims to be in the **Quality Level 1** category when it is used with a **Quality Level 1** middleware.
|
||||
|
||||
Below are the rationales, notes, and caveats for this claim, organized by each requirement listed in the [Package Quality Categories in REP-2004](https://www.ros.org/reps/rep-2004.html) of the ROS2 developer guide.
|
||||
Below are the rationales, notes, and caveats for this claim, organized by each requirement listed in the [Package Requirements for Quality Level 1 in REP-2004](https://www.ros.org/reps/rep-2004.html).
|
||||
|
||||
## Version Policy [1]
|
||||
|
||||
@@ -94,7 +94,7 @@ There is an automated test which runs a linter that ensures each file has at lea
|
||||
|
||||
### Feature Testing [4.i]
|
||||
|
||||
Each feature in `rclcpp` has corresponding tests which simulate typical usage, and they are located in the [`test`](https://github.com/ros2/rclcpp/tree/master/test) directory.
|
||||
Each feature in `rclcpp` has corresponding tests which simulate typical usage, and they are located in the [`test`](https://github.com/ros2/rclcpp/tree/rolling/test) directory.
|
||||
New features are required to have tests before being added.
|
||||
|
||||
Currently nightly test results can be seen here:
|
||||
@@ -129,7 +129,7 @@ Current coverage statistics can be viewed [here](https://ci.ros2.org/job/nightly
|
||||
|
||||
`rclcpp` follows the recommendations for performance testing of C/C++ code in the [ROS 2 Developer Guide](https://docs.ros.org/en/rolling/Contributing/Developer-Guide.html#performance), and opts to do performance analysis on each release rather than each change.
|
||||
|
||||
The performance tests of `rclcpp` are located in the [test/benchmark directory](https://github.com/ros2/rclcpp/tree/master/rclcpp/test/benchmark).
|
||||
The performance tests of `rclcpp` are located in the [test/benchmark directory](https://github.com/ros2/rclcpp/tree/rolling/rclcpp/test/benchmark).
|
||||
|
||||
System level performance benchmarks that cover features of `rclcpp` can be found at:
|
||||
* [Benchmarks](http://build.ros2.org/view/Rci/job/Rci__benchmark_ubuntu_focal_amd64/BenchmarkTable/)
|
||||
@@ -139,7 +139,7 @@ Changes that introduce regressions in performance must be adequately justified i
|
||||
|
||||
### Linters and Static Analysis [4.v]
|
||||
|
||||
`rclcpp` uses and passes all the ROS2 standard linters and static analysis tools for a C++ package as described in the [ROS 2 Developer Guide](https://docs.ros.org/en/rolling/Contributing/Developer-Guide.html#linters-and-static-analysis). Passing implies there are no linter/static errors when testing against CI of supported platforms.
|
||||
`rclcpp` uses and passes all the ROS 2 standard linters and static analysis tools for a C++ package as described in the [ROS 2 Developer Guide](https://docs.ros.org/en/rolling/Contributing/Developer-Guide.html#linters-and-static-analysis). Passing implies there are no linter/static errors when testing against CI of supported platforms.
|
||||
|
||||
Currently nightly test results can be seen here:
|
||||
* [linux-aarch64_release](https://ci.ros2.org/view/nightly/job/nightly_linux-aarch64_release/lastBuild/testReport/rclcpp/)
|
||||
@@ -163,49 +163,49 @@ It also has several test dependencies, which do not affect the resulting quality
|
||||
|
||||
The `libstatistics_collector` package provides lightweight aggregation utilities to collect statistics and measure message metrics.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros-tooling/libstatistics_collector/tree/master/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros-tooling/libstatistics_collector/tree/rolling/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `rcl`
|
||||
|
||||
`rcl` a library to support implementation of language specific ROS 2 Client Libraries.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcl/blob/master/rcl/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcl/blob/rolling/rcl/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `rcl_yaml_param_parser`
|
||||
|
||||
The `rcl_yaml_param_parser` package provides an API that is used to parse YAML configuration files which may be used to configure ROS and specific nodes.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcl/tree/master/rcl_yaml_param_parser/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcl/tree/rolling/rcl_yaml_param_parser/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `rcpputils`
|
||||
|
||||
The `rcpputils` package provides an API which contains common utilities and data structures useful when programming in C++.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcpputils/blob/master/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcpputils/blob/rolling/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `rcutils`
|
||||
|
||||
The `rcutils` package provides an API which contains common utilities and data structures useful when programming in C.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcutils/blob/master/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcutils/blob/rolling/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `rmw`
|
||||
|
||||
`rmw` is the ROS 2 middleware library.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rmw/blob/master/rmw/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rmw/blob/rolling/rmw/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `statistics_msgs`
|
||||
|
||||
The `statistics_msgs` package contains ROS 2 message definitions for reporting statistics for topics and system resources.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcl_interfaces/blob/master/statistics_msgs/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/rcl_interfaces/blob/rolling/statistics_msgs/QUALITY_DECLARATION.md).
|
||||
|
||||
#### `tracetools`
|
||||
|
||||
The `tracetools` package provides utilities for instrumenting the code in `rclcpp` so that it may be traced for debugging and performance analysis.
|
||||
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://gitlab.com/ros-tracing/ros2_tracing/-/blob/master/tracetools/QUALITY_DECLARATION.md).
|
||||
It is **Quality Level 1**, see its [Quality Declaration document](https://github.com/ros2/ros2_tracing/blob/rolling/tracetools/QUALITY_DECLARATION.md).
|
||||
|
||||
### Direct Runtime non-ROS Dependency [5.iii]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The ROS client library in C++.
|
||||
|
||||
The link to the latest rclcpp API documentation, which includes a complete list of its main components and features, can be found on the rclcpp package info page, at the [ROS Index](https://index.ros.org/p/rclcpp/).
|
||||
The link to the latest rclcpp API documentation, which includes a complete list of its main components and features, can be found on the [rclcpp package info page](https://docs.ros.org/en/rolling/p/rclcpp).
|
||||
|
||||
## Quality Declaration
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ struct AnyExecutable
|
||||
rclcpp::ClientBase::SharedPtr client;
|
||||
rclcpp::Waitable::SharedPtr waitable;
|
||||
// These are used to keep the scope on the containing items
|
||||
rclcpp::CallbackGroup::SharedPtr callback_group;
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base;
|
||||
std::shared_ptr<void> data;
|
||||
rclcpp::CallbackGroup::SharedPtr callback_group {nullptr};
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base {nullptr};
|
||||
std::shared_ptr<void> data {nullptr};
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include "rclcpp/serialized_message.hpp"
|
||||
#include "rclcpp/type_adapter.hpp"
|
||||
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
@@ -394,58 +393,12 @@ public:
|
||||
// converted to one another, e.g. shared_ptr and unique_ptr.
|
||||
using scbth = detail::SubscriptionCallbackTypeHelper<MessageT, CallbackT>;
|
||||
|
||||
// Determine if the given CallbackT is a deprecated signature or not.
|
||||
constexpr auto is_deprecated =
|
||||
rclcpp::function_traits::same_arguments<
|
||||
typename scbth::callback_type,
|
||||
std::function<void(std::shared_ptr<MessageT>)>
|
||||
>::value
|
||||
||
|
||||
rclcpp::function_traits::same_arguments<
|
||||
typename scbth::callback_type,
|
||||
std::function<void(std::shared_ptr<MessageT>, const rclcpp::MessageInfo &)>
|
||||
>::value;
|
||||
|
||||
// Use the discovered type to force the type of callback when assigning
|
||||
// into the variant.
|
||||
if constexpr (is_deprecated) {
|
||||
// If deprecated, call sub-routine that is deprecated.
|
||||
set_deprecated(static_cast<typename scbth::callback_type>(callback));
|
||||
} else {
|
||||
// Otherwise just assign it.
|
||||
callback_variant_ = static_cast<typename scbth::callback_type>(callback);
|
||||
}
|
||||
callback_variant_ = static_cast<typename scbth::callback_type>(callback);
|
||||
|
||||
// Return copy of self for easier testing, normally will be compiled out.
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Function for shared_ptr to non-const MessageT, which is deprecated.
|
||||
template<typename SetT>
|
||||
#if !defined(RCLCPP_AVOID_DEPRECATIONS_FOR_UNIT_TESTS)
|
||||
// suppress deprecation warnings in `test_any_subscription_callback.cpp`
|
||||
[[deprecated("use 'void(std::shared_ptr<const MessageT>)' instead")]]
|
||||
#endif
|
||||
void
|
||||
set_deprecated(std::function<void(std::shared_ptr<SetT>)> callback)
|
||||
{
|
||||
callback_variant_ = callback;
|
||||
}
|
||||
|
||||
/// Function for shared_ptr to non-const MessageT with MessageInfo, which is deprecated.
|
||||
template<typename SetT>
|
||||
#if !defined(RCLCPP_AVOID_DEPRECATIONS_FOR_UNIT_TESTS)
|
||||
// suppress deprecation warnings in `test_any_subscription_callback.cpp`
|
||||
[[deprecated(
|
||||
"use 'void(std::shared_ptr<const MessageT>, const rclcpp::MessageInfo &)' instead"
|
||||
)]]
|
||||
#endif
|
||||
void
|
||||
set_deprecated(std::function<void(std::shared_ptr<SetT>, const rclcpp::MessageInfo &)> callback)
|
||||
{
|
||||
callback_variant_ = callback;
|
||||
}
|
||||
|
||||
std::unique_ptr<ROSMessageType, ROSMessageTypeDeleter>
|
||||
create_ros_unique_ptr_from_ros_shared_ptr_message(
|
||||
const std::shared_ptr<const ROSMessageType> & message)
|
||||
|
||||
@@ -129,8 +129,7 @@ public:
|
||||
* added to the executor in either case.
|
||||
*
|
||||
* \param[in] group_type The type of the callback group.
|
||||
* \param[in] get_node_context Lambda to retrieve the node context when
|
||||
* checking that the creating node is valid and using the guard condition.
|
||||
* \param[in] context A weak pointer to the context associated with this callback group.
|
||||
* \param[in] automatically_add_to_executor_with_node A boolean that
|
||||
* determines whether a callback group is automatically added to an executor
|
||||
* with the node with which it is associated.
|
||||
@@ -138,7 +137,7 @@ public:
|
||||
RCLCPP_PUBLIC
|
||||
explicit CallbackGroup(
|
||||
CallbackGroupType group_type,
|
||||
std::function<rclcpp::Context::SharedPtr(void)> get_node_context,
|
||||
rclcpp::Context::WeakPtr context,
|
||||
bool automatically_add_to_executor_with_node = true);
|
||||
|
||||
/// Default destructor.
|
||||
@@ -185,18 +184,41 @@ public:
|
||||
* \return the number of entities in the callback group.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
size_t size() const;
|
||||
size_t
|
||||
size() const;
|
||||
|
||||
/// Return a reference to the 'can be taken' atomic boolean.
|
||||
/**
|
||||
* The resulting bool will be true in the case that no executor is currently
|
||||
* using an executable entity from this group.
|
||||
* The resulting bool will be false in the case that an executor is currently
|
||||
* using an executable entity from this group, and the group policy doesn't
|
||||
* allow a second take (eg mutual exclusion)
|
||||
* \return a reference to the flag
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::atomic_bool &
|
||||
can_be_taken_from();
|
||||
|
||||
/// Get the group type.
|
||||
/**
|
||||
* \return the group type
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
const CallbackGroupType &
|
||||
type() const;
|
||||
|
||||
/// Collect all of the entity pointers contained in this callback group.
|
||||
/**
|
||||
* \param[in] sub_func Function to execute for each subscription
|
||||
* \param[in] service_func Function to execute for each service
|
||||
* \param[in] client_func Function to execute for each client
|
||||
* \param[in] timer_func Function to execute for each timer
|
||||
* \param[in] waitable_fuinc Function to execute for each waitable
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void collect_all_ptrs(
|
||||
void
|
||||
collect_all_ptrs(
|
||||
std::function<void(const rclcpp::SubscriptionBase::SharedPtr &)> sub_func,
|
||||
std::function<void(const rclcpp::ServiceBase::SharedPtr &)> service_func,
|
||||
std::function<void(const rclcpp::ClientBase::SharedPtr &)> client_func,
|
||||
@@ -228,16 +250,6 @@ public:
|
||||
bool
|
||||
automatically_add_to_executor_with_node() const;
|
||||
|
||||
/// Retrieve the guard condition used to signal changes to this callback group.
|
||||
/**
|
||||
* \param[in] context_ptr context to use when creating the guard condition
|
||||
* \return guard condition if it is valid, otherwise nullptr.
|
||||
*/
|
||||
[[deprecated("Use get_notify_guard_condition() without arguments")]]
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::GuardCondition::SharedPtr
|
||||
get_notify_guard_condition(const rclcpp::Context::SharedPtr context_ptr);
|
||||
|
||||
/// Retrieve the guard condition used to signal changes to this callback group.
|
||||
/**
|
||||
* \return guard condition if it is valid, otherwise nullptr.
|
||||
@@ -297,7 +309,7 @@ protected:
|
||||
std::shared_ptr<rclcpp::GuardCondition> notify_guard_condition_ = nullptr;
|
||||
std::recursive_mutex notify_guard_condition_mutex_;
|
||||
|
||||
std::function<rclcpp::Context::SharedPtr(void)> get_context_;
|
||||
rclcpp::Context::WeakPtr context_;
|
||||
|
||||
private:
|
||||
template<typename TypeT, typename Function>
|
||||
|
||||
@@ -115,6 +115,29 @@ struct FutureAndRequestId
|
||||
/// Destructor.
|
||||
~FutureAndRequestId() = default;
|
||||
};
|
||||
|
||||
template<typename PendingRequestsT, typename AllocatorT = std::allocator<int64_t>>
|
||||
size_t
|
||||
prune_requests_older_than_impl(
|
||||
PendingRequestsT & pending_requests,
|
||||
std::mutex & pending_requests_mutex,
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point,
|
||||
std::vector<int64_t, AllocatorT> * pruned_requests = nullptr)
|
||||
{
|
||||
std::lock_guard guard(pending_requests_mutex);
|
||||
auto old_size = pending_requests.size();
|
||||
for (auto it = pending_requests.begin(), last = pending_requests.end(); it != last; ) {
|
||||
if (it->second.first < time_point) {
|
||||
if (pruned_requests) {
|
||||
pruned_requests->push_back(it->first);
|
||||
}
|
||||
it = pending_requests.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return old_size - pending_requests.size();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
namespace node_interfaces
|
||||
@@ -467,7 +490,7 @@ public:
|
||||
* \param[in] node_base NodeBaseInterface pointer that is used in part of the setup.
|
||||
* \param[in] node_graph The node graph interface of the corresponding node.
|
||||
* \param[in] service_name Name of the topic to publish to.
|
||||
* \param[in] client_options options for the subscription.
|
||||
* \param[in] client_options options for the client.
|
||||
*/
|
||||
Client(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
@@ -771,19 +794,11 @@ public:
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point,
|
||||
std::vector<int64_t, AllocatorT> * pruned_requests = nullptr)
|
||||
{
|
||||
std::lock_guard guard(pending_requests_mutex_);
|
||||
auto old_size = pending_requests_.size();
|
||||
for (auto it = pending_requests_.begin(), last = pending_requests_.end(); it != last; ) {
|
||||
if (it->second.first < time_point) {
|
||||
if (pruned_requests) {
|
||||
pruned_requests->push_back(it->first);
|
||||
}
|
||||
it = pending_requests_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return old_size - pending_requests_.size();
|
||||
return detail::prune_requests_older_than_impl(
|
||||
pending_requests_,
|
||||
pending_requests_mutex_,
|
||||
time_point,
|
||||
pruned_requests);
|
||||
}
|
||||
|
||||
/// Configure client introspection.
|
||||
@@ -849,7 +864,7 @@ protected:
|
||||
"Received invalid sequence number. Ignoring...");
|
||||
return std::nullopt;
|
||||
}
|
||||
auto value = std::move(it->second.second);
|
||||
std::optional<CallbackInfoVariant> value = std::move(it->second.second);
|
||||
this->pending_requests_.erase(request_number);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -193,6 +193,16 @@ public:
|
||||
bool
|
||||
ros_time_is_active();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
cancel_sleep_or_wait();
|
||||
|
||||
/// Return the rcl_clock_t clock handle
|
||||
RCLCPP_PUBLIC
|
||||
rcl_clock_t *
|
||||
@@ -207,13 +217,13 @@ public:
|
||||
std::mutex &
|
||||
get_clock_mutex() noexcept;
|
||||
|
||||
// Add a callback to invoke if the jump threshold is exceeded.
|
||||
/// Add a callback to invoke if the jump threshold is exceeded.
|
||||
/**
|
||||
* These callback functions must remain valid as long as the
|
||||
* returned shared pointer is valid.
|
||||
*
|
||||
* Function will register callbacks to the callback queue. On time jump all
|
||||
* callbacks will be executed whose threshold is greater then the time jump;
|
||||
* callbacks will be executed whose threshold is greater than the time jump;
|
||||
* The logic will first call selected pre_callbacks and then all selected
|
||||
* post_callbacks.
|
||||
*
|
||||
@@ -222,7 +232,7 @@ public:
|
||||
* \param pre_callback Must be non-throwing
|
||||
* \param post_callback Must be non-throwing.
|
||||
* \param threshold Callbacks will be triggered if the time jump is greater
|
||||
* then the threshold.
|
||||
* than the threshold.
|
||||
* \throws anything rclcpp::exceptions::throw_from_rcl_error can throw.
|
||||
* \throws std::bad_alloc if the allocation of the JumpHandler fails.
|
||||
* \warning the instance of the clock must remain valid as long as any created
|
||||
|
||||
90
rclcpp/include/rclcpp/create_generic_client.hpp
Normal file
90
rclcpp/include/rclcpp/create_generic_client.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2023 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__CREATE_GENERIC_CLIENT_HPP_
|
||||
#define RCLCPP__CREATE_GENERIC_CLIENT_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "rclcpp/generic_client.hpp"
|
||||
#include "rclcpp/node_interfaces/get_node_base_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/get_node_graph_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/get_node_services_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_base_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_graph_interface.hpp"
|
||||
#include "rclcpp/node_interfaces/node_services_interface.hpp"
|
||||
#include "rclcpp/qos.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
/// Create a generic service client with a name of given type.
|
||||
/**
|
||||
* \param[in] node_base NodeBaseInterface implementation of the node on which
|
||||
* to create the client.
|
||||
* \param[in] node_graph NodeGraphInterface implementation of the node on which
|
||||
* to create the client.
|
||||
* \param[in] node_services NodeServicesInterface implementation of the node on
|
||||
* which to create the client.
|
||||
* \param[in] service_name The name on which the service is accessible.
|
||||
* \param[in] service_type The name of service type, e.g. "test_msgs/srv/BasicTypes"
|
||||
* \param[in] qos Quality of service profile for client.
|
||||
* \param[in] group Callback group to handle the reply to service calls.
|
||||
* \return Shared pointer to the created client.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
std::shared_ptr<node_interfaces::NodeBaseInterface> node_base,
|
||||
std::shared_ptr<node_interfaces::NodeGraphInterface> node_graph,
|
||||
std::shared_ptr<node_interfaces::NodeServicesInterface> node_services,
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
const rclcpp::QoS & qos = rclcpp::ServicesQoS(),
|
||||
rclcpp::CallbackGroup::SharedPtr group = nullptr);
|
||||
|
||||
/// Create a generic service client with a name of given type.
|
||||
/**
|
||||
* The NodeT type needs to have NodeBaseInterface implementation, NodeGraphInterface implementation
|
||||
* and NodeServicesInterface implementation of the node which to create the client.
|
||||
*
|
||||
* \param[in] node The node on which to create the client.
|
||||
* \param[in] service_name The name on which the service is accessible.
|
||||
* \param[in] service_type The name of service type, e.g. "test_msgs/srv/BasicTypes"
|
||||
* \param[in] qos Quality of service profile for client.
|
||||
* \param[in] group Callback group to handle the reply to service calls.
|
||||
* \return Shared pointer to the created client.
|
||||
*/
|
||||
template<typename NodeT>
|
||||
rclcpp::GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
NodeT node,
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
const rclcpp::QoS & qos = rclcpp::ServicesQoS(),
|
||||
rclcpp::CallbackGroup::SharedPtr group = nullptr)
|
||||
{
|
||||
return create_generic_client(
|
||||
rclcpp::node_interfaces::get_node_base_interface(node),
|
||||
rclcpp::node_interfaces::get_node_graph_interface(node),
|
||||
rclcpp::node_interfaces::get_node_services_interface(node),
|
||||
service_name,
|
||||
service_type,
|
||||
qos,
|
||||
group
|
||||
);
|
||||
}
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__CREATE_GENERIC_CLIENT_HPP_
|
||||
@@ -47,6 +47,11 @@ resolve_intra_process_buffer_type(
|
||||
return resolved_buffer_type;
|
||||
}
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::IntraProcessBufferType
|
||||
resolve_intra_process_buffer_type(
|
||||
const rclcpp::IntraProcessBufferType buffer_type);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <chrono>
|
||||
|
||||
#include "builtin_interfaces/msg/duration.hpp"
|
||||
#include "builtin_interfaces/msg/time.hpp"
|
||||
#include "rcl/time.h"
|
||||
#include "rclcpp/visibility_control.hpp"
|
||||
|
||||
@@ -158,6 +159,14 @@ private:
|
||||
Duration() = default;
|
||||
};
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
builtin_interfaces::msg::Time
|
||||
operator+(const builtin_interfaces::msg::Time & lhs, const rclcpp::Duration & rhs);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
builtin_interfaces::msg::Time
|
||||
operator-(const builtin_interfaces::msg::Time & lhs, const rclcpp::Duration & rhs);
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__DURATION_HPP_
|
||||
|
||||
@@ -117,12 +117,12 @@ public:
|
||||
/// Add the Waitable to a wait set.
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set) override;
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// Check if the Waitable is ready.
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) override;
|
||||
is_ready(const rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// Set a callback to be called when each new event instance occurs.
|
||||
/**
|
||||
@@ -294,7 +294,7 @@ public:
|
||||
|
||||
/// Execute any entities of the Waitable that are ready.
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) override
|
||||
execute(const std::shared_ptr<void> & data) override
|
||||
{
|
||||
if (!data) {
|
||||
throw std::runtime_error("'data' is empty");
|
||||
|
||||
@@ -29,26 +29,24 @@
|
||||
|
||||
#include "rcl/guard_condition.h"
|
||||
#include "rcl/wait.h"
|
||||
#include "rclcpp/executors/executor_notify_waitable.hpp"
|
||||
#include "rcpputils/scope_exit.hpp"
|
||||
|
||||
#include "rclcpp/context.hpp"
|
||||
#include "rclcpp/contexts/default_context.hpp"
|
||||
#include "rclcpp/guard_condition.hpp"
|
||||
#include "rclcpp/executor_options.hpp"
|
||||
#include "rclcpp/executors/executor_entities_collection.hpp"
|
||||
#include "rclcpp/executors/executor_entities_collector.hpp"
|
||||
#include "rclcpp/future_return_code.hpp"
|
||||
#include "rclcpp/memory_strategies.hpp"
|
||||
#include "rclcpp/memory_strategy.hpp"
|
||||
#include "rclcpp/node_interfaces/node_base_interface.hpp"
|
||||
#include "rclcpp/utilities.hpp"
|
||||
#include "rclcpp/visibility_control.hpp"
|
||||
#include "rclcpp/wait_set.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
typedef std::map<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr,
|
||||
std::owner_less<rclcpp::CallbackGroup::WeakPtr>> WeakCallbackGroupsToNodesMap;
|
||||
|
||||
// Forward declaration is used in convenience method signature.
|
||||
class Node;
|
||||
class ExecutorImplementation;
|
||||
@@ -274,24 +272,31 @@ public:
|
||||
* \param[in] node Shared pointer to the node to add.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
virtual void
|
||||
spin_node_some(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node);
|
||||
|
||||
/// Convenience function which takes Node and forwards NodeBaseInterface.
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
virtual void
|
||||
spin_node_some(std::shared_ptr<rclcpp::Node> node);
|
||||
|
||||
/// Collect work once and execute all available work, optionally within a duration.
|
||||
/// Collect work once and execute all available work, optionally within a max duration.
|
||||
/**
|
||||
* This function can be overridden. The default implementation is suitable for a
|
||||
* single-threaded model of execution.
|
||||
* Adding subscriptions, timers, services, etc. with blocking callbacks will cause this function
|
||||
* to block (which may have unintended consequences).
|
||||
* This function can be overridden.
|
||||
* The default implementation is suitable for a single-threaded model of execution.
|
||||
* Adding subscriptions, timers, services, etc. with blocking or long running
|
||||
* callbacks may cause the function exceed the max_duration significantly.
|
||||
*
|
||||
* 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
|
||||
* max_duration has been exceeded, and if it has it returns without starting
|
||||
* the execution of the next piece of work.
|
||||
*
|
||||
* If a max_duration of 0 is given, then all of the collected work will be
|
||||
* executed before the function returns.
|
||||
*
|
||||
* \param[in] max_duration The maximum amount of time to spend executing work, or 0 for no limit.
|
||||
* Note that spin_some() may take longer than this time as it only returns once max_duration has
|
||||
* been exceeded.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual void
|
||||
@@ -302,14 +307,14 @@ public:
|
||||
* \param[in] node Shared pointer to the node to add.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
virtual void
|
||||
spin_node_all(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node,
|
||||
std::chrono::nanoseconds max_duration);
|
||||
|
||||
/// Convenience function which takes Node and forwards NodeBaseInterface.
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
virtual void
|
||||
spin_node_all(std::shared_ptr<rclcpp::Node> node, std::chrono::nanoseconds max_duration);
|
||||
|
||||
/// Collect and execute work repeatedly within a duration or until no more work is available.
|
||||
@@ -361,52 +366,12 @@ public:
|
||||
const FutureT & future,
|
||||
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
|
||||
{
|
||||
// TODO(wjwwood): does not work recursively; can't call spin_node_until_future_complete
|
||||
// inside a callback executed by an executor.
|
||||
|
||||
// Check the future before entering the while loop.
|
||||
// If the future is already complete, don't try to spin.
|
||||
std::future_status status = future.wait_for(std::chrono::seconds(0));
|
||||
if (status == std::future_status::ready) {
|
||||
return FutureReturnCode::SUCCESS;
|
||||
}
|
||||
|
||||
auto end_time = std::chrono::steady_clock::now();
|
||||
std::chrono::nanoseconds timeout_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
timeout);
|
||||
if (timeout_ns > std::chrono::nanoseconds::zero()) {
|
||||
end_time += timeout_ns;
|
||||
}
|
||||
std::chrono::nanoseconds timeout_left = timeout_ns;
|
||||
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin_until_future_complete() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
while (rclcpp::ok(this->context_) && spinning.load()) {
|
||||
// Do one item of work.
|
||||
spin_once_impl(timeout_left);
|
||||
|
||||
// Check if the future is set, return SUCCESS if it is.
|
||||
status = future.wait_for(std::chrono::seconds(0));
|
||||
if (status == std::future_status::ready) {
|
||||
return FutureReturnCode::SUCCESS;
|
||||
return spin_until_future_complete_impl(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(timeout),
|
||||
[&future](std::chrono::nanoseconds wait_time) {
|
||||
return future.wait_for(wait_time);
|
||||
}
|
||||
// If the original timeout is < 0, then this is blocking, never TIMEOUT.
|
||||
if (timeout_ns < std::chrono::nanoseconds::zero()) {
|
||||
continue;
|
||||
}
|
||||
// Otherwise check if we still have time to wait, return TIMEOUT if not.
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now >= end_time) {
|
||||
return FutureReturnCode::TIMEOUT;
|
||||
}
|
||||
// Subtract the elapsed time from the original timeout.
|
||||
timeout_left = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - now);
|
||||
}
|
||||
|
||||
// The future did not complete before ok() returned false, return INTERRUPTED.
|
||||
return FutureReturnCode::INTERRUPTED;
|
||||
);
|
||||
}
|
||||
|
||||
/// Cancel any running spin* function, causing it to return.
|
||||
@@ -415,20 +380,9 @@ public:
|
||||
* \throws std::runtime_error if there is an issue triggering the guard condition
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
virtual void
|
||||
cancel();
|
||||
|
||||
/// Support dynamic switching of the memory strategy.
|
||||
/**
|
||||
* Switching the memory strategy while the executor is spinning in another threading could have
|
||||
* unintended consequences.
|
||||
* \param[in] memory_strategy Shared pointer to the memory strategy to set.
|
||||
* \throws std::runtime_error if memory_strategy is null
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
set_memory_strategy(memory_strategy::MemoryStrategy::SharedPtr memory_strategy);
|
||||
|
||||
/// Returns true if the executor is currently spinning.
|
||||
/**
|
||||
* This function can be called asynchronously from any thread.
|
||||
@@ -439,6 +393,14 @@ public:
|
||||
is_spinning();
|
||||
|
||||
protected:
|
||||
/// Constructor that will not initialize any non-trivial members.
|
||||
/**
|
||||
* This constructor is intended to be used by any derived executor
|
||||
* that explicitly does not want to use the default implementation provided
|
||||
* by this class.
|
||||
*/
|
||||
explicit Executor(const std::shared_ptr<rclcpp::Context> & context);
|
||||
|
||||
/// Add a node to executor, execute the next available unit of work, and remove the node.
|
||||
/**
|
||||
* Implementation of spin_node_once using std::chrono::nanoseconds
|
||||
@@ -453,6 +415,23 @@ protected:
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node,
|
||||
std::chrono::nanoseconds timeout);
|
||||
|
||||
/// Spin (blocking) until the future is complete, it times out waiting, or rclcpp is interrupted.
|
||||
/**
|
||||
* \sa spin_until_future_complete()
|
||||
* The only difference with spin_until_future_complete() is that the future's
|
||||
* type is obscured through a std::function which lets you wait on it
|
||||
* reguardless of type.
|
||||
*
|
||||
* \param[in] timeout see spin_until_future_complete() for details
|
||||
* \param[in] wait_for_future function to wait on the future and get the
|
||||
* status after waiting
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual FutureReturnCode
|
||||
spin_until_future_complete_impl(
|
||||
std::chrono::nanoseconds timeout,
|
||||
const std::function<std::future_status(std::chrono::nanoseconds wait_time)> & wait_for_future);
|
||||
|
||||
/// Collect work and execute available work, optionally within a duration.
|
||||
/**
|
||||
* Implementation of spin_some and spin_all.
|
||||
@@ -493,7 +472,7 @@ protected:
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
static void
|
||||
execute_timer(rclcpp::TimerBase::SharedPtr timer);
|
||||
execute_timer(rclcpp::TimerBase::SharedPtr timer, const std::shared_ptr<void> & data_ptr);
|
||||
|
||||
/// Run service server executable.
|
||||
/**
|
||||
@@ -513,6 +492,11 @@ protected:
|
||||
static void
|
||||
execute_client(rclcpp::ClientBase::SharedPtr client);
|
||||
|
||||
/// Gather all of the waitable entities from associated nodes and callback groups.
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
collect_entities();
|
||||
|
||||
/// Block until more work becomes avilable or timeout is reached.
|
||||
/**
|
||||
* Builds a set of waitable entities, which are passed to the middleware.
|
||||
@@ -524,62 +508,6 @@ protected:
|
||||
void
|
||||
wait_for_work(std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));
|
||||
|
||||
/// Find node associated with a callback group
|
||||
/**
|
||||
* \param[in] weak_groups_to_nodes map of callback groups to nodes
|
||||
* \param[in] group callback group to find assocatiated node
|
||||
* \return Pointer to associated node if found, else nullptr
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr
|
||||
get_node_by_group(
|
||||
const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes,
|
||||
rclcpp::CallbackGroup::SharedPtr group);
|
||||
|
||||
/// Return true if the node has been added to this executor.
|
||||
/**
|
||||
* \param[in] node_ptr a shared pointer that points to a node base interface
|
||||
* \param[in] weak_groups_to_nodes map to nodes to lookup
|
||||
* \return true if the node is associated with the executor, otherwise false
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
has_node(
|
||||
const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) const;
|
||||
|
||||
/// Find the callback group associated with a timer
|
||||
/**
|
||||
* \param[in] timer Timer to find associated callback group
|
||||
* \return Pointer to callback group node if found, else nullptr
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::CallbackGroup::SharedPtr
|
||||
get_group_by_timer(rclcpp::TimerBase::SharedPtr timer);
|
||||
|
||||
/// Add a callback group to an executor
|
||||
/**
|
||||
* \see rclcpp::Executor::add_callback_group
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual void
|
||||
add_callback_group_to_map(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
WeakCallbackGroupsToNodesMap & weak_groups_to_nodes,
|
||||
bool notify = true) RCPPUTILS_TSA_REQUIRES(mutex_);
|
||||
|
||||
/// Remove a callback group from the executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::remove_callback_group
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual void
|
||||
remove_callback_group_from_map(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
WeakCallbackGroupsToNodesMap & weak_groups_to_nodes,
|
||||
bool notify = true) RCPPUTILS_TSA_REQUIRES(mutex_);
|
||||
|
||||
/// Check for executable in ready state and populate union structure.
|
||||
/**
|
||||
* \param[out] any_executable populated union structure of ready executable
|
||||
@@ -590,33 +518,6 @@ protected:
|
||||
bool
|
||||
get_next_ready_executable(AnyExecutable & any_executable);
|
||||
|
||||
/// Check for executable in ready state and populate union structure.
|
||||
/**
|
||||
* This is the implementation of get_next_ready_executable that takes into
|
||||
* account the current state of callback groups' association with nodes and
|
||||
* executors.
|
||||
*
|
||||
* This checks in a particular order for available work:
|
||||
* * Timers
|
||||
* * Subscriptions
|
||||
* * Services
|
||||
* * Clients
|
||||
* * Waitable
|
||||
*
|
||||
* If the next executable is not associated with this executor/node pair,
|
||||
* then this method will return false.
|
||||
*
|
||||
* \param[out] any_executable populated union structure of ready executable
|
||||
* \param[in] weak_groups_to_nodes mapping of callback groups to nodes
|
||||
* \return true if an executable was ready and any_executable was populated,
|
||||
* otherwise false
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
get_next_ready_executable_from_map(
|
||||
AnyExecutable & any_executable,
|
||||
const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes);
|
||||
|
||||
/// Wait for executable in ready state and populate union structure.
|
||||
/**
|
||||
* If an executable is ready, it will return immediately, otherwise
|
||||
@@ -634,20 +535,14 @@ protected:
|
||||
AnyExecutable & any_executable,
|
||||
std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));
|
||||
|
||||
/// Add all callback groups that can be automatically added from associated nodes.
|
||||
/// This function triggers a recollect of all entities that are registered to the executor.
|
||||
/**
|
||||
* The executor, before collecting entities, verifies if any callback group from
|
||||
* nodes associated with the executor, which is not already associated to an executor,
|
||||
* can be automatically added to this executor.
|
||||
* This takes care of any callback group that has been added to a node but not explicitly added
|
||||
* to the executor.
|
||||
* It is important to note that in order for the callback groups to be automatically added to an
|
||||
* executor through this function, the node of the callback groups needs to have been added
|
||||
* through the `add_node` method.
|
||||
* Calling this function is thread safe.
|
||||
*
|
||||
* \param[in] notify if true will execute a trigger that will wake up a waiting executor
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual void
|
||||
add_callback_groups_from_nodes_associated_to_executor() RCPPUTILS_TSA_REQUIRES(mutex_);
|
||||
void
|
||||
trigger_entity_recollect(bool notify);
|
||||
|
||||
/// Spinning state, used to prevent multi threaded calls to spin and to cancel blocking spins.
|
||||
std::atomic_bool spinning;
|
||||
@@ -658,16 +553,8 @@ protected:
|
||||
/// Guard condition for signaling the rmw layer to wake up for system shutdown.
|
||||
std::shared_ptr<rclcpp::GuardCondition> shutdown_guard_condition_;
|
||||
|
||||
/// Wait set for managing entities that the rmw layer waits on.
|
||||
rcl_wait_set_t wait_set_ = rcl_get_zero_initialized_wait_set();
|
||||
|
||||
// Mutex to protect the subsequent memory_strategy_.
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
/// The memory strategy: an interface for handling user-defined memory allocation strategies.
|
||||
memory_strategy::MemoryStrategy::SharedPtr
|
||||
memory_strategy_ RCPPUTILS_TSA_PT_GUARDED_BY(mutex_);
|
||||
|
||||
/// The context associated with this executor.
|
||||
std::shared_ptr<rclcpp::Context> context_;
|
||||
|
||||
@@ -677,39 +564,31 @@ protected:
|
||||
virtual void
|
||||
spin_once_impl(std::chrono::nanoseconds timeout);
|
||||
|
||||
typedef std::map<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr,
|
||||
const rclcpp::GuardCondition *,
|
||||
std::owner_less<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>>
|
||||
WeakNodesToGuardConditionsMap;
|
||||
/// Waitable containing guard conditions controlling the executor flow.
|
||||
/**
|
||||
* This waitable contains the interrupt and shutdown guard condition, as well
|
||||
* as the guard condition associated with each node and callback group.
|
||||
* By default, if any change is detected in the monitored entities, the notify
|
||||
* waitable will awake the executor and rebuild the collections.
|
||||
*/
|
||||
std::shared_ptr<rclcpp::executors::ExecutorNotifyWaitable> notify_waitable_;
|
||||
|
||||
typedef std::map<rclcpp::CallbackGroup::WeakPtr,
|
||||
const rclcpp::GuardCondition *,
|
||||
std::owner_less<rclcpp::CallbackGroup::WeakPtr>>
|
||||
WeakCallbackGroupsToGuardConditionsMap;
|
||||
std::atomic_bool entities_need_rebuild_;
|
||||
|
||||
/// maps nodes to guard conditions
|
||||
WeakNodesToGuardConditionsMap
|
||||
weak_nodes_to_guard_conditions_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
/// Collector used to associate executable entities from nodes and guard conditions
|
||||
rclcpp::executors::ExecutorEntitiesCollector collector_;
|
||||
|
||||
/// maps callback groups to guard conditions
|
||||
WeakCallbackGroupsToGuardConditionsMap
|
||||
weak_groups_to_guard_conditions_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
/// WaitSet to be waited on.
|
||||
rclcpp::WaitSet wait_set_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
std::optional<rclcpp::WaitResult<rclcpp::WaitSet>> wait_result_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
|
||||
/// maps callback groups associated to nodes
|
||||
WeakCallbackGroupsToNodesMap
|
||||
weak_groups_associated_with_executor_to_nodes_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
/// Hold the current state of the collection being waited on by the waitset
|
||||
rclcpp::executors::ExecutorEntitiesCollection current_collection_ RCPPUTILS_TSA_GUARDED_BY(
|
||||
mutex_);
|
||||
|
||||
/// maps callback groups to nodes associated with executor
|
||||
WeakCallbackGroupsToNodesMap
|
||||
weak_groups_to_nodes_associated_with_executor_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
|
||||
/// maps all callback groups to nodes
|
||||
WeakCallbackGroupsToNodesMap
|
||||
weak_groups_to_nodes_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
|
||||
/// nodes that are associated with the executor
|
||||
std::list<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>
|
||||
weak_nodes_ RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
/// Hold the current state of the notify waitable being waited on by the waitset
|
||||
std::shared_ptr<rclcpp::executors::ExecutorNotifyWaitable> current_notify_waitable_
|
||||
RCPPUTILS_TSA_GUARDED_BY(mutex_);
|
||||
|
||||
/// shutdown callback handle registered to Context
|
||||
rclcpp::OnShutdownCallbackHandle shutdown_callback_handle_;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#ifndef RCLCPP__EXECUTOR_OPTIONS_HPP_
|
||||
#define RCLCPP__EXECUTOR_OPTIONS_HPP_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "rclcpp/context.hpp"
|
||||
#include "rclcpp/contexts/default_context.hpp"
|
||||
#include "rclcpp/memory_strategies.hpp"
|
||||
@@ -24,18 +26,30 @@
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
class ExecutorOptionsImplementation;
|
||||
|
||||
/// Options to be passed to the executor constructor.
|
||||
struct ExecutorOptions
|
||||
{
|
||||
ExecutorOptions()
|
||||
: memory_strategy(rclcpp::memory_strategies::create_default_strategy()),
|
||||
context(rclcpp::contexts::get_global_default_context()),
|
||||
max_conditions(0)
|
||||
{}
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorOptions();
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
virtual ~ExecutorOptions();
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorOptions(const ExecutorOptions &);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorOptions & operator=(const ExecutorOptions &);
|
||||
|
||||
rclcpp::memory_strategy::MemoryStrategy::SharedPtr memory_strategy;
|
||||
rclcpp::Context::SharedPtr context;
|
||||
size_t max_conditions;
|
||||
|
||||
private:
|
||||
/// Pointer to implementation
|
||||
std::unique_ptr<ExecutorOptionsImplementation> impl_;
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -120,7 +120,9 @@ spin_until_future_complete(
|
||||
const FutureT & future,
|
||||
std::chrono::duration<TimeRepT, TimeT> timeout = std::chrono::duration<TimeRepT, TimeT>(-1))
|
||||
{
|
||||
rclcpp::executors::SingleThreadedExecutor executor;
|
||||
rclcpp::ExecutorOptions options;
|
||||
options.context = node_ptr->get_context();
|
||||
rclcpp::executors::SingleThreadedExecutor executor(options);
|
||||
return executors::spin_node_until_future_complete<FutureT>(executor, node_ptr, future, timeout);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,12 @@ struct ExecutorEntitiesCollection
|
||||
|
||||
/// Clear the entities collection
|
||||
void clear();
|
||||
|
||||
/// Remove entities that have expired weak ownership
|
||||
/**
|
||||
* \return The total number of removed entities
|
||||
*/
|
||||
size_t remove_expired_entities();
|
||||
};
|
||||
|
||||
/// Build an entities collection from callback groups
|
||||
|
||||
@@ -48,11 +48,10 @@ public:
|
||||
~ExecutorNotifyWaitable() override = default;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorNotifyWaitable(const ExecutorNotifyWaitable & other);
|
||||
|
||||
ExecutorNotifyWaitable(ExecutorNotifyWaitable & other);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
ExecutorNotifyWaitable & operator=(const ExecutorNotifyWaitable & other);
|
||||
ExecutorNotifyWaitable & operator=(ExecutorNotifyWaitable & other);
|
||||
|
||||
/// Add conditions to the wait set
|
||||
/**
|
||||
@@ -60,7 +59,7 @@ public:
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set) override;
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// Check conditions against the wait set
|
||||
/**
|
||||
@@ -69,7 +68,7 @@ public:
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) override;
|
||||
is_ready(const rcl_wait_set_t & wait_set) override;
|
||||
|
||||
/// Perform work associated with the waitable.
|
||||
/**
|
||||
@@ -78,7 +77,7 @@ public:
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) override;
|
||||
execute(const std::shared_ptr<void> & data) override;
|
||||
|
||||
/// Retrieve data to be used in the next execute call.
|
||||
/**
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
// Copyright 2020 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__EXECUTORS__STATIC_EXECUTOR_ENTITIES_COLLECTOR_HPP_
|
||||
#define RCLCPP__EXECUTORS__STATIC_EXECUTOR_ENTITIES_COLLECTOR_HPP_
|
||||
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "rcl/guard_condition.h"
|
||||
#include "rcl/wait.h"
|
||||
|
||||
#include "rclcpp/experimental/executable_list.hpp"
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/memory_strategy.hpp"
|
||||
#include "rclcpp/visibility_control.hpp"
|
||||
#include "rclcpp/waitable.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
namespace executors
|
||||
{
|
||||
typedef std::map<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr,
|
||||
std::owner_less<rclcpp::CallbackGroup::WeakPtr>> WeakCallbackGroupsToNodesMap;
|
||||
|
||||
class StaticExecutorEntitiesCollector final
|
||||
: public rclcpp::Waitable,
|
||||
public std::enable_shared_from_this<StaticExecutorEntitiesCollector>
|
||||
{
|
||||
public:
|
||||
RCLCPP_SMART_PTR_DEFINITIONS(StaticExecutorEntitiesCollector)
|
||||
|
||||
// Constructor
|
||||
RCLCPP_PUBLIC
|
||||
StaticExecutorEntitiesCollector() = default;
|
||||
|
||||
// Destructor
|
||||
RCLCPP_PUBLIC
|
||||
~StaticExecutorEntitiesCollector();
|
||||
|
||||
/// Initialize StaticExecutorEntitiesCollector
|
||||
/**
|
||||
* \param p_wait_set A reference to the wait set to be used in the executor
|
||||
* \param memory_strategy Shared pointer to the memory strategy to set.
|
||||
* \throws std::runtime_error if memory strategy is null
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
init(
|
||||
rcl_wait_set_t * p_wait_set,
|
||||
rclcpp::memory_strategy::MemoryStrategy::SharedPtr memory_strategy);
|
||||
|
||||
/// Finalize StaticExecutorEntitiesCollector to clear resources
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
is_init() {return initialized_;}
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
fini();
|
||||
|
||||
/// Execute the waitable.
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) override;
|
||||
|
||||
/// Take the data so that it can be consumed with `execute`.
|
||||
/**
|
||||
* For `StaticExecutorEntitiesCollector`, this always return `nullptr`.
|
||||
* \sa rclcpp::Waitable::take_data()
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::shared_ptr<void>
|
||||
take_data() override;
|
||||
|
||||
/// Function to add_handles_to_wait_set and wait for work and
|
||||
/**
|
||||
* block until the wait set is ready or until the timeout has been exceeded.
|
||||
* \throws std::runtime_error if wait set couldn't be cleared or filled.
|
||||
* \throws any rcl errors from rcl_wait, \see rclcpp::exceptions::throw_from_rcl_error()
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
refresh_wait_set(std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));
|
||||
|
||||
/**
|
||||
* \throws std::runtime_error if it couldn't add guard condition to wait set
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set) override;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions() override;
|
||||
|
||||
/// Add a callback group to an executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::add_callback_group
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr);
|
||||
|
||||
/// Add a callback group to an executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::add_callback_group
|
||||
* \return boolean whether the node from the callback group is new
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
WeakCallbackGroupsToNodesMap & weak_groups_to_nodes);
|
||||
|
||||
/// Remove a callback group from the executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::remove_callback_group
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
remove_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr);
|
||||
|
||||
/// Remove a callback group from the executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::remove_callback_group_from_map
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
remove_callback_group_from_map(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
WeakCallbackGroupsToNodesMap & weak_groups_to_nodes);
|
||||
|
||||
/**
|
||||
* \see rclcpp::Executor::add_node()
|
||||
* \throw std::runtime_error if node was already added
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
add_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr);
|
||||
|
||||
/**
|
||||
* \see rclcpp::Executor::remove_node()
|
||||
* \throw std::runtime_error if no guard condition is associated with node.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
remove_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
get_all_callback_groups();
|
||||
|
||||
/// Get callback groups that belong to executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::get_manually_added_callback_groups()
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
get_manually_added_callback_groups();
|
||||
|
||||
/// Get callback groups that belong to executor.
|
||||
/**
|
||||
* \see rclcpp::Executor::get_automatically_added_callback_groups_from_nodes()
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
get_automatically_added_callback_groups_from_nodes();
|
||||
|
||||
/// Complete all available queued work without blocking.
|
||||
/**
|
||||
* This function checks if after the guard condition was triggered
|
||||
* (or a spurious wakeup happened) we are really ready to execute
|
||||
* i.e. re-collect entities
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) override;
|
||||
|
||||
/// Return number of timers
|
||||
/**
|
||||
* \return number of timers
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
get_number_of_timers() {return exec_list_.number_of_timers;}
|
||||
|
||||
/// Return number of subscriptions
|
||||
/**
|
||||
* \return number of subscriptions
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
get_number_of_subscriptions() {return exec_list_.number_of_subscriptions;}
|
||||
|
||||
/// Return number of services
|
||||
/**
|
||||
* \return number of services
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
get_number_of_services() {return exec_list_.number_of_services;}
|
||||
|
||||
/// Return number of clients
|
||||
/**
|
||||
* \return number of clients
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
get_number_of_clients() {return exec_list_.number_of_clients;}
|
||||
|
||||
/// Return number of waitables
|
||||
/**
|
||||
* \return number of waitables
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
get_number_of_waitables() {return exec_list_.number_of_waitables;}
|
||||
|
||||
/** Return a SubscritionBase Sharedptr by index.
|
||||
* \param[in] i The index of the SubscritionBase
|
||||
* \return a SubscritionBase shared pointer
|
||||
* \throws std::out_of_range if the argument is higher than the size of the structrue.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::SubscriptionBase::SharedPtr
|
||||
get_subscription(size_t i) {return exec_list_.subscription[i];}
|
||||
|
||||
/** Return a TimerBase Sharedptr by index.
|
||||
* \param[in] i The index of the TimerBase
|
||||
* \return a TimerBase shared pointer
|
||||
* \throws std::out_of_range if the argument is higher than the size.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::TimerBase::SharedPtr
|
||||
get_timer(size_t i) {return exec_list_.timer[i];}
|
||||
|
||||
/** Return a ServiceBase Sharedptr by index.
|
||||
* \param[in] i The index of the ServiceBase
|
||||
* \return a ServiceBase shared pointer
|
||||
* \throws std::out_of_range if the argument is higher than the size.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::ServiceBase::SharedPtr
|
||||
get_service(size_t i) {return exec_list_.service[i];}
|
||||
|
||||
/** Return a ClientBase Sharedptr by index
|
||||
* \param[in] i The index of the ClientBase
|
||||
* \return a ClientBase shared pointer
|
||||
* \throws std::out_of_range if the argument is higher than the size.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::ClientBase::SharedPtr
|
||||
get_client(size_t i) {return exec_list_.client[i];}
|
||||
|
||||
/** Return a Waitable Sharedptr by index
|
||||
* \param[in] i The index of the Waitable
|
||||
* \return a Waitable shared pointer
|
||||
* \throws std::out_of_range if the argument is higher than the size.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::Waitable::SharedPtr
|
||||
get_waitable(size_t i) {return exec_list_.waitable[i];}
|
||||
|
||||
private:
|
||||
/// Function to reallocate space for entities in the wait set.
|
||||
/**
|
||||
* \throws std::runtime_error if wait set couldn't be cleared or resized.
|
||||
*/
|
||||
void
|
||||
prepare_wait_set();
|
||||
|
||||
void
|
||||
fill_executable_list();
|
||||
|
||||
void
|
||||
fill_memory_strategy();
|
||||
|
||||
/// Return true if the node belongs to the collector
|
||||
/**
|
||||
* \param[in] node_ptr a node base interface shared pointer
|
||||
* \param[in] weak_groups_to_nodes map to nodes to lookup
|
||||
* \return boolean whether a node belongs the collector
|
||||
*/
|
||||
bool
|
||||
has_node(
|
||||
const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) const;
|
||||
|
||||
/// Add all callback groups that can be automatically added by any executor
|
||||
/// and is not already associated with an executor from nodes
|
||||
/// that are associated with executor
|
||||
/**
|
||||
* \see rclcpp::Executor::add_callback_groups_from_nodes_associated_to_executor()
|
||||
*/
|
||||
void
|
||||
add_callback_groups_from_nodes_associated_to_executor();
|
||||
|
||||
void
|
||||
fill_executable_list_from_map(const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes);
|
||||
|
||||
/// Memory strategy: an interface for handling user-defined memory allocation strategies.
|
||||
rclcpp::memory_strategy::MemoryStrategy::SharedPtr memory_strategy_;
|
||||
|
||||
// maps callback groups to nodes.
|
||||
WeakCallbackGroupsToNodesMap weak_groups_associated_with_executor_to_nodes_;
|
||||
// maps callback groups to nodes.
|
||||
WeakCallbackGroupsToNodesMap weak_groups_to_nodes_associated_with_executor_;
|
||||
|
||||
typedef std::map<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr,
|
||||
const rclcpp::GuardCondition *,
|
||||
std::owner_less<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>>
|
||||
WeakNodesToGuardConditionsMap;
|
||||
WeakNodesToGuardConditionsMap weak_nodes_to_guard_conditions_;
|
||||
|
||||
/// List of weak nodes registered in the static executor
|
||||
std::list<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr> weak_nodes_;
|
||||
|
||||
// Mutex to protect vector of new nodes.
|
||||
std::mutex new_nodes_mutex_;
|
||||
std::vector<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr> new_nodes_;
|
||||
|
||||
/// Wait set for managing entities that the rmw layer waits on.
|
||||
rcl_wait_set_t * p_wait_set_ = nullptr;
|
||||
|
||||
/// Executable list: timers, subscribers, clients, services and waitables
|
||||
rclcpp::experimental::ExecutableList exec_list_;
|
||||
|
||||
/// Bool to check if the entities collector has been initialized
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace executors
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__EXECUTORS__STATIC_EXECUTOR_ENTITIES_COLLECTOR_HPP_
|
||||
@@ -15,24 +15,13 @@
|
||||
#ifndef RCLCPP__EXECUTORS__STATIC_SINGLE_THREADED_EXECUTOR_HPP_
|
||||
#define RCLCPP__EXECUTORS__STATIC_SINGLE_THREADED_EXECUTOR_HPP_
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "rmw/rmw.h"
|
||||
|
||||
#include "rclcpp/executor.hpp"
|
||||
#include "rclcpp/executors/static_executor_entities_collector.hpp"
|
||||
#include "rclcpp/experimental/executable_list.hpp"
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/memory_strategies.hpp"
|
||||
#include "rclcpp/node.hpp"
|
||||
#include "rclcpp/rate.hpp"
|
||||
#include "rclcpp/utilities.hpp"
|
||||
#include "rclcpp/visibility_control.hpp"
|
||||
#include "rclcpp/executors/executor_entities_collection.hpp"
|
||||
#include "rclcpp/executors/single_threaded_executor.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
@@ -65,7 +54,7 @@ public:
|
||||
explicit StaticSingleThreadedExecutor(
|
||||
const rclcpp::ExecutorOptions & options = rclcpp::ExecutorOptions());
|
||||
|
||||
/// Default destrcutor.
|
||||
/// Default destructor.
|
||||
RCLCPP_PUBLIC
|
||||
virtual ~StaticSingleThreadedExecutor();
|
||||
|
||||
@@ -116,105 +105,31 @@ public:
|
||||
void
|
||||
spin_all(std::chrono::nanoseconds max_duration) override;
|
||||
|
||||
/// Add a callback group to an executor.
|
||||
/**
|
||||
* \sa rclcpp::Executor::add_callback_group
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
bool notify = true) override;
|
||||
|
||||
/// Remove callback group from the executor
|
||||
/**
|
||||
* \sa rclcpp::Executor::remove_callback_group
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
remove_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
bool notify = true) override;
|
||||
|
||||
/// Add a node to the executor.
|
||||
/**
|
||||
* \sa rclcpp::Executor::add_node
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
bool notify = true) override;
|
||||
|
||||
/// Convenience function which takes Node and forwards NodeBaseInterface.
|
||||
/**
|
||||
* \sa rclcpp::StaticSingleThreadedExecutor::add_node
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_node(std::shared_ptr<rclcpp::Node> node_ptr, bool notify = true) override;
|
||||
|
||||
/// Remove a node from the executor.
|
||||
/**
|
||||
* \sa rclcpp::Executor::remove_node
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
remove_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
bool notify = true) override;
|
||||
|
||||
/// Convenience function which takes Node and forwards NodeBaseInterface.
|
||||
/**
|
||||
* \sa rclcpp::Executor::remove_node
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
remove_node(std::shared_ptr<rclcpp::Node> node_ptr, bool notify = true) override;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
get_all_callback_groups() override;
|
||||
|
||||
/// Get callback groups that belong to executor.
|
||||
/**
|
||||
* \sa rclcpp::Executor::get_manually_added_callback_groups()
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
get_manually_added_callback_groups() override;
|
||||
|
||||
/// Get callback groups that belong to executor.
|
||||
/**
|
||||
* \sa rclcpp::Executor::get_automatically_added_callback_groups_from_nodes()
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
get_automatically_added_callback_groups_from_nodes() override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Executes ready executables from wait set.
|
||||
* @param collection entities to evaluate for ready executables.
|
||||
* @param wait_result result to check for ready executables.
|
||||
* @param spin_once if true executes only the first ready executable.
|
||||
* @return true if any executable was ready.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
execute_ready_executables(bool spin_once = false);
|
||||
execute_ready_executables(
|
||||
const rclcpp::executors::ExecutorEntitiesCollection & collection,
|
||||
rclcpp::WaitResult<rclcpp::WaitSet> & wait_result,
|
||||
bool spin_once);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
spin_once_impl(std::chrono::nanoseconds timeout) override;
|
||||
|
||||
std::optional<rclcpp::WaitResult<rclcpp::WaitSet>>
|
||||
collect_and_wait(std::chrono::nanoseconds timeout);
|
||||
|
||||
private:
|
||||
RCLCPP_DISABLE_COPY(StaticSingleThreadedExecutor)
|
||||
|
||||
StaticExecutorEntitiesCollector::SharedPtr entities_collector_;
|
||||
};
|
||||
|
||||
} // namespace executors
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#ifndef RCLCPP__EXPERIMENTAL__BUFFERS__BUFFER_IMPLEMENTATION_BASE_HPP_
|
||||
#define RCLCPP__EXPERIMENTAL__BUFFERS__BUFFER_IMPLEMENTATION_BASE_HPP_
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
namespace experimental
|
||||
@@ -31,6 +33,8 @@ public:
|
||||
virtual BufferT dequeue() = 0;
|
||||
virtual void enqueue(BufferT request) = 0;
|
||||
|
||||
virtual std::vector<BufferT> get_all_data() = 0;
|
||||
|
||||
virtual void clear() = 0;
|
||||
virtual bool has_data() const = 0;
|
||||
virtual size_t available_capacity() const = 0;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/allocator/allocator_common.hpp"
|
||||
#include "rclcpp/allocator/allocator_deleter.hpp"
|
||||
@@ -66,6 +67,9 @@ public:
|
||||
|
||||
virtual MessageSharedPtr consume_shared() = 0;
|
||||
virtual MessageUniquePtr consume_unique() = 0;
|
||||
|
||||
virtual std::vector<MessageSharedPtr> get_all_data_shared() = 0;
|
||||
virtual std::vector<MessageUniquePtr> get_all_data_unique() = 0;
|
||||
};
|
||||
|
||||
template<
|
||||
@@ -129,6 +133,16 @@ public:
|
||||
return consume_unique_impl<BufferT>();
|
||||
}
|
||||
|
||||
std::vector<MessageSharedPtr> get_all_data_shared() override
|
||||
{
|
||||
return get_all_data_shared_impl();
|
||||
}
|
||||
|
||||
std::vector<MessageUniquePtr> get_all_data_unique() override
|
||||
{
|
||||
return get_all_data_unique_impl();
|
||||
}
|
||||
|
||||
bool has_data() const override
|
||||
{
|
||||
return buffer_->has_data();
|
||||
@@ -243,6 +257,71 @@ private:
|
||||
{
|
||||
return buffer_->dequeue();
|
||||
}
|
||||
|
||||
// MessageSharedPtr to MessageSharedPtr
|
||||
template<typename T = BufferT>
|
||||
typename std::enable_if<
|
||||
std::is_same<T, MessageSharedPtr>::value,
|
||||
std::vector<MessageSharedPtr>
|
||||
>::type
|
||||
get_all_data_shared_impl()
|
||||
{
|
||||
return buffer_->get_all_data();
|
||||
}
|
||||
|
||||
// MessageUniquePtr to MessageSharedPtr
|
||||
template<typename T = BufferT>
|
||||
typename std::enable_if<
|
||||
std::is_same<T, MessageUniquePtr>::value,
|
||||
std::vector<MessageSharedPtr>
|
||||
>::type
|
||||
get_all_data_shared_impl()
|
||||
{
|
||||
std::vector<MessageSharedPtr> result;
|
||||
auto uni_ptr_vec = buffer_->get_all_data();
|
||||
result.reserve(uni_ptr_vec.size());
|
||||
for (MessageUniquePtr & uni_ptr : uni_ptr_vec) {
|
||||
result.emplace_back(std::move(uni_ptr));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// MessageSharedPtr to MessageUniquePtr
|
||||
template<typename T = BufferT>
|
||||
typename std::enable_if<
|
||||
std::is_same<T, MessageSharedPtr>::value,
|
||||
std::vector<MessageUniquePtr>
|
||||
>::type
|
||||
get_all_data_unique_impl()
|
||||
{
|
||||
std::vector<MessageUniquePtr> result;
|
||||
auto shared_ptr_vec = buffer_->get_all_data();
|
||||
result.reserve(shared_ptr_vec.size());
|
||||
for (MessageSharedPtr shared_msg : shared_ptr_vec) {
|
||||
MessageUniquePtr unique_msg;
|
||||
MessageDeleter * deleter = std::get_deleter<MessageDeleter, const MessageT>(shared_msg);
|
||||
auto ptr = MessageAllocTraits::allocate(*message_allocator_.get(), 1);
|
||||
MessageAllocTraits::construct(*message_allocator_.get(), ptr, *shared_msg);
|
||||
if (deleter) {
|
||||
unique_msg = MessageUniquePtr(ptr, *deleter);
|
||||
} else {
|
||||
unique_msg = MessageUniquePtr(ptr);
|
||||
}
|
||||
result.push_back(std::move(unique_msg));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// MessageUniquePtr to MessageUniquePtr
|
||||
template<typename T = BufferT>
|
||||
typename std::enable_if<
|
||||
std::is_same<T, MessageUniquePtr>::value,
|
||||
std::vector<MessageUniquePtr>
|
||||
>::type
|
||||
get_all_data_unique_impl()
|
||||
{
|
||||
return buffer_->get_all_data();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace buffers
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#ifndef RCLCPP__EXPERIMENTAL__BUFFERS__RING_BUFFER_IMPLEMENTATION_HPP_
|
||||
#define RCLCPP__EXPERIMENTAL__BUFFERS__RING_BUFFER_IMPLEMENTATION_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
@@ -66,7 +67,7 @@ public:
|
||||
*
|
||||
* \param request the element to be stored in the ring buffer
|
||||
*/
|
||||
void enqueue(BufferT request)
|
||||
void enqueue(BufferT request) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
@@ -92,7 +93,7 @@ public:
|
||||
*
|
||||
* \return the element that is being removed from the ring buffer
|
||||
*/
|
||||
BufferT dequeue()
|
||||
BufferT dequeue() override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
@@ -113,6 +114,17 @@ public:
|
||||
return request;
|
||||
}
|
||||
|
||||
/// Get all the elements from the ring buffer
|
||||
/**
|
||||
* This member function is thread-safe.
|
||||
*
|
||||
* \return a vector containing all the elements from the ring buffer
|
||||
*/
|
||||
std::vector<BufferT> get_all_data() override
|
||||
{
|
||||
return get_all_data_impl();
|
||||
}
|
||||
|
||||
/// Get the next index value for the ring buffer
|
||||
/**
|
||||
* This member function is thread-safe.
|
||||
@@ -132,7 +144,7 @@ public:
|
||||
*
|
||||
* \return `true` if there is data and `false` otherwise
|
||||
*/
|
||||
inline bool has_data() const
|
||||
inline bool has_data() const override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return has_data_();
|
||||
@@ -157,13 +169,13 @@ public:
|
||||
*
|
||||
* \return the number of free capacity for new messages
|
||||
*/
|
||||
size_t available_capacity() const
|
||||
size_t available_capacity() const override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return available_capacity_();
|
||||
}
|
||||
|
||||
void clear()
|
||||
void clear() override
|
||||
{
|
||||
TRACETOOLS_TRACEPOINT(rclcpp_ring_buffer_clear, static_cast<const void *>(this));
|
||||
}
|
||||
@@ -215,6 +227,71 @@ private:
|
||||
return capacity_ - size_;
|
||||
}
|
||||
|
||||
/// Traits for checking if a type is std::unique_ptr
|
||||
template<typename ...>
|
||||
struct is_std_unique_ptr final : std::false_type {};
|
||||
template<class T, typename ... Args>
|
||||
struct is_std_unique_ptr<std::unique_ptr<T, Args...>> final : std::true_type
|
||||
{
|
||||
typedef T Ptr_type;
|
||||
};
|
||||
|
||||
/// Get all the elements from the ring buffer
|
||||
/**
|
||||
* This member function is thread-safe.
|
||||
* Two versions for the implementation of the function.
|
||||
* One for buffer containing unique_ptr and the other for other types
|
||||
*
|
||||
* \return a vector containing all the elements from the ring buffer
|
||||
*/
|
||||
template<typename T = BufferT, std::enable_if_t<is_std_unique_ptr<T>::value &&
|
||||
std::is_copy_constructible<
|
||||
typename is_std_unique_ptr<T>::Ptr_type
|
||||
>::value,
|
||||
void> * = nullptr>
|
||||
std::vector<BufferT> get_all_data_impl()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<BufferT> result_vtr;
|
||||
result_vtr.reserve(size_);
|
||||
for (size_t id = 0; id < size_; ++id) {
|
||||
result_vtr.emplace_back(
|
||||
new typename is_std_unique_ptr<T>::Ptr_type(
|
||||
*(ring_buffer_[(read_index_ + id) % capacity_])));
|
||||
}
|
||||
return result_vtr;
|
||||
}
|
||||
|
||||
template<typename T = BufferT, std::enable_if_t<
|
||||
std::is_copy_constructible<T>::value, void> * = nullptr>
|
||||
std::vector<BufferT> get_all_data_impl()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<BufferT> result_vtr;
|
||||
result_vtr.reserve(size_);
|
||||
for (size_t id = 0; id < size_; ++id) {
|
||||
result_vtr.emplace_back(ring_buffer_[(read_index_ + id) % capacity_]);
|
||||
}
|
||||
return result_vtr;
|
||||
}
|
||||
|
||||
template<typename T = BufferT, std::enable_if_t<!is_std_unique_ptr<T>::value &&
|
||||
!std::is_copy_constructible<T>::value, void> * = nullptr>
|
||||
std::vector<BufferT> get_all_data_impl()
|
||||
{
|
||||
throw std::logic_error("Underlined type results in invalid get_all_data_impl()");
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T = BufferT, std::enable_if_t<is_std_unique_ptr<T>::value &&
|
||||
!std::is_copy_constructible<typename is_std_unique_ptr<T>::Ptr_type>::value,
|
||||
void> * = nullptr>
|
||||
std::vector<BufferT> get_all_data_impl()
|
||||
{
|
||||
throw std::logic_error("Underlined type in unique_ptr results in invalid get_all_data_impl()");
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t capacity_;
|
||||
|
||||
std::vector<BufferT> ring_buffer_;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#ifndef RCLCPP__EXPERIMENTAL__EXECUTORS__EVENTS_EXECUTOR__EVENTS_EXECUTOR_EVENT_TYPES_HPP_
|
||||
#define RCLCPP__EXPERIMENTAL__EXECUTORS__EVENTS_EXECUTOR__EVENTS_EXECUTOR_EVENT_TYPES_HPP_
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
namespace experimental
|
||||
@@ -34,6 +36,7 @@ enum ExecutorEventType
|
||||
struct ExecutorEvent
|
||||
{
|
||||
const void * entity_key;
|
||||
std::shared_ptr<void> data;
|
||||
int waitable_data;
|
||||
ExecutorEventType type;
|
||||
size_t num_events;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <typeinfo>
|
||||
|
||||
#include "rclcpp/allocator/allocator_deleter.hpp"
|
||||
#include "rclcpp/experimental/buffers/intra_process_buffer.hpp"
|
||||
#include "rclcpp/experimental/ros_message_intra_process_buffer.hpp"
|
||||
#include "rclcpp/experimental/subscription_intra_process.hpp"
|
||||
#include "rclcpp/experimental/subscription_intra_process_base.hpp"
|
||||
@@ -112,9 +113,40 @@ public:
|
||||
* \param subscription the SubscriptionIntraProcess to register.
|
||||
* \return an unsigned 64-bit integer which is the subscription's unique id.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
template<
|
||||
typename ROSMessageType,
|
||||
typename Alloc = std::allocator<ROSMessageType>
|
||||
>
|
||||
uint64_t
|
||||
add_subscription(rclcpp::experimental::SubscriptionIntraProcessBase::SharedPtr subscription);
|
||||
add_subscription(rclcpp::experimental::SubscriptionIntraProcessBase::SharedPtr subscription)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
|
||||
|
||||
uint64_t sub_id = IntraProcessManager::get_next_unique_id();
|
||||
|
||||
subscriptions_[sub_id] = subscription;
|
||||
|
||||
// adds the subscription id to all the matchable publishers
|
||||
for (auto & pair : publishers_) {
|
||||
auto publisher = pair.second.lock();
|
||||
if (!publisher) {
|
||||
continue;
|
||||
}
|
||||
if (can_communicate(publisher, subscription)) {
|
||||
uint64_t pub_id = pair.first;
|
||||
insert_sub_id_for_pub(sub_id, pub_id, subscription->use_take_shared_method());
|
||||
if (publisher->is_durability_transient_local() &&
|
||||
subscription->is_durability_transient_local())
|
||||
{
|
||||
do_transient_local_publish<ROSMessageType, Alloc>(
|
||||
pub_id, sub_id,
|
||||
subscription->use_take_shared_method());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sub_id;
|
||||
}
|
||||
|
||||
/// Unregister a subscription using the subscription's unique id.
|
||||
/**
|
||||
@@ -131,14 +163,21 @@ public:
|
||||
* This method stores the publisher intra process object, together with
|
||||
* the information of its wrapped publisher (i.e. topic name and QoS).
|
||||
*
|
||||
* If the publisher's durability is transient local, its buffer pointer should
|
||||
* be passed and the method will store it as well.
|
||||
*
|
||||
* In addition this generates a unique intra process id for the publisher.
|
||||
*
|
||||
* \param publisher publisher to be registered with the manager.
|
||||
* \param buffer publisher's buffer to be stored if its duability is transient local.
|
||||
* \return an unsigned 64-bit integer which is the publisher's unique id.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
uint64_t
|
||||
add_publisher(rclcpp::PublisherBase::SharedPtr publisher);
|
||||
add_publisher(
|
||||
rclcpp::PublisherBase::SharedPtr publisher,
|
||||
rclcpp::experimental::buffers::IntraProcessBufferBase::SharedPtr buffer =
|
||||
rclcpp::experimental::buffers::IntraProcessBufferBase::SharedPtr());
|
||||
|
||||
/// Unregister a publisher using the publisher's unique id.
|
||||
/**
|
||||
@@ -214,7 +253,8 @@ public:
|
||||
// So this case is equivalent to all the buffers requiring ownership
|
||||
|
||||
// Merge the two vector of ids into a unique one
|
||||
std::vector<uint64_t> concatenated_vector(sub_ids.take_shared_subscriptions);
|
||||
std::vector<uint64_t> concatenated_vector(
|
||||
sub_ids.take_shared_subscriptions.begin(), sub_ids.take_shared_subscriptions.end());
|
||||
concatenated_vector.insert(
|
||||
concatenated_vector.end(),
|
||||
sub_ids.take_ownership_subscriptions.begin(),
|
||||
@@ -292,6 +332,34 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
template<
|
||||
typename MessageT,
|
||||
typename Alloc,
|
||||
typename Deleter,
|
||||
typename ROSMessageType>
|
||||
void
|
||||
add_shared_msg_to_buffer(
|
||||
std::shared_ptr<const MessageT> message,
|
||||
uint64_t subscription_id)
|
||||
{
|
||||
add_shared_msg_to_buffers<MessageT, Alloc, Deleter, ROSMessageType>(message, {subscription_id});
|
||||
}
|
||||
|
||||
template<
|
||||
typename MessageT,
|
||||
typename Alloc,
|
||||
typename Deleter,
|
||||
typename ROSMessageType>
|
||||
void
|
||||
add_owned_msg_to_buffer(
|
||||
std::unique_ptr<MessageT, Deleter> message,
|
||||
uint64_t subscription_id,
|
||||
typename allocator::AllocRebind<MessageT, Alloc>::allocator_type & allocator)
|
||||
{
|
||||
add_owned_msg_to_buffers<MessageT, Alloc, Deleter, ROSMessageType>(
|
||||
std::move(message), {subscription_id}, allocator);
|
||||
}
|
||||
|
||||
/// Return true if the given rmw_gid_t matches any stored Publishers.
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
@@ -324,6 +392,9 @@ private:
|
||||
using PublisherMap =
|
||||
std::unordered_map<uint64_t, rclcpp::PublisherBase::WeakPtr>;
|
||||
|
||||
using PublisherBufferMap =
|
||||
std::unordered_map<uint64_t, rclcpp::experimental::buffers::IntraProcessBufferBase::WeakPtr>;
|
||||
|
||||
using PublisherToSubscriptionIdsMap =
|
||||
std::unordered_map<uint64_t, SplittedSubscriptions>;
|
||||
|
||||
@@ -342,6 +413,54 @@ private:
|
||||
rclcpp::PublisherBase::SharedPtr pub,
|
||||
rclcpp::experimental::SubscriptionIntraProcessBase::SharedPtr sub) const;
|
||||
|
||||
template<
|
||||
typename ROSMessageType,
|
||||
typename Alloc = std::allocator<ROSMessageType>
|
||||
>
|
||||
void do_transient_local_publish(
|
||||
const uint64_t pub_id, const uint64_t sub_id,
|
||||
const bool use_take_shared_method)
|
||||
{
|
||||
using ROSMessageTypeAllocatorTraits = allocator::AllocRebind<ROSMessageType, Alloc>;
|
||||
using ROSMessageTypeAllocator = typename ROSMessageTypeAllocatorTraits::allocator_type;
|
||||
using ROSMessageTypeDeleter = allocator::Deleter<ROSMessageTypeAllocator, ROSMessageType>;
|
||||
|
||||
auto publisher_buffer = publisher_buffers_[pub_id].lock();
|
||||
if (!publisher_buffer) {
|
||||
throw std::runtime_error("publisher buffer has unexpectedly gone out of scope");
|
||||
}
|
||||
auto buffer = std::dynamic_pointer_cast<
|
||||
rclcpp::experimental::buffers::IntraProcessBuffer<
|
||||
ROSMessageType,
|
||||
ROSMessageTypeAllocator,
|
||||
ROSMessageTypeDeleter
|
||||
>
|
||||
>(publisher_buffer);
|
||||
if (!buffer) {
|
||||
throw std::runtime_error(
|
||||
"failed to dynamic cast publisher's IntraProcessBufferBase to "
|
||||
"IntraProcessBuffer<ROSMessageType,ROSMessageTypeAllocator,"
|
||||
"ROSMessageTypeDeleter> which can happen when the publisher and "
|
||||
"subscription use different allocator types, which is not supported");
|
||||
}
|
||||
if (use_take_shared_method) {
|
||||
auto data_vec = buffer->get_all_data_shared();
|
||||
for (auto shared_data : data_vec) {
|
||||
this->template add_shared_msg_to_buffer<
|
||||
ROSMessageType, ROSMessageTypeAllocator, ROSMessageTypeDeleter, ROSMessageType>(
|
||||
shared_data, sub_id);
|
||||
}
|
||||
} else {
|
||||
auto data_vec = buffer->get_all_data_unique();
|
||||
for (auto & owned_data : data_vec) {
|
||||
auto allocator = ROSMessageTypeAllocator();
|
||||
this->template add_owned_msg_to_buffer<
|
||||
ROSMessageType, ROSMessageTypeAllocator, ROSMessageTypeDeleter, ROSMessageType>(
|
||||
std::move(owned_data), sub_id, allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<
|
||||
typename MessageT,
|
||||
typename Alloc,
|
||||
@@ -520,6 +639,7 @@ private:
|
||||
PublisherToSubscriptionIdsMap pub_to_subs_;
|
||||
SubscriptionMap subscriptions_;
|
||||
PublisherMap publishers_;
|
||||
PublisherBufferMap publisher_buffers_;
|
||||
|
||||
mutable std::shared_timed_mutex mutex_;
|
||||
};
|
||||
|
||||
@@ -101,6 +101,23 @@ public:
|
||||
|
||||
virtual ~SubscriptionIntraProcess() = default;
|
||||
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
// This block is necessary when the guard condition wakes the wait set, but
|
||||
// the intra process waitable was not handled before the wait set is waited
|
||||
// on again.
|
||||
// Basically we're keeping the guard condition triggered so long as there is
|
||||
// data in the buffer.
|
||||
if (this->buffer_->has_data()) {
|
||||
// If there is data still to be processed, indicate to the
|
||||
// executor or waitset by triggering the guard condition.
|
||||
this->trigger_guard_condition();
|
||||
}
|
||||
// Let the parent classes handle the rest of the work:
|
||||
return SubscriptionIntraProcessBufferT::add_to_wait_set(wait_set);
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override
|
||||
{
|
||||
@@ -132,7 +149,7 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
void execute(std::shared_ptr<void> & data) override
|
||||
void execute(const std::shared_ptr<void> & data) override
|
||||
{
|
||||
execute_impl<SubscribedType>(data);
|
||||
}
|
||||
@@ -140,17 +157,16 @@ public:
|
||||
protected:
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_same<T, rcl_serialized_message_t>::value, void>::type
|
||||
execute_impl(std::shared_ptr<void> & data)
|
||||
execute_impl(const std::shared_ptr<void> &)
|
||||
{
|
||||
(void)data;
|
||||
throw std::runtime_error("Subscription intra-process can't handle serialized messages");
|
||||
}
|
||||
|
||||
template<class T>
|
||||
typename std::enable_if<!std::is_same<T, rcl_serialized_message_t>::value, void>::type
|
||||
execute_impl(std::shared_ptr<void> & data)
|
||||
execute_impl(const std::shared_ptr<void> & data)
|
||||
{
|
||||
if (!data) {
|
||||
if (nullptr == data) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,15 +60,19 @@ public:
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set) override;
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
size_t
|
||||
available_capacity() const = 0;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) override = 0;
|
||||
is_durability_transient_local() const;
|
||||
|
||||
bool
|
||||
is_ready(const rcl_wait_set_t & wait_set) override = 0;
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override = 0;
|
||||
@@ -81,7 +85,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) override = 0;
|
||||
execute(const std::shared_ptr<void> & data) override = 0;
|
||||
|
||||
virtual
|
||||
bool
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "rclcpp/experimental/ros_message_intra_process_buffer.hpp"
|
||||
#include "rclcpp/qos.hpp"
|
||||
#include "rclcpp/type_support_decl.hpp"
|
||||
#include "rclcpp/detail/add_guard_condition_to_rcl_wait_set.hpp"
|
||||
|
||||
#include "tracetools/tracetools.h"
|
||||
|
||||
@@ -99,8 +100,17 @@ public:
|
||||
static_cast<const void *>(this));
|
||||
}
|
||||
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
if (this->buffer_->has_data()) {
|
||||
this->trigger_guard_condition();
|
||||
}
|
||||
detail::add_guard_condition_to_rcl_wait_set(wait_set, this->gc_);
|
||||
}
|
||||
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) override
|
||||
is_ready(const rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
(void) wait_set;
|
||||
return buffer_->has_data();
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/context.hpp"
|
||||
#include "rclcpp/timer.hpp"
|
||||
|
||||
@@ -86,7 +86,8 @@ public:
|
||||
RCLCPP_PUBLIC
|
||||
TimersManager(
|
||||
std::shared_ptr<rclcpp::Context> context,
|
||||
std::function<void(const rclcpp::TimerBase *)> on_ready_callback = nullptr);
|
||||
std::function<void(const rclcpp::TimerBase *,
|
||||
const std::shared_ptr<void> &)> on_ready_callback = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Destruct the TimersManager object making sure to stop thread and release memory.
|
||||
@@ -164,21 +165,23 @@ public:
|
||||
* the TimersManager on_ready_callback was passed during construction.
|
||||
*
|
||||
* @param timer_id the timer ID of the timer to execute
|
||||
* @param data internal data of the timer
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void execute_ready_timer(const rclcpp::TimerBase * timer_id);
|
||||
void execute_ready_timer(const rclcpp::TimerBase * timer_id, const std::shared_ptr<void> & data);
|
||||
|
||||
/**
|
||||
* @brief Get the amount of time before the next timer triggers.
|
||||
* This function is thread safe.
|
||||
*
|
||||
* @return std::chrono::nanoseconds to wait,
|
||||
* @return std::optional<std::chrono::nanoseconds> to wait,
|
||||
* the returned value could be negative if the timer is already expired
|
||||
* or std::chrono::nanoseconds::max() if there are no timers stored in the object.
|
||||
* If the head timer was cancelled, then this will return a nullopt.
|
||||
* @throws std::runtime_error if the timers thread was already running.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
std::chrono::nanoseconds get_head_timeout();
|
||||
std::optional<std::chrono::nanoseconds> get_head_timeout();
|
||||
|
||||
private:
|
||||
RCLCPP_DISABLE_COPY(TimersManager)
|
||||
@@ -512,12 +515,13 @@ private:
|
||||
* @brief Get the amount of time before the next timer triggers.
|
||||
* This function is not thread safe, acquire a mutex before calling it.
|
||||
*
|
||||
* @return std::chrono::nanoseconds to wait,
|
||||
* @return std::optional<std::chrono::nanoseconds> to wait,
|
||||
* the returned value could be negative if the timer is already expired
|
||||
* or std::chrono::nanoseconds::max() if the heap is empty.
|
||||
* If the head timer was cancelled, then this will return a nullopt.
|
||||
* This function is not thread safe, acquire the timers_mutex_ before calling it.
|
||||
*/
|
||||
std::chrono::nanoseconds get_head_timeout_unsafe();
|
||||
std::optional<std::chrono::nanoseconds> get_head_timeout_unsafe();
|
||||
|
||||
/**
|
||||
* @brief Executes all the timers currently ready when the function is invoked
|
||||
@@ -527,7 +531,8 @@ private:
|
||||
void execute_ready_timers_unsafe();
|
||||
|
||||
// Callback to be called when timer is ready
|
||||
std::function<void(const rclcpp::TimerBase *)> on_ready_callback_;
|
||||
std::function<void(const rclcpp::TimerBase *,
|
||||
const std::shared_ptr<void> &)> on_ready_callback_ = nullptr;
|
||||
|
||||
// Thread used to run the timers execution task
|
||||
std::thread timers_thread_;
|
||||
|
||||
207
rclcpp/include/rclcpp/generic_client.hpp
Normal file
207
rclcpp/include/rclcpp/generic_client.hpp
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright 2023 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__GENERIC_CLIENT_HPP_
|
||||
#define RCLCPP__GENERIC_CLIENT_HPP_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "rcl/client.h"
|
||||
|
||||
#include "rclcpp/client.hpp"
|
||||
#include "rclcpp/visibility_control.hpp"
|
||||
#include "rcpputils/shared_library.hpp"
|
||||
|
||||
#include "rosidl_typesupport_introspection_cpp/message_introspection.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
class GenericClient : public ClientBase
|
||||
{
|
||||
public:
|
||||
using Request = void *; // Serialized data pointer of request message
|
||||
using Response = void *; // Serialized data pointer of response message
|
||||
|
||||
using SharedResponse = std::shared_ptr<void>;
|
||||
|
||||
using Promise = std::promise<SharedResponse>;
|
||||
using SharedPromise = std::shared_ptr<Promise>;
|
||||
|
||||
using Future = std::future<SharedResponse>;
|
||||
using SharedFuture = std::shared_future<SharedResponse>;
|
||||
|
||||
RCLCPP_SMART_PTR_DEFINITIONS(GenericClient)
|
||||
|
||||
/// A convenient GenericClient::Future and request id pair.
|
||||
/**
|
||||
* Public members:
|
||||
* - future: a std::future<void *>.
|
||||
* - request_id: the request id associated with the future.
|
||||
*
|
||||
* All the other methods are equivalent to the ones std::future provides.
|
||||
*/
|
||||
struct FutureAndRequestId
|
||||
: detail::FutureAndRequestId<Future>
|
||||
{
|
||||
using detail::FutureAndRequestId<Future>::FutureAndRequestId;
|
||||
|
||||
/// See std::future::share().
|
||||
SharedFuture share() noexcept {return this->future.share();}
|
||||
|
||||
/// Move constructor.
|
||||
FutureAndRequestId(FutureAndRequestId && other) noexcept = default;
|
||||
/// Deleted copy constructor, each instance is a unique owner of the future.
|
||||
FutureAndRequestId(const FutureAndRequestId & other) = delete;
|
||||
/// Move assignment.
|
||||
FutureAndRequestId & operator=(FutureAndRequestId && other) noexcept = default;
|
||||
/// Deleted copy assignment, each instance is a unique owner of the future.
|
||||
FutureAndRequestId & operator=(const FutureAndRequestId & other) = delete;
|
||||
/// Destructor.
|
||||
~FutureAndRequestId() = default;
|
||||
};
|
||||
|
||||
GenericClient(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
rcl_client_options_t & client_options);
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
SharedResponse
|
||||
create_response() override;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
std::shared_ptr<rmw_request_id_t>
|
||||
create_request_header() override;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
handle_response(
|
||||
std::shared_ptr<rmw_request_id_t> request_header,
|
||||
std::shared_ptr<void> response) override;
|
||||
|
||||
/// Send a request to the service server.
|
||||
/**
|
||||
* This method returns a `FutureAndRequestId` instance
|
||||
* that can be passed to Executor::spin_until_future_complete() to
|
||||
* wait until it has been completed.
|
||||
*
|
||||
* If the future never completes,
|
||||
* e.g. the call to Executor::spin_until_future_complete() times out,
|
||||
* GenericClient::remove_pending_request() must be called to clean the client internal state.
|
||||
* Not doing so will make the `Client` instance to use more memory each time a response is not
|
||||
* received from the service server.
|
||||
*
|
||||
* ```cpp
|
||||
* auto future = client->async_send_request(my_request);
|
||||
* if (
|
||||
* rclcpp::FutureReturnCode::TIMEOUT ==
|
||||
* executor->spin_until_future_complete(future, timeout))
|
||||
* {
|
||||
* client->remove_pending_request(future);
|
||||
* // handle timeout
|
||||
* } else {
|
||||
* handle_response(future.get());
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* \param[in] request request to be send.
|
||||
* \return a FutureAndRequestId instance.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
FutureAndRequestId
|
||||
async_send_request(const Request request);
|
||||
|
||||
/// Clean all pending requests older than a time_point.
|
||||
/**
|
||||
* \param[in] time_point Requests that were sent before this point are going to be removed.
|
||||
* \param[inout] pruned_requests Removed requests id will be pushed to the vector
|
||||
* if a pointer is provided.
|
||||
* \return number of pending requests that were removed.
|
||||
*/
|
||||
template<typename AllocatorT = std::allocator<int64_t>>
|
||||
size_t
|
||||
prune_requests_older_than(
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point,
|
||||
std::vector<int64_t, AllocatorT> * pruned_requests = nullptr)
|
||||
{
|
||||
return detail::prune_requests_older_than_impl(
|
||||
pending_requests_,
|
||||
pending_requests_mutex_,
|
||||
time_point,
|
||||
pruned_requests);
|
||||
}
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
size_t
|
||||
prune_pending_requests();
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
remove_pending_request(
|
||||
int64_t request_id);
|
||||
|
||||
/// Take the next response for this client.
|
||||
/**
|
||||
* \sa ClientBase::take_type_erased_response().
|
||||
*
|
||||
* \param[out] response_out The reference to a Service Response into
|
||||
* which the middleware will copy the response being taken.
|
||||
* \param[out] request_header_out The request header to be filled by the
|
||||
* middleware when taking, and which can be used to associate the response
|
||||
* to a specific request.
|
||||
* \returns true if the response was taken, otherwise false.
|
||||
* \throws rclcpp::exceptions::RCLError based exceptions if the underlying
|
||||
* rcl function fail.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
take_response(Response response_out, rmw_request_id_t & request_header_out)
|
||||
{
|
||||
return this->take_type_erased_response(response_out, request_header_out);
|
||||
}
|
||||
|
||||
protected:
|
||||
using CallbackInfoVariant = std::variant<
|
||||
std::promise<SharedResponse>>; // Use variant for extension
|
||||
|
||||
int64_t
|
||||
async_send_request_impl(
|
||||
const Request request,
|
||||
CallbackInfoVariant value);
|
||||
|
||||
std::optional<CallbackInfoVariant>
|
||||
get_and_erase_pending_request(
|
||||
int64_t request_number);
|
||||
|
||||
RCLCPP_DISABLE_COPY(GenericClient)
|
||||
|
||||
std::map<int64_t, std::pair<
|
||||
std::chrono::time_point<std::chrono::system_clock>,
|
||||
CallbackInfoVariant>> pending_requests_;
|
||||
std::mutex pending_requests_mutex_;
|
||||
|
||||
private:
|
||||
std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
|
||||
const rosidl_typesupport_introspection_cpp::MessageMembers * response_members_;
|
||||
};
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__GENERIC_CLIENT_HPP_
|
||||
@@ -84,13 +84,22 @@ public:
|
||||
options.event_callbacks,
|
||||
options.use_default_callbacks,
|
||||
DeliveredMessageKind::SERIALIZED_MESSAGE),
|
||||
callback_([callback](
|
||||
std::shared_ptr<const rclcpp::SerializedMessage> serialized_message,
|
||||
const rclcpp::MessageInfo & message_info) mutable {
|
||||
callback.dispatch(serialized_message, message_info);
|
||||
}),
|
||||
any_callback_(callback),
|
||||
ts_lib_(ts_lib)
|
||||
{}
|
||||
{
|
||||
TRACETOOLS_TRACEPOINT(
|
||||
rclcpp_subscription_init,
|
||||
static_cast<const void *>(get_subscription_handle().get()),
|
||||
static_cast<const void *>(this));
|
||||
TRACETOOLS_TRACEPOINT(
|
||||
rclcpp_subscription_callback_added,
|
||||
static_cast<const void *>(this),
|
||||
static_cast<const void *>(&any_callback_));
|
||||
|
||||
#ifndef TRACETOOLS_DISABLED
|
||||
any_callback_.register_callback_for_tracing();
|
||||
#endif
|
||||
}
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
virtual ~GenericSubscription() = default;
|
||||
@@ -153,10 +162,7 @@ public:
|
||||
|
||||
private:
|
||||
RCLCPP_DISABLE_COPY(GenericSubscription)
|
||||
|
||||
std::function<void(
|
||||
std::shared_ptr<const rclcpp::SerializedMessage>,
|
||||
const rclcpp::MessageInfo)> callback_;
|
||||
AnySubscriptionCallback<rclcpp::SerializedMessage, std::allocator<void>> any_callback_;
|
||||
// The type support library should stay loaded, so it is stored in the GenericSubscription
|
||||
std::shared_ptr<rcpputils::SharedLibrary> ts_lib_;
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
explicit GuardCondition(
|
||||
rclcpp::Context::SharedPtr context =
|
||||
const rclcpp::Context::SharedPtr & context =
|
||||
rclcpp::contexts::get_global_default_context(),
|
||||
rcl_guard_condition_options_t guard_condition_options =
|
||||
rcl_guard_condition_get_default_options());
|
||||
@@ -57,11 +57,6 @@ public:
|
||||
virtual
|
||||
~GuardCondition();
|
||||
|
||||
/// Return the context used when creating this guard condition.
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::Context::SharedPtr
|
||||
get_context() const;
|
||||
|
||||
/// Return the underlying rcl guard condition structure.
|
||||
RCLCPP_PUBLIC
|
||||
rcl_guard_condition_t &
|
||||
@@ -105,7 +100,7 @@ public:
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set);
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set);
|
||||
|
||||
/// Set a callback to be called whenever the guard condition is triggered.
|
||||
/**
|
||||
@@ -128,13 +123,14 @@ public:
|
||||
set_on_trigger_callback(std::function<void(size_t)> callback);
|
||||
|
||||
protected:
|
||||
rclcpp::Context::SharedPtr context_;
|
||||
rcl_guard_condition_t rcl_guard_condition_;
|
||||
std::atomic<bool> in_use_by_wait_set_{false};
|
||||
std::recursive_mutex reentrant_mutex_;
|
||||
std::function<void(size_t)> on_trigger_callback_{nullptr};
|
||||
size_t unread_count_{0};
|
||||
rcl_wait_set_t * wait_set_{nullptr};
|
||||
// the type of wait_set_ is actually rcl_wait_set_t *, but it's never
|
||||
// dereferenced, only compared to, so make it void * to avoid accidental use
|
||||
void * wait_set_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "rclcpp/clock.hpp"
|
||||
#include "rclcpp/context.hpp"
|
||||
#include "rclcpp/event.hpp"
|
||||
#include "rclcpp/generic_client.hpp"
|
||||
#include "rclcpp/generic_publisher.hpp"
|
||||
#include "rclcpp/generic_subscription.hpp"
|
||||
#include "rclcpp/logger.hpp"
|
||||
@@ -320,6 +321,22 @@ public:
|
||||
const rclcpp::QoS & qos = rclcpp::ServicesQoS(),
|
||||
rclcpp::CallbackGroup::SharedPtr group = nullptr);
|
||||
|
||||
/// Create and return a GenericClient.
|
||||
/**
|
||||
* \param[in] service_name The name on which the service is accessible.
|
||||
* \param[in] service_type The name of service type, e.g. "std_srvs/srv/SetBool"
|
||||
* \param[in] qos Quality of service profile for client.
|
||||
* \param[in] group Callback group to handle the reply to service calls.
|
||||
* \return Shared pointer to the created GenericClient.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
rclcpp::GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
const rclcpp::QoS & qos = rclcpp::ServicesQoS(),
|
||||
rclcpp::CallbackGroup::SharedPtr group = nullptr);
|
||||
|
||||
/// Create and return a GenericPublisher.
|
||||
/**
|
||||
* The returned pointer will never be empty, but this function can throw various exceptions, for
|
||||
|
||||
@@ -167,6 +167,7 @@ init_tuple(NodeT & n)
|
||||
* something like that, then you'll need to create your own specialization of
|
||||
* the NodeInterfacesSupports struct without this macro.
|
||||
*/
|
||||
// *INDENT-OFF*
|
||||
#define RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT(NodeInterfaceType, NodeInterfaceName) \
|
||||
namespace rclcpp::node_interfaces::detail { \
|
||||
template<typename StorageClassT, typename ... RemainingInterfaceTs> \
|
||||
@@ -189,7 +190,7 @@ init_tuple(NodeT & n)
|
||||
/* Perfect forwarding constructor to get arguments down to StorageClassT (eventually). */ \
|
||||
template<typename ... ArgsT> \
|
||||
explicit NodeInterfacesSupports(ArgsT && ... args) \
|
||||
: NodeInterfacesSupports<StorageClassT, RemainingInterfaceTs ...>( \
|
||||
: NodeInterfacesSupports<StorageClassT, RemainingInterfaceTs ...>( \
|
||||
std::forward<ArgsT>(args) ...) \
|
||||
{} \
|
||||
\
|
||||
@@ -200,6 +201,7 @@ init_tuple(NodeT & n)
|
||||
} \
|
||||
}; \
|
||||
} // namespace rclcpp::node_interfaces::detail
|
||||
// *INDENT-ON*
|
||||
|
||||
} // namespace detail
|
||||
} // namespace node_interfaces
|
||||
|
||||
@@ -127,7 +127,9 @@ public:
|
||||
* the RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT macro.
|
||||
*
|
||||
* Usage example:
|
||||
* ```RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT(rclcpp::node_interfaces::NodeBaseInterface, base)```
|
||||
* ```cpp
|
||||
* RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT(rclcpp::node_interfaces::NodeBaseInterface, base)
|
||||
* ```
|
||||
*
|
||||
* If you choose not to use the helper macro, then you can specialize the
|
||||
* template yourself, but you must:
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#include "rclcpp/allocator/allocator_common.hpp"
|
||||
#include "rclcpp/allocator/allocator_deleter.hpp"
|
||||
#include "rclcpp/detail/resolve_use_intra_process.hpp"
|
||||
#include "rclcpp/detail/resolve_intra_process_buffer_type.hpp"
|
||||
#include "rclcpp/experimental/buffers/intra_process_buffer.hpp"
|
||||
#include "rclcpp/experimental/create_intra_process_buffer.hpp"
|
||||
#include "rclcpp/experimental/intra_process_manager.hpp"
|
||||
#include "rclcpp/get_message_type_support_handle.hpp"
|
||||
#include "rclcpp/is_ros_compatible_type.hpp"
|
||||
@@ -109,6 +112,12 @@ public:
|
||||
[[deprecated("use std::shared_ptr<const PublishedType>")]] =
|
||||
std::shared_ptr<const PublishedType>;
|
||||
|
||||
using BufferSharedPtr = typename rclcpp::experimental::buffers::IntraProcessBuffer<
|
||||
ROSMessageType,
|
||||
ROSMessageTypeAllocator,
|
||||
ROSMessageTypeDeleter
|
||||
>::SharedPtr;
|
||||
|
||||
RCLCPP_SMART_PTR_DEFINITIONS(Publisher<MessageT, AllocatorT>)
|
||||
|
||||
/// Default constructor.
|
||||
@@ -119,8 +128,8 @@ public:
|
||||
*
|
||||
* \param[in] node_base NodeBaseInterface pointer that is used in part of the setup.
|
||||
* \param[in] topic Name of the topic to publish to.
|
||||
* \param[in] qos QoS profile for Subcription.
|
||||
* \param[in] options Options for the subscription.
|
||||
* \param[in] qos QoS profile for the publisher.
|
||||
* \param[in] options Options for the publisher.
|
||||
*/
|
||||
Publisher(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
@@ -171,11 +180,14 @@ public:
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication is not allowed with a zero qos history depth value");
|
||||
}
|
||||
if (qos.durability() != rclcpp::DurabilityPolicy::Volatile) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication allowed only with volatile durability");
|
||||
if (qos.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,
|
||||
std::make_shared<ROSMessageTypeAllocator>(ros_message_type_allocator_));
|
||||
}
|
||||
uint64_t intra_process_publisher_id = ipm->add_publisher(this->shared_from_this());
|
||||
uint64_t intra_process_publisher_id = ipm->add_publisher(this->shared_from_this(), buffer_);
|
||||
this->setup_intra_process(
|
||||
intra_process_publisher_id,
|
||||
ipm);
|
||||
@@ -242,9 +254,18 @@ public:
|
||||
if (inter_process_publish_needed) {
|
||||
auto shared_msg =
|
||||
this->do_intra_process_ros_message_publish_and_return_shared(std::move(msg));
|
||||
if (buffer_) {
|
||||
buffer_->add_shared(shared_msg);
|
||||
}
|
||||
this->do_inter_process_publish(*shared_msg);
|
||||
} else {
|
||||
this->do_intra_process_ros_message_publish(std::move(msg));
|
||||
if (buffer_) {
|
||||
auto shared_msg =
|
||||
this->do_intra_process_ros_message_publish_and_return_shared(std::move(msg));
|
||||
buffer_->add_shared(shared_msg);
|
||||
} else {
|
||||
this->do_intra_process_ros_message_publish(std::move(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,8 +290,8 @@ public:
|
||||
{
|
||||
// Avoid allocating when not using intra process.
|
||||
if (!intra_process_is_enabled_) {
|
||||
// In this case we're not using intra process.
|
||||
return this->do_inter_process_publish(msg);
|
||||
this->do_inter_process_publish(msg);
|
||||
return;
|
||||
}
|
||||
// Otherwise we have to allocate memory in a unique_ptr and pass it along.
|
||||
// As the message is not const, a copy should be made.
|
||||
@@ -297,26 +318,31 @@ public:
|
||||
>
|
||||
publish(std::unique_ptr<T, PublishedTypeDeleter> msg)
|
||||
{
|
||||
// Avoid allocating when not using intra process.
|
||||
if (!intra_process_is_enabled_) {
|
||||
// In this case we're not using intra process.
|
||||
ROSMessageType ros_msg;
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(*msg, ros_msg);
|
||||
return this->do_inter_process_publish(ros_msg);
|
||||
auto ros_msg_ptr = std::make_unique<ROSMessageType>();
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(*msg, *ros_msg_ptr);
|
||||
this->do_inter_process_publish(*ros_msg_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
bool inter_process_publish_needed =
|
||||
get_subscription_count() > get_intra_process_subscription_count();
|
||||
|
||||
if (inter_process_publish_needed) {
|
||||
ROSMessageType ros_msg;
|
||||
// TODO(clalancette): This is unnecessarily doing an additional conversion
|
||||
// that may have already been done in do_intra_process_publish_and_return_shared().
|
||||
// We should just reuse that effort.
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(*msg, ros_msg);
|
||||
auto ros_msg_ptr = std::make_shared<ROSMessageType>();
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(*msg, *ros_msg_ptr);
|
||||
this->do_intra_process_publish(std::move(msg));
|
||||
this->do_inter_process_publish(ros_msg);
|
||||
this->do_inter_process_publish(*ros_msg_ptr);
|
||||
if (buffer_) {
|
||||
buffer_->add_shared(ros_msg_ptr);
|
||||
}
|
||||
} else {
|
||||
if (buffer_) {
|
||||
auto ros_msg_ptr = std::make_shared<ROSMessageType>();
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(*msg, *ros_msg_ptr);
|
||||
buffer_->add_shared(ros_msg_ptr);
|
||||
}
|
||||
this->do_intra_process_publish(std::move(msg));
|
||||
}
|
||||
}
|
||||
@@ -339,13 +365,12 @@ public:
|
||||
>
|
||||
publish(const T & msg)
|
||||
{
|
||||
// Avoid double allocating when not using intra process.
|
||||
if (!intra_process_is_enabled_) {
|
||||
// Convert to the ROS message equivalent and publish it.
|
||||
ROSMessageType ros_msg;
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(msg, ros_msg);
|
||||
// In this case we're not using intra process.
|
||||
return this->do_inter_process_publish(ros_msg);
|
||||
auto ros_msg_ptr = std::make_unique<ROSMessageType>();
|
||||
rclcpp::TypeAdapter<MessageT>::convert_to_ros_message(msg, *ros_msg_ptr);
|
||||
this->do_inter_process_publish(*ros_msg_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise we have to allocate memory in a unique_ptr and pass it along.
|
||||
@@ -581,6 +606,8 @@ protected:
|
||||
PublishedTypeDeleter published_type_deleter_;
|
||||
ROSMessageTypeAllocator ros_message_type_allocator_;
|
||||
ROSMessageTypeDeleter ros_message_type_deleter_;
|
||||
|
||||
BufferSharedPtr buffer_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -139,6 +139,12 @@ public:
|
||||
size_t
|
||||
get_intra_process_subscription_count() const;
|
||||
|
||||
/// Get if durability is transient local
|
||||
/** \return If durability is transient local*/
|
||||
RCLCPP_PUBLIC
|
||||
bool
|
||||
is_durability_transient_local() const;
|
||||
|
||||
/// Manually assert that this Publisher is alive (for RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC).
|
||||
/**
|
||||
* If the rmw Liveliness policy is set to RMW_QOS_POLICY_LIVELINESS_MANUAL_BY_TOPIC, the creator
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "rclcpp/allocator/allocator_common.hpp"
|
||||
#include "rclcpp/detail/rmw_implementation_specific_publisher_payload.hpp"
|
||||
#include "rclcpp/intra_process_buffer_type.hpp"
|
||||
#include "rclcpp/intra_process_setting.hpp"
|
||||
#include "rclcpp/qos.hpp"
|
||||
#include "rclcpp/event_handler.hpp"
|
||||
@@ -40,6 +41,9 @@ struct PublisherOptionsBase
|
||||
/// Setting to explicitly set intraprocess communications.
|
||||
IntraProcessSetting use_intra_process_comm = IntraProcessSetting::NodeDefault;
|
||||
|
||||
/// Setting the data-type stored in the intraprocess buffer
|
||||
IntraProcessBufferType intra_process_buffer_type = IntraProcessBufferType::SharedPtr;
|
||||
|
||||
/// Callbacks for various events related to publishers.
|
||||
PublisherEventCallbacks event_callbacks;
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2019 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__QOS_EVENT_HPP_
|
||||
#define RCLCPP__QOS_EVENT_HPP_
|
||||
|
||||
#warning This header is obsolete, please include rclcpp/event_handler.hpp instead
|
||||
|
||||
#include "rclcpp/event_handler.hpp"
|
||||
|
||||
#endif // RCLCPP__QOS_EVENT_HPP_
|
||||
@@ -307,7 +307,7 @@ public:
|
||||
* \param[in] node_handle NodeBaseInterface pointer that is used in part of the setup.
|
||||
* \param[in] service_name Name of the topic to publish to.
|
||||
* \param[in] any_callback User defined callback to call when a client request is received.
|
||||
* \param[in] service_options options for the subscription.
|
||||
* \param[in] service_options options for the service.
|
||||
*/
|
||||
Service(
|
||||
std::shared_ptr<rcl_node_t> node_handle,
|
||||
@@ -499,7 +499,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure client introspection.
|
||||
/// Configure service 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
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "rcl/allocator.h"
|
||||
|
||||
@@ -121,8 +120,8 @@ public:
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < waitable_handles_.size(); ++i) {
|
||||
if (waitable_handles_[i]->is_ready(wait_set)) {
|
||||
waitable_triggered_handles_.emplace_back(std::move(waitable_handles_[i]));
|
||||
if (!waitable_handles_[i]->is_ready(*wait_set)) {
|
||||
waitable_handles_[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +145,10 @@ public:
|
||||
timer_handles_.end()
|
||||
);
|
||||
|
||||
waitable_handles_.clear();
|
||||
waitable_handles_.erase(
|
||||
std::remove(waitable_handles_.begin(), waitable_handles_.end(), nullptr),
|
||||
waitable_handles_.end()
|
||||
);
|
||||
}
|
||||
|
||||
bool collect_entities(const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) override
|
||||
@@ -235,7 +237,7 @@ public:
|
||||
}
|
||||
|
||||
for (const std::shared_ptr<Waitable> & waitable : waitable_handles_) {
|
||||
waitable->add_to_wait_set(wait_set);
|
||||
waitable->add_to_wait_set(*wait_set);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -368,7 +370,8 @@ public:
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (!timer->call()) {
|
||||
auto data = timer->call();
|
||||
if (!data) {
|
||||
// timer was cancelled, skip it.
|
||||
++it;
|
||||
continue;
|
||||
@@ -377,6 +380,7 @@ public:
|
||||
any_exec.timer = timer;
|
||||
any_exec.callback_group = group;
|
||||
any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);
|
||||
any_exec.data = data;
|
||||
timer_handles_.erase(it);
|
||||
return;
|
||||
}
|
||||
@@ -390,9 +394,8 @@ public:
|
||||
rclcpp::AnyExecutable & any_exec,
|
||||
const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) override
|
||||
{
|
||||
auto & waitable_handles = waitable_triggered_handles_;
|
||||
auto it = waitable_handles.begin();
|
||||
while (it != waitable_handles.end()) {
|
||||
auto it = waitable_handles_.begin();
|
||||
while (it != waitable_handles_.end()) {
|
||||
std::shared_ptr<Waitable> & waitable = *it;
|
||||
if (waitable) {
|
||||
// Find the group for this handle and see if it can be serviced
|
||||
@@ -400,7 +403,7 @@ public:
|
||||
if (!group) {
|
||||
// Group was not found, meaning the waitable is not valid...
|
||||
// Remove it from the ready list and continue looking
|
||||
it = waitable_handles.erase(it);
|
||||
it = waitable_handles_.erase(it);
|
||||
continue;
|
||||
}
|
||||
if (!group->can_be_taken_from().load()) {
|
||||
@@ -413,11 +416,11 @@ public:
|
||||
any_exec.waitable = waitable;
|
||||
any_exec.callback_group = group;
|
||||
any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);
|
||||
waitable_handles.erase(it);
|
||||
waitable_handles_.erase(it);
|
||||
return;
|
||||
}
|
||||
// Else, the waitable is no longer valid, remove it and continue
|
||||
it = waitable_handles.erase(it);
|
||||
it = waitable_handles_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,8 +501,6 @@ private:
|
||||
VectorRebind<std::shared_ptr<const rcl_timer_t>> timer_handles_;
|
||||
VectorRebind<std::shared_ptr<Waitable>> waitable_handles_;
|
||||
|
||||
VectorRebind<std::shared_ptr<Waitable>> waitable_triggered_handles_;
|
||||
|
||||
std::shared_ptr<VoidAlloc> allocator_;
|
||||
};
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ public:
|
||||
* of the following conditions are true: qos_profile.history == RMW_QOS_POLICY_HISTORY_KEEP_ALL,
|
||||
* qos_profile.depth == 0 or qos_profile.durability != RMW_QOS_POLICY_DURABILITY_VOLATILE).
|
||||
*/
|
||||
// *INDENT-OFF*
|
||||
Subscription(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
const rosidl_message_type_support_t & type_support_handle,
|
||||
@@ -148,6 +149,7 @@ public:
|
||||
any_callback_(callback),
|
||||
options_(options),
|
||||
message_memory_strategy_(message_memory_strategy)
|
||||
// *INDENT-ON*
|
||||
{
|
||||
// Setup intra process publishing if requested.
|
||||
if (rclcpp::detail::resolve_use_intra_process(options_, *node_base)) {
|
||||
@@ -163,10 +165,6 @@ public:
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication is not allowed with 0 depth qos policy");
|
||||
}
|
||||
if (qos_profile.durability() != rclcpp::DurabilityPolicy::Volatile) {
|
||||
throw std::invalid_argument(
|
||||
"intraprocess communication allowed only with volatile durability");
|
||||
}
|
||||
|
||||
using SubscriptionIntraProcessT = rclcpp::experimental::SubscriptionIntraProcess<
|
||||
MessageT,
|
||||
@@ -193,7 +191,8 @@ public:
|
||||
// Add it to the intra process manager.
|
||||
using rclcpp::experimental::IntraProcessManager;
|
||||
auto ipm = context->get_sub_context<IntraProcessManager>();
|
||||
uint64_t intra_process_subscription_id = ipm->add_subscription(subscription_intra_process_);
|
||||
uint64_t intra_process_subscription_id = ipm->template add_subscription<
|
||||
ROSMessageType, ROSMessageTypeAllocator>(subscription_intra_process_);
|
||||
this->setup_intra_process(intra_process_subscription_id, ipm);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
/**
|
||||
* \param nanoseconds since time epoch
|
||||
* \param clock_type clock type
|
||||
* \throws std::runtime_error if nanoseconds are negative
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
explicit Time(int64_t nanoseconds = 0, rcl_clock_type_t clock_type = RCL_SYSTEM_TIME);
|
||||
@@ -57,6 +58,10 @@ public:
|
||||
RCLCPP_PUBLIC
|
||||
Time(const Time & rhs);
|
||||
|
||||
/// Move constructor
|
||||
RCLCPP_PUBLIC
|
||||
Time(Time && rhs) noexcept;
|
||||
|
||||
/// Time constructor
|
||||
/**
|
||||
* \param time_msg builtin_interfaces time message to copy
|
||||
@@ -84,6 +89,7 @@ public:
|
||||
operator builtin_interfaces::msg::Time() const;
|
||||
|
||||
/**
|
||||
* Copy assignment operator
|
||||
* \throws std::runtime_error if seconds are negative
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
@@ -100,6 +106,13 @@ public:
|
||||
Time &
|
||||
operator=(const builtin_interfaces::msg::Time & time_msg);
|
||||
|
||||
/**
|
||||
* Move assignment operator
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
Time &
|
||||
operator=(Time && rhs) noexcept;
|
||||
|
||||
/**
|
||||
* \throws std::runtime_error if the time sources are different
|
||||
*/
|
||||
@@ -222,6 +235,15 @@ RCLCPP_PUBLIC
|
||||
Time
|
||||
operator+(const rclcpp::Duration & lhs, const rclcpp::Time & rhs);
|
||||
|
||||
/// Convert rcl_time_point_value_t to builtin_interfaces::msg::Time
|
||||
/**
|
||||
* \param[in] time_point is a rcl_time_point_value_t
|
||||
* \return the builtin_interfaces::msg::Time from the time_point
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
builtin_interfaces::msg::Time
|
||||
convert_rcl_time_to_sec_nanos(const rcl_time_point_value_t & time_point);
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
#endif // RCLCPP__TIME_HPP_
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
@@ -43,6 +44,12 @@
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
struct TimerInfo
|
||||
{
|
||||
Time expected_call_time;
|
||||
Time actual_call_time;
|
||||
};
|
||||
|
||||
class TimerBase
|
||||
{
|
||||
public:
|
||||
@@ -101,16 +108,20 @@ public:
|
||||
* The multithreaded executor takes advantage of this to avoid scheduling
|
||||
* the callback multiple times.
|
||||
*
|
||||
* \return `true` if the callback should be executed, `false` if the timer was canceled.
|
||||
* \return a valid shared_ptr if the callback should be executed,
|
||||
* an invalid shared_ptr (nullptr) if the timer was canceled.
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual bool
|
||||
virtual std::shared_ptr<void>
|
||||
call() = 0;
|
||||
|
||||
/// Call the callback function when the timer signal is emitted.
|
||||
/**
|
||||
* \param[in] data the pointer returned by the call function
|
||||
*/
|
||||
RCLCPP_PUBLIC
|
||||
virtual void
|
||||
execute_callback() = 0;
|
||||
execute_callback(const std::shared_ptr<void> & data) = 0;
|
||||
|
||||
RCLCPP_PUBLIC
|
||||
std::shared_ptr<const rcl_timer_t>
|
||||
@@ -198,16 +209,17 @@ protected:
|
||||
set_on_reset_callback(rcl_event_callback_t callback, const void * user_data);
|
||||
};
|
||||
|
||||
|
||||
using VoidCallbackType = std::function<void ()>;
|
||||
using TimerCallbackType = std::function<void (TimerBase &)>;
|
||||
using TimerInfoCallbackType = std::function<void (const TimerInfo &)>;
|
||||
|
||||
/// Generic timer. Periodically executes a user-specified callback.
|
||||
template<
|
||||
typename FunctorT,
|
||||
typename std::enable_if<
|
||||
rclcpp::function_traits::same_arguments<FunctorT, VoidCallbackType>::value ||
|
||||
rclcpp::function_traits::same_arguments<FunctorT, TimerCallbackType>::value
|
||||
rclcpp::function_traits::same_arguments<FunctorT, TimerCallbackType>::value ||
|
||||
rclcpp::function_traits::same_arguments<FunctorT, TimerInfoCallbackType>::value
|
||||
>::type * = nullptr
|
||||
>
|
||||
class GenericTimer : public TimerBase
|
||||
@@ -256,27 +268,28 @@ public:
|
||||
* \sa rclcpp::TimerBase::call
|
||||
* \throws std::runtime_error if it failed to notify timer that callback will occurr
|
||||
*/
|
||||
bool
|
||||
std::shared_ptr<void>
|
||||
call() override
|
||||
{
|
||||
rcl_ret_t ret = rcl_timer_call(timer_handle_.get());
|
||||
auto timer_call_info_ = std::make_shared<rcl_timer_call_info_t>();
|
||||
rcl_ret_t ret = rcl_timer_call_with_info(timer_handle_.get(), timer_call_info_.get());
|
||||
if (ret == RCL_RET_TIMER_CANCELED) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
if (ret != RCL_RET_OK) {
|
||||
throw std::runtime_error("Failed to notify timer that callback occurred");
|
||||
}
|
||||
return true;
|
||||
return timer_call_info_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \sa rclcpp::TimerBase::execute_callback
|
||||
*/
|
||||
void
|
||||
execute_callback() override
|
||||
execute_callback(const std::shared_ptr<void> & data) override
|
||||
{
|
||||
TRACETOOLS_TRACEPOINT(callback_start, reinterpret_cast<const void *>(&callback_), false);
|
||||
execute_callback_delegate<>();
|
||||
execute_callback_delegate<>(*static_cast<rcl_timer_call_info_t *>(data.get()));
|
||||
TRACETOOLS_TRACEPOINT(callback_end, reinterpret_cast<const void *>(&callback_));
|
||||
}
|
||||
|
||||
@@ -288,7 +301,7 @@ public:
|
||||
>::type * = nullptr
|
||||
>
|
||||
void
|
||||
execute_callback_delegate()
|
||||
execute_callback_delegate(const rcl_timer_call_info_t &)
|
||||
{
|
||||
callback_();
|
||||
}
|
||||
@@ -300,11 +313,26 @@ public:
|
||||
>::type * = nullptr
|
||||
>
|
||||
void
|
||||
execute_callback_delegate()
|
||||
execute_callback_delegate(const rcl_timer_call_info_t &)
|
||||
{
|
||||
callback_(*this);
|
||||
}
|
||||
|
||||
|
||||
template<
|
||||
typename CallbackT = FunctorT,
|
||||
typename std::enable_if<
|
||||
rclcpp::function_traits::same_arguments<CallbackT, TimerInfoCallbackType>::value
|
||||
>::type * = nullptr
|
||||
>
|
||||
void
|
||||
execute_callback_delegate(const rcl_timer_call_info_t & timer_call_info)
|
||||
{
|
||||
const TimerInfo info{Time{timer_call_info.expected_call_time, clock_->get_clock_type()},
|
||||
Time{timer_call_info.actual_call_time, clock_->get_clock_type()}};
|
||||
callback_(info);
|
||||
}
|
||||
|
||||
/// Is the clock steady (i.e. is the time between ticks constant?)
|
||||
/** \return True if the clock used by this timer is steady. */
|
||||
bool
|
||||
@@ -323,7 +351,8 @@ template<
|
||||
typename FunctorT,
|
||||
typename std::enable_if<
|
||||
rclcpp::function_traits::same_arguments<FunctorT, VoidCallbackType>::value ||
|
||||
rclcpp::function_traits::same_arguments<FunctorT, TimerCallbackType>::value
|
||||
rclcpp::function_traits::same_arguments<FunctorT, TimerCallbackType>::value ||
|
||||
rclcpp::function_traits::same_arguments<FunctorT, TimerInfoCallbackType>::value
|
||||
>::type * = nullptr
|
||||
>
|
||||
class WallTimer : public GenericTimer<FunctorT>
|
||||
|
||||
@@ -17,13 +17,21 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "rcl/wait.h"
|
||||
|
||||
#include "rclcpp/macros.hpp"
|
||||
#include "rclcpp/wait_result_kind.hpp"
|
||||
|
||||
#include "rclcpp/client.hpp"
|
||||
#include "rclcpp/service.hpp"
|
||||
#include "rclcpp/subscription_base.hpp"
|
||||
#include "rclcpp/timer.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
@@ -134,6 +142,151 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next ready timer and its index in the wait result, but do not clear it.
|
||||
/**
|
||||
* The returned timer is not cleared automatically, as it the case with the
|
||||
* other next_ready_*()-like functions.
|
||||
* Instead, this function returns the timer and the index that identifies it
|
||||
* in the wait result, so that it can be cleared (marked as taken or used)
|
||||
* in a separate step with clear_timer_with_index().
|
||||
* This is necessary in some multi-threaded executor implementations.
|
||||
*
|
||||
* If the timer is not cleared using the index, subsequent calls to this
|
||||
* function will return the same timer.
|
||||
*
|
||||
* If there is no ready timer, then nullptr will be returned and the index
|
||||
* will be invalid and should not be used.
|
||||
*
|
||||
* \param[in] start_index index at which to start searching for the next ready
|
||||
* timer in the wait result. If the start_index is out of bounds for the
|
||||
* list of timers in the wait result, then {nullptr, start_index} will be
|
||||
* returned. Defaults to 0.
|
||||
* \return next ready timer pointer and its index in the wait result, or
|
||||
* {nullptr, start_index} if none was found.
|
||||
*/
|
||||
std::pair<std::shared_ptr<rclcpp::TimerBase>, size_t>
|
||||
peek_next_ready_timer(size_t start_index = 0)
|
||||
{
|
||||
check_wait_result_dirty();
|
||||
auto ret = std::shared_ptr<rclcpp::TimerBase>{nullptr};
|
||||
size_t ii = start_index;
|
||||
if (this->kind() == WaitResultKind::Ready) {
|
||||
auto & wait_set = this->get_wait_set();
|
||||
auto & rcl_wait_set = wait_set.storage_get_rcl_wait_set();
|
||||
for (; ii < wait_set.size_of_timers(); ++ii) {
|
||||
if (rcl_wait_set.timers[ii] != nullptr) {
|
||||
ret = wait_set.timers(ii);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {ret, ii};
|
||||
}
|
||||
|
||||
/// Clear the timer at the given index.
|
||||
/**
|
||||
* Clearing a timer from the wait result prevents it from being returned by
|
||||
* the peek_next_ready_timer() on subsequent calls.
|
||||
*
|
||||
* The index should come from the peek_next_ready_timer() function, and
|
||||
* should only be used with this function if the timer pointer was valid.
|
||||
*
|
||||
* \throws std::out_of_range if the given index is out of range
|
||||
*/
|
||||
void
|
||||
clear_timer_with_index(size_t index)
|
||||
{
|
||||
auto & wait_set = this->get_wait_set();
|
||||
auto & rcl_wait_set = wait_set.storage_get_rcl_wait_set();
|
||||
if (index >= wait_set.size_of_timers()) {
|
||||
throw std::out_of_range("given timer index is out of range");
|
||||
}
|
||||
rcl_wait_set.timers[index] = nullptr;
|
||||
}
|
||||
|
||||
/// Get the next ready subscription, clearing it from the wait result.
|
||||
std::shared_ptr<rclcpp::SubscriptionBase>
|
||||
next_ready_subscription()
|
||||
{
|
||||
check_wait_result_dirty();
|
||||
auto ret = std::shared_ptr<rclcpp::SubscriptionBase>{nullptr};
|
||||
if (this->kind() == WaitResultKind::Ready) {
|
||||
auto & wait_set = this->get_wait_set();
|
||||
auto & rcl_wait_set = wait_set.storage_get_rcl_wait_set();
|
||||
for (size_t ii = 0; ii < wait_set.size_of_subscriptions(); ++ii) {
|
||||
if (rcl_wait_set.subscriptions[ii] != nullptr) {
|
||||
ret = wait_set.subscriptions(ii);
|
||||
rcl_wait_set.subscriptions[ii] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Get the next ready service, clearing it from the wait result.
|
||||
std::shared_ptr<rclcpp::ServiceBase>
|
||||
next_ready_service()
|
||||
{
|
||||
check_wait_result_dirty();
|
||||
auto ret = std::shared_ptr<rclcpp::ServiceBase>{nullptr};
|
||||
if (this->kind() == WaitResultKind::Ready) {
|
||||
auto & wait_set = this->get_wait_set();
|
||||
auto & rcl_wait_set = wait_set.storage_get_rcl_wait_set();
|
||||
for (size_t ii = 0; ii < wait_set.size_of_services(); ++ii) {
|
||||
if (rcl_wait_set.services[ii] != nullptr) {
|
||||
ret = wait_set.services(ii);
|
||||
rcl_wait_set.services[ii] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Get the next ready client, clearing it from the wait result.
|
||||
std::shared_ptr<rclcpp::ClientBase>
|
||||
next_ready_client()
|
||||
{
|
||||
check_wait_result_dirty();
|
||||
auto ret = std::shared_ptr<rclcpp::ClientBase>{nullptr};
|
||||
if (this->kind() == WaitResultKind::Ready) {
|
||||
auto & wait_set = this->get_wait_set();
|
||||
auto & rcl_wait_set = wait_set.storage_get_rcl_wait_set();
|
||||
for (size_t ii = 0; ii < wait_set.size_of_clients(); ++ii) {
|
||||
if (rcl_wait_set.clients[ii] != nullptr) {
|
||||
ret = wait_set.clients(ii);
|
||||
rcl_wait_set.clients[ii] = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Get the next ready waitable, clearing it from the wait result.
|
||||
std::shared_ptr<rclcpp::Waitable>
|
||||
next_ready_waitable()
|
||||
{
|
||||
check_wait_result_dirty();
|
||||
auto waitable = std::shared_ptr<rclcpp::Waitable>{nullptr};
|
||||
auto data = std::shared_ptr<void>{nullptr};
|
||||
|
||||
if (this->kind() == WaitResultKind::Ready) {
|
||||
auto & wait_set = this->get_wait_set();
|
||||
auto & rcl_wait_set = wait_set.get_rcl_wait_set();
|
||||
while (next_waitable_index_ < wait_set.size_of_waitables()) {
|
||||
auto cur_waitable = wait_set.waitables(next_waitable_index_++);
|
||||
if (cur_waitable != nullptr && cur_waitable->is_ready(rcl_wait_set)) {
|
||||
waitable = cur_waitable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return waitable;
|
||||
}
|
||||
|
||||
private:
|
||||
RCLCPP_DISABLE_COPY(WaitResult)
|
||||
|
||||
@@ -151,12 +304,25 @@ private:
|
||||
// Should be enforced by the static factory methods on this class.
|
||||
assert(WaitResultKind::Ready == wait_result_kind);
|
||||
// Secure thread-safety (if provided) and shared ownership (if needed).
|
||||
wait_set_pointer_->wait_result_acquire();
|
||||
this->get_wait_set().wait_result_acquire();
|
||||
}
|
||||
|
||||
const WaitResultKind wait_result_kind_;
|
||||
/// Check if the wait result is invalid because the wait set was modified.
|
||||
void
|
||||
check_wait_result_dirty()
|
||||
{
|
||||
// In the case that the wait set was modified while the result was out,
|
||||
// we must mark the wait result as no longer valid
|
||||
if (wait_set_pointer_ && this->get_wait_set().wait_result_dirty_) {
|
||||
this->wait_result_kind_ = WaitResultKind::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
WaitResultKind wait_result_kind_;
|
||||
|
||||
WaitSetT * wait_set_pointer_ = nullptr;
|
||||
|
||||
size_t next_waitable_index_ = 0;
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -26,6 +26,7 @@ enum RCLCPP_PUBLIC WaitResultKind
|
||||
Ready, //<! Kind used when something in the wait set was ready.
|
||||
Timeout, //<! Kind used when the wait resulted in a timeout.
|
||||
Empty, //<! Kind used when trying to wait on an empty wait set.
|
||||
Invalid, //<! Kind used when wait result has been invalidated.
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -104,7 +104,7 @@ protected:
|
||||
// TODO(wjwwood): support custom allocator, maybe restrict to polymorphic allocator
|
||||
rcl_get_default_allocator());
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Failed to create wait set");
|
||||
}
|
||||
|
||||
// (Re)build the wait set for the first time.
|
||||
@@ -192,8 +192,7 @@ protected:
|
||||
size_t services_from_waitables = 0;
|
||||
size_t events_from_waitables = 0;
|
||||
for (const auto & waitable_entry : waitables) {
|
||||
auto waitable_ptr_pair = get_raw_pointer_from_smart_pointer(waitable_entry.waitable);
|
||||
if (nullptr == waitable_ptr_pair.second) {
|
||||
if (!waitable_entry.waitable) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -204,7 +203,7 @@ protected:
|
||||
needs_pruning_ = true;
|
||||
continue;
|
||||
}
|
||||
rclcpp::Waitable & waitable = *waitable_ptr_pair.second;
|
||||
auto & waitable = *waitable_entry.waitable;
|
||||
subscriptions_from_waitables += waitable.get_number_of_ready_subscriptions();
|
||||
guard_conditions_from_waitables += waitable.get_number_of_ready_guard_conditions();
|
||||
timers_from_waitables += waitable.get_number_of_ready_timers();
|
||||
@@ -222,7 +221,7 @@ protected:
|
||||
events_from_waitables
|
||||
);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't resize the wait set");
|
||||
}
|
||||
was_resized = true;
|
||||
// Assumption: the calling code ensures this function is not called
|
||||
@@ -238,15 +237,13 @@ protected:
|
||||
if (!was_resized) {
|
||||
rcl_ret_t ret = rcl_wait_set_clear(&rcl_wait_set_);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't clear the wait set");
|
||||
}
|
||||
}
|
||||
|
||||
// Add subscriptions.
|
||||
for (const auto & subscription_entry : subscriptions) {
|
||||
auto subscription_ptr_pair =
|
||||
get_raw_pointer_from_smart_pointer(subscription_entry.subscription);
|
||||
if (nullptr == subscription_ptr_pair.second) {
|
||||
if (!subscription_entry.subscription) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -257,12 +254,13 @@ protected:
|
||||
needs_pruning_ = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
rcl_ret_t ret = rcl_wait_set_add_subscription(
|
||||
&rcl_wait_set_,
|
||||
subscription_ptr_pair.second->get_subscription_handle().get(),
|
||||
subscription_entry.subscription->get_subscription_handle().get(),
|
||||
nullptr);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't fill wait set");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +269,7 @@ protected:
|
||||
[this](const auto & inner_guard_conditions)
|
||||
{
|
||||
for (const auto & guard_condition : inner_guard_conditions) {
|
||||
auto guard_condition_ptr_pair = get_raw_pointer_from_smart_pointer(guard_condition);
|
||||
if (nullptr == guard_condition_ptr_pair.second) {
|
||||
if (!guard_condition) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -285,10 +282,10 @@ protected:
|
||||
}
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(
|
||||
&rcl_wait_set_,
|
||||
&guard_condition_ptr_pair.second->get_rcl_guard_condition(),
|
||||
&guard_condition->get_rcl_guard_condition(),
|
||||
nullptr);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't fill wait set");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -301,8 +298,7 @@ protected:
|
||||
|
||||
// Add timers.
|
||||
for (const auto & timer : timers) {
|
||||
auto timer_ptr_pair = get_raw_pointer_from_smart_pointer(timer);
|
||||
if (nullptr == timer_ptr_pair.second) {
|
||||
if (!timer) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -315,17 +311,16 @@ protected:
|
||||
}
|
||||
rcl_ret_t ret = rcl_wait_set_add_timer(
|
||||
&rcl_wait_set_,
|
||||
timer_ptr_pair.second->get_timer_handle().get(),
|
||||
timer->get_timer_handle().get(),
|
||||
nullptr);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't fill wait set");
|
||||
}
|
||||
}
|
||||
|
||||
// Add clients.
|
||||
for (const auto & client : clients) {
|
||||
auto client_ptr_pair = get_raw_pointer_from_smart_pointer(client);
|
||||
if (nullptr == client_ptr_pair.second) {
|
||||
if (!client) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -338,7 +333,7 @@ protected:
|
||||
}
|
||||
rcl_ret_t ret = rcl_wait_set_add_client(
|
||||
&rcl_wait_set_,
|
||||
client_ptr_pair.second->get_client_handle().get(),
|
||||
client->get_client_handle().get(),
|
||||
nullptr);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
@@ -347,8 +342,7 @@ protected:
|
||||
|
||||
// Add services.
|
||||
for (const auto & service : services) {
|
||||
auto service_ptr_pair = get_raw_pointer_from_smart_pointer(service);
|
||||
if (nullptr == service_ptr_pair.second) {
|
||||
if (!service) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -361,17 +355,16 @@ protected:
|
||||
}
|
||||
rcl_ret_t ret = rcl_wait_set_add_service(
|
||||
&rcl_wait_set_,
|
||||
service_ptr_pair.second->get_service_handle().get(),
|
||||
service->get_service_handle().get(),
|
||||
nullptr);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "Couldn't fill wait set");
|
||||
}
|
||||
}
|
||||
|
||||
// Add waitables.
|
||||
for (auto & waitable_entry : waitables) {
|
||||
auto waitable_ptr_pair = get_raw_pointer_from_smart_pointer(waitable_entry.waitable);
|
||||
if (nullptr == waitable_ptr_pair.second) {
|
||||
if (!waitable_entry.waitable) {
|
||||
// In this case it was probably stored as a weak_ptr, but is now locking to nullptr.
|
||||
if (HasStrongOwnership) {
|
||||
// This will not happen in fixed sized storage, as it holds
|
||||
@@ -382,8 +375,7 @@ protected:
|
||||
needs_pruning_ = true;
|
||||
continue;
|
||||
}
|
||||
rclcpp::Waitable & waitable = *waitable_ptr_pair.second;
|
||||
waitable.add_to_wait_set(&rcl_wait_set_);
|
||||
waitable_entry.waitable->add_to_wait_set(rcl_wait_set_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +397,32 @@ protected:
|
||||
needs_resize_ = true;
|
||||
}
|
||||
|
||||
size_t size_of_subscriptions() const {return 0;}
|
||||
size_t size_of_timers() const {return 0;}
|
||||
size_t size_of_clients() const {return 0;}
|
||||
size_t size_of_services() const {return 0;}
|
||||
size_t size_of_waitables() const {return 0;}
|
||||
|
||||
template<class SubscriptionsIterable>
|
||||
typename SubscriptionsIterable::value_type
|
||||
subscriptions(size_t) const {return nullptr;}
|
||||
|
||||
template<class TimersIterable>
|
||||
typename TimersIterable::value_type
|
||||
timers(size_t) const {return nullptr;}
|
||||
|
||||
template<class ClientsIterable>
|
||||
typename ClientsIterable::value_type
|
||||
clients(size_t) const {return nullptr;}
|
||||
|
||||
template<class ServicesIterable>
|
||||
typename ServicesIterable::value_type
|
||||
services(size_t) const {return nullptr;}
|
||||
|
||||
template<class WaitablesIterable>
|
||||
typename WaitablesIterable::value_type
|
||||
waitables(size_t) const {return nullptr;}
|
||||
|
||||
rcl_wait_set_t rcl_wait_set_;
|
||||
rclcpp::Context::SharedPtr context_;
|
||||
|
||||
|
||||
@@ -204,15 +204,19 @@ public:
|
||||
void
|
||||
storage_rebuild_rcl_wait_set(const ArrayOfExtraGuardConditions & extra_guard_conditions)
|
||||
{
|
||||
this->storage_acquire_ownerships();
|
||||
|
||||
this->storage_rebuild_rcl_wait_set_with_sets(
|
||||
subscriptions_,
|
||||
guard_conditions_,
|
||||
shared_subscriptions_,
|
||||
shared_guard_conditions_,
|
||||
extra_guard_conditions,
|
||||
timers_,
|
||||
clients_,
|
||||
services_,
|
||||
waitables_
|
||||
shared_timers_,
|
||||
shared_clients_,
|
||||
shared_services_,
|
||||
shared_waitables_
|
||||
);
|
||||
|
||||
this->storage_release_ownerships();
|
||||
}
|
||||
|
||||
template<class EntityT, class SequenceOfEntitiesT>
|
||||
@@ -382,6 +386,8 @@ public:
|
||||
return weak_ptr.expired();
|
||||
};
|
||||
// remove guard conditions which have been deleted
|
||||
subscriptions_.erase(
|
||||
std::remove_if(subscriptions_.begin(), subscriptions_.end(), p), subscriptions_.end());
|
||||
guard_conditions_.erase(
|
||||
std::remove_if(guard_conditions_.begin(), guard_conditions_.end(), p),
|
||||
guard_conditions_.end());
|
||||
@@ -407,6 +413,7 @@ public:
|
||||
}
|
||||
};
|
||||
// Lock all the weak pointers and hold them until released.
|
||||
lock_all(subscriptions_, shared_subscriptions_);
|
||||
lock_all(guard_conditions_, shared_guard_conditions_);
|
||||
lock_all(timers_, shared_timers_);
|
||||
lock_all(clients_, shared_clients_);
|
||||
@@ -438,6 +445,7 @@ public:
|
||||
shared_ptr.reset();
|
||||
}
|
||||
};
|
||||
reset_all(shared_subscriptions_);
|
||||
reset_all(shared_guard_conditions_);
|
||||
reset_all(shared_timers_);
|
||||
reset_all(shared_clients_);
|
||||
@@ -445,6 +453,61 @@ public:
|
||||
reset_all(shared_waitables_);
|
||||
}
|
||||
|
||||
size_t size_of_subscriptions() const
|
||||
{
|
||||
return shared_subscriptions_.size();
|
||||
}
|
||||
|
||||
size_t size_of_timers() const
|
||||
{
|
||||
return shared_timers_.size();
|
||||
}
|
||||
|
||||
size_t size_of_clients() const
|
||||
{
|
||||
return shared_clients_.size();
|
||||
}
|
||||
|
||||
size_t size_of_services() const
|
||||
{
|
||||
return shared_services_.size();
|
||||
}
|
||||
|
||||
size_t size_of_waitables() const
|
||||
{
|
||||
return shared_waitables_.size();
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::SubscriptionBase>
|
||||
subscriptions(size_t ii) const
|
||||
{
|
||||
return shared_subscriptions_[ii].subscription;
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::TimerBase>
|
||||
timers(size_t ii) const
|
||||
{
|
||||
return shared_timers_[ii];
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::ClientBase>
|
||||
clients(size_t ii) const
|
||||
{
|
||||
return shared_clients_[ii];
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::ServiceBase>
|
||||
services(size_t ii) const
|
||||
{
|
||||
return shared_services_[ii];
|
||||
}
|
||||
|
||||
std::shared_ptr<rclcpp::Waitable>
|
||||
waitables(size_t ii) const
|
||||
{
|
||||
return shared_waitables_[ii].waitable;
|
||||
}
|
||||
|
||||
size_t ownership_reference_counter_ = 0;
|
||||
|
||||
SequenceOfWeakSubscriptions subscriptions_;
|
||||
|
||||
@@ -290,7 +290,7 @@ protected:
|
||||
return create_wait_result(WaitResultKind::Empty);
|
||||
} else {
|
||||
// Some other error case, throw.
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret);
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "rcl_wait() failed");
|
||||
}
|
||||
} while (should_loop());
|
||||
|
||||
|
||||
@@ -188,6 +188,61 @@ public:
|
||||
// Explicitly do nothing.
|
||||
}
|
||||
|
||||
size_t size_of_subscriptions() const
|
||||
{
|
||||
return subscriptions_.size();
|
||||
}
|
||||
|
||||
size_t size_of_timers() const
|
||||
{
|
||||
return timers_.size();
|
||||
}
|
||||
|
||||
size_t size_of_clients() const
|
||||
{
|
||||
return clients_.size();
|
||||
}
|
||||
|
||||
size_t size_of_services() const
|
||||
{
|
||||
return services_.size();
|
||||
}
|
||||
|
||||
size_t size_of_waitables() const
|
||||
{
|
||||
return waitables_.size();
|
||||
}
|
||||
|
||||
typename ArrayOfSubscriptions::value_type
|
||||
subscriptions(size_t ii) const
|
||||
{
|
||||
return subscriptions_[ii];
|
||||
}
|
||||
|
||||
typename ArrayOfTimers::value_type
|
||||
timers(size_t ii) const
|
||||
{
|
||||
return timers_[ii];
|
||||
}
|
||||
|
||||
typename ArrayOfClients::value_type
|
||||
clients(size_t ii) const
|
||||
{
|
||||
return clients_[ii];
|
||||
}
|
||||
|
||||
typename ArrayOfServices::value_type
|
||||
services(size_t ii) const
|
||||
{
|
||||
return services_[ii];
|
||||
}
|
||||
|
||||
typename ArrayOfWaitables::value_type
|
||||
waitables(size_t ii) const
|
||||
{
|
||||
return waitables_[ii];
|
||||
}
|
||||
|
||||
const ArrayOfSubscriptions subscriptions_;
|
||||
const ArrayOfGuardConditions guard_conditions_;
|
||||
const ArrayOfTimers timers_;
|
||||
|
||||
@@ -153,6 +153,7 @@ public:
|
||||
throw std::runtime_error("subscription already associated with a wait set");
|
||||
}
|
||||
this->storage_add_subscription(std::move(local_subscription));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
}
|
||||
if (mask.include_events) {
|
||||
for (auto key_event_pair : inner_subscription->get_event_handlers()) {
|
||||
@@ -164,6 +165,7 @@ public:
|
||||
throw std::runtime_error("subscription event already associated with a wait set");
|
||||
}
|
||||
this->storage_add_waitable(std::move(event), std::move(local_subscription));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
}
|
||||
}
|
||||
if (mask.include_intra_process_waitable) {
|
||||
@@ -180,6 +182,7 @@ public:
|
||||
this->storage_add_waitable(
|
||||
std::move(inner_subscription->get_intra_process_waitable()),
|
||||
std::move(local_subscription));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -224,6 +227,7 @@ public:
|
||||
auto local_subscription = inner_subscription;
|
||||
local_subscription->exchange_in_use_by_wait_set_state(local_subscription.get(), false);
|
||||
this->storage_remove_subscription(std::move(local_subscription));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
}
|
||||
if (mask.include_events) {
|
||||
for (auto key_event_pair : inner_subscription->get_event_handlers()) {
|
||||
@@ -231,6 +235,7 @@ public:
|
||||
auto local_subscription = inner_subscription;
|
||||
local_subscription->exchange_in_use_by_wait_set_state(event.get(), false);
|
||||
this->storage_remove_waitable(std::move(event));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
}
|
||||
}
|
||||
if (mask.include_intra_process_waitable) {
|
||||
@@ -239,6 +244,7 @@ public:
|
||||
// This is the case when intra process is enabled for the subscription.
|
||||
inner_subscription->exchange_in_use_by_wait_set_state(local_waitable.get(), false);
|
||||
this->storage_remove_waitable(std::move(local_waitable));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -289,6 +295,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the guard condition has already been added.
|
||||
this->storage_add_guard_condition(std::move(inner_guard_condition));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -326,6 +333,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the guard condition is not in the wait set.
|
||||
this->storage_remove_guard_condition(std::move(inner_guard_condition));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -357,6 +365,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the timer has already been added.
|
||||
this->storage_add_timer(std::move(inner_timer));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -384,6 +393,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the timer is not in the wait set.
|
||||
this->storage_remove_timer(std::move(inner_timer));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -415,6 +425,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the client has already been added.
|
||||
this->storage_add_client(std::move(inner_client));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -442,6 +453,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the client is not in the wait set.
|
||||
this->storage_remove_client(std::move(inner_client));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -473,6 +485,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the service has already been added.
|
||||
this->storage_add_service(std::move(inner_service));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -500,6 +513,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the service is not in the wait set.
|
||||
this->storage_remove_service(std::move(inner_service));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -551,6 +565,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the waitable has already been added.
|
||||
this->storage_add_waitable(std::move(inner_waitable), std::move(associated_entity));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -578,6 +593,7 @@ public:
|
||||
// fixed sized storage policies.
|
||||
// It will throw if the waitable is not in the wait set.
|
||||
this->storage_remove_waitable(std::move(inner_waitable));
|
||||
if (this->wait_result_holding_) {this->wait_result_dirty_ = true;}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -715,6 +731,7 @@ private:
|
||||
throw std::runtime_error("wait_result_acquire() called while already holding");
|
||||
}
|
||||
wait_result_holding_ = true;
|
||||
wait_result_dirty_ = false;
|
||||
// this method comes from the SynchronizationPolicy
|
||||
this->sync_wait_result_acquire();
|
||||
// this method comes from the StoragePolicy
|
||||
@@ -734,6 +751,7 @@ private:
|
||||
throw std::runtime_error("wait_result_release() called while not holding");
|
||||
}
|
||||
wait_result_holding_ = false;
|
||||
wait_result_dirty_ = false;
|
||||
// this method comes from the StoragePolicy
|
||||
this->storage_release_ownerships();
|
||||
// this method comes from the SynchronizationPolicy
|
||||
@@ -741,6 +759,7 @@ private:
|
||||
}
|
||||
|
||||
bool wait_result_holding_ = false;
|
||||
bool wait_result_dirty_ = false;
|
||||
};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -101,6 +101,23 @@ public:
|
||||
size_t
|
||||
get_number_of_ready_guard_conditions();
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
|
||||
#endif
|
||||
/// Deprecated.
|
||||
/**
|
||||
* \sa add_to_wait_set(rcl_wait_set_t &)
|
||||
*/
|
||||
[[deprecated("Use add_to_wait_set(rcl_wait_set_t & wait_set) signature")]]
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set);
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
/// Add the Waitable to a wait set.
|
||||
/**
|
||||
* \param[in] wait_set A handle to the wait set to add the Waitable to.
|
||||
@@ -109,7 +126,24 @@ public:
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set) = 0;
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set);
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
|
||||
#endif
|
||||
/// Deprecated.
|
||||
/**
|
||||
* \sa is_ready(const rcl_wait_set_t &)
|
||||
*/
|
||||
[[deprecated("Use is_ready(const rcl_wait_set_t & wait_set) signature")]]
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set);
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
/// Check if the Waitable is ready.
|
||||
/**
|
||||
@@ -124,7 +158,7 @@ public:
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) = 0;
|
||||
is_ready(const rcl_wait_set_t & wait_set);
|
||||
|
||||
/// Take the data so that it can be consumed with `execute`.
|
||||
/**
|
||||
@@ -178,6 +212,23 @@ public:
|
||||
std::shared_ptr<void>
|
||||
take_data_by_entity_id(size_t id);
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
|
||||
#endif
|
||||
/// Deprecated.
|
||||
/**
|
||||
* \sa execute(const std::shared_ptr<void> &)
|
||||
*/
|
||||
[[deprecated("Use execute(const std::shared_ptr<void> & data) signature")]]
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
void
|
||||
execute(std::shared_ptr<void> & data);
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
/// Execute data that is passed in.
|
||||
/**
|
||||
* Before calling this method, the Waitable should be added to a wait set,
|
||||
@@ -203,7 +254,7 @@ public:
|
||||
RCLCPP_PUBLIC
|
||||
virtual
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) = 0;
|
||||
execute(const std::shared_ptr<void> & data);
|
||||
|
||||
/// Exchange the "in use by wait set" state for this timer.
|
||||
/**
|
||||
|
||||
@@ -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>25.0.0</version>
|
||||
<version>28.3.1</version>
|
||||
<description>The ROS client library in C++.</description>
|
||||
|
||||
<maintainer email="ivanpauno@ekumenlabs.com">Ivan Paunovic</maintainer>
|
||||
|
||||
@@ -21,6 +21,7 @@ AnyExecutable::AnyExecutable()
|
||||
timer(nullptr),
|
||||
service(nullptr),
|
||||
client(nullptr),
|
||||
waitable(nullptr),
|
||||
callback_group(nullptr),
|
||||
node_base(nullptr)
|
||||
{}
|
||||
|
||||
@@ -31,12 +31,12 @@ using rclcpp::CallbackGroupType;
|
||||
|
||||
CallbackGroup::CallbackGroup(
|
||||
CallbackGroupType group_type,
|
||||
std::function<rclcpp::Context::SharedPtr(void)> get_context,
|
||||
rclcpp::Context::WeakPtr context,
|
||||
bool automatically_add_to_executor_with_node)
|
||||
: type_(group_type), associated_with_executor_(false),
|
||||
can_be_taken_from_(true),
|
||||
automatically_add_to_executor_with_node_(automatically_add_to_executor_with_node),
|
||||
get_context_(get_context)
|
||||
context_(context)
|
||||
{}
|
||||
|
||||
CallbackGroup::~CallbackGroup()
|
||||
@@ -66,6 +66,7 @@ CallbackGroup::size() const
|
||||
timer_ptrs_.size() +
|
||||
waitable_ptrs_.size();
|
||||
}
|
||||
|
||||
void CallbackGroup::collect_all_ptrs(
|
||||
std::function<void(const rclcpp::SubscriptionBase::SharedPtr &)> sub_func,
|
||||
std::function<void(const rclcpp::ServiceBase::SharedPtr &)> service_func,
|
||||
@@ -123,40 +124,12 @@ CallbackGroup::automatically_add_to_executor_with_node() const
|
||||
return automatically_add_to_executor_with_node_;
|
||||
}
|
||||
|
||||
// \TODO(mjcarroll) Deprecated, remove on tock
|
||||
rclcpp::GuardCondition::SharedPtr
|
||||
CallbackGroup::get_notify_guard_condition(const rclcpp::Context::SharedPtr context_ptr)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(notify_guard_condition_mutex_);
|
||||
if (notify_guard_condition_ && context_ptr != notify_guard_condition_->get_context()) {
|
||||
if (associated_with_executor_) {
|
||||
trigger_notify_guard_condition();
|
||||
}
|
||||
notify_guard_condition_ = nullptr;
|
||||
}
|
||||
|
||||
if (!notify_guard_condition_) {
|
||||
notify_guard_condition_ = std::make_shared<rclcpp::GuardCondition>(context_ptr);
|
||||
}
|
||||
|
||||
return notify_guard_condition_;
|
||||
}
|
||||
|
||||
rclcpp::GuardCondition::SharedPtr
|
||||
CallbackGroup::get_notify_guard_condition()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(notify_guard_condition_mutex_);
|
||||
if (!this->get_context_) {
|
||||
throw std::runtime_error("Callback group was created without context and not passed context");
|
||||
}
|
||||
auto context_ptr = this->get_context_();
|
||||
rclcpp::Context::SharedPtr context_ptr = context_.lock();
|
||||
if (context_ptr && context_ptr->is_valid()) {
|
||||
if (notify_guard_condition_ && context_ptr != notify_guard_condition_->get_context()) {
|
||||
if (associated_with_executor_) {
|
||||
trigger_notify_guard_condition();
|
||||
}
|
||||
notify_guard_condition_ = nullptr;
|
||||
}
|
||||
if (!notify_guard_condition_) {
|
||||
notify_guard_condition_ = std::make_shared<rclcpp::GuardCondition>(context_ptr);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
|
||||
rcl_clock_t rcl_clock_;
|
||||
rcl_allocator_t allocator_;
|
||||
bool stop_sleeping_ = false;
|
||||
bool shutdown_ = false;
|
||||
std::condition_variable cv_;
|
||||
std::mutex wait_mutex_;
|
||||
std::mutex clock_mutex_;
|
||||
};
|
||||
|
||||
@@ -79,8 +83,20 @@ Clock::now() const
|
||||
return now;
|
||||
}
|
||||
|
||||
void
|
||||
Clock::cancel_sleep_or_wait()
|
||||
{
|
||||
{
|
||||
std::unique_lock lock(impl_->wait_mutex_);
|
||||
impl_->stop_sleeping_ = true;
|
||||
}
|
||||
impl_->cv_.notify_one();
|
||||
}
|
||||
|
||||
bool
|
||||
Clock::sleep_until(Time until, Context::SharedPtr context)
|
||||
Clock::sleep_until(
|
||||
Time until,
|
||||
Context::SharedPtr context)
|
||||
{
|
||||
if (!context || !context->is_valid()) {
|
||||
throw std::runtime_error("context cannot be slept with because it's invalid");
|
||||
@@ -91,12 +107,14 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
|
||||
}
|
||||
bool time_source_changed = false;
|
||||
|
||||
std::condition_variable cv;
|
||||
|
||||
// Wake this thread if the context is shutdown
|
||||
rclcpp::OnShutdownCallbackHandle shutdown_cb_handle = context->add_on_shutdown_callback(
|
||||
[&cv]() {
|
||||
cv.notify_one();
|
||||
[this]() {
|
||||
{
|
||||
std::unique_lock lock(impl_->wait_mutex_);
|
||||
impl_->shutdown_ = true;
|
||||
}
|
||||
impl_->cv_.notify_one();
|
||||
});
|
||||
// No longer need the shutdown callback when this function exits
|
||||
auto callback_remover = rcpputils::scope_exit(
|
||||
@@ -112,22 +130,24 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
|
||||
const std::chrono::steady_clock::time_point chrono_until =
|
||||
chrono_entry + std::chrono::nanoseconds(delta_t.nanoseconds());
|
||||
|
||||
// loop over spurious wakeups but notice shutdown
|
||||
std::unique_lock lock(impl_->clock_mutex_);
|
||||
while (now() < until && context->is_valid()) {
|
||||
cv.wait_until(lock, chrono_until);
|
||||
// loop over spurious wakeups but notice shutdown or stop of sleep
|
||||
std::unique_lock lock(impl_->wait_mutex_);
|
||||
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid()) {
|
||||
impl_->cv_.wait_until(lock, chrono_until);
|
||||
}
|
||||
impl_->stop_sleeping_ = false;
|
||||
} else if (this_clock_type == RCL_SYSTEM_TIME) {
|
||||
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(until.nanoseconds())));
|
||||
|
||||
// loop over spurious wakeups but notice shutdown
|
||||
std::unique_lock lock(impl_->clock_mutex_);
|
||||
while (now() < until && context->is_valid()) {
|
||||
cv.wait_until(lock, system_time);
|
||||
// loop over spurious wakeups but notice shutdown or stop of sleep
|
||||
std::unique_lock lock(impl_->wait_mutex_);
|
||||
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid()) {
|
||||
impl_->cv_.wait_until(lock, system_time);
|
||||
}
|
||||
impl_->stop_sleeping_ = false;
|
||||
} else if (this_clock_type == RCL_ROS_TIME) {
|
||||
// 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
|
||||
@@ -139,11 +159,12 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
|
||||
threshold.min_forward.nanoseconds = 1;
|
||||
auto clock_handler = create_jump_callback(
|
||||
nullptr,
|
||||
[&cv, &time_source_changed](const rcl_time_jump_t & jump) {
|
||||
[this, &time_source_changed](const rcl_time_jump_t & jump) {
|
||||
if (jump.clock_change != RCL_ROS_TIME_NO_CHANGE) {
|
||||
std::lock_guard<std::mutex> lk(impl_->wait_mutex_);
|
||||
time_source_changed = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
impl_->cv_.notify_one();
|
||||
},
|
||||
threshold);
|
||||
|
||||
@@ -153,19 +174,25 @@ Clock::sleep_until(Time until, Context::SharedPtr context)
|
||||
std::chrono::duration_cast<std::chrono::system_clock::duration>(
|
||||
std::chrono::nanoseconds(until.nanoseconds())));
|
||||
|
||||
// loop over spurious wakeups but notice shutdown or time source change
|
||||
std::unique_lock lock(impl_->clock_mutex_);
|
||||
while (now() < until && context->is_valid() && !time_source_changed) {
|
||||
cv.wait_until(lock, system_time);
|
||||
// loop over spurious wakeups but notice shutdown, stop of sleep or time source change
|
||||
std::unique_lock lock(impl_->wait_mutex_);
|
||||
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid() &&
|
||||
!time_source_changed)
|
||||
{
|
||||
impl_->cv_.wait_until(lock, system_time);
|
||||
}
|
||||
impl_->stop_sleeping_ = false;
|
||||
} else {
|
||||
// RCL_ROS_TIME with ros_time_is_active.
|
||||
// Just wait without "until" because installed
|
||||
// jump callbacks wake the cv on every new sample.
|
||||
std::unique_lock lock(impl_->clock_mutex_);
|
||||
while (now() < until && context->is_valid() && !time_source_changed) {
|
||||
cv.wait(lock);
|
||||
std::unique_lock lock(impl_->wait_mutex_);
|
||||
while (now() < until && !impl_->stop_sleeping_ && !impl_->shutdown_ && context->is_valid() &&
|
||||
!time_source_changed)
|
||||
{
|
||||
impl_->cv_.wait(lock);
|
||||
}
|
||||
impl_->stop_sleeping_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
44
rclcpp/src/rclcpp/create_generic_client.cpp
Normal file
44
rclcpp/src/rclcpp/create_generic_client.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2023 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 "rclcpp/create_generic_client.hpp"
|
||||
#include "rclcpp/generic_client.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
rclcpp::GenericClient::SharedPtr
|
||||
create_generic_client(
|
||||
std::shared_ptr<node_interfaces::NodeBaseInterface> node_base,
|
||||
std::shared_ptr<node_interfaces::NodeGraphInterface> node_graph,
|
||||
std::shared_ptr<node_interfaces::NodeServicesInterface> node_services,
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
const rclcpp::QoS & qos,
|
||||
rclcpp::CallbackGroup::SharedPtr group)
|
||||
{
|
||||
rcl_client_options_t options = rcl_client_get_default_options();
|
||||
options.qos = qos.get_rmw_qos_profile();
|
||||
|
||||
auto cli = rclcpp::GenericClient::make_shared(
|
||||
node_base.get(),
|
||||
node_graph,
|
||||
service_name,
|
||||
service_type,
|
||||
options);
|
||||
|
||||
auto cli_base_ptr = std::dynamic_pointer_cast<rclcpp::ClientBase>(cli);
|
||||
node_services->add_client(cli_base_ptr, group);
|
||||
return cli;
|
||||
}
|
||||
} // namespace rclcpp
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
#include <rclcpp/detail/resolve_intra_process_buffer_type.hpp>
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
rclcpp::IntraProcessBufferType
|
||||
resolve_intra_process_buffer_type(
|
||||
const rclcpp::IntraProcessBufferType buffer_type)
|
||||
{
|
||||
if (buffer_type == IntraProcessBufferType::CallbackDefault) {
|
||||
throw std::invalid_argument(
|
||||
"IntraProcessBufferType::CallbackDefault is not allowed "
|
||||
"when there is no callback function");
|
||||
}
|
||||
|
||||
return buffer_type;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace rclcpp
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "rcutils/logging_macros.h"
|
||||
|
||||
#include "rclcpp/utilities.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
@@ -316,4 +318,50 @@ Duration::from_nanoseconds(rcl_duration_value_t nanoseconds)
|
||||
return ret;
|
||||
}
|
||||
|
||||
builtin_interfaces::msg::Time
|
||||
operator+(const builtin_interfaces::msg::Time & lhs, const rclcpp::Duration & rhs)
|
||||
{
|
||||
if (lhs.sec < 0) {
|
||||
throw std::runtime_error("message time is negative");
|
||||
}
|
||||
|
||||
rcl_time_point_value_t rcl_time;
|
||||
rcl_time = RCL_S_TO_NS(static_cast<int64_t>(lhs.sec));
|
||||
rcl_time += lhs.nanosec;
|
||||
|
||||
if (rclcpp::add_will_overflow(rcl_time, rhs.nanoseconds())) {
|
||||
throw std::overflow_error("addition leads to int64_t overflow");
|
||||
}
|
||||
if (rclcpp::add_will_underflow(rcl_time, rhs.nanoseconds())) {
|
||||
throw std::underflow_error("addition leads to int64_t underflow");
|
||||
}
|
||||
|
||||
rcl_time += rhs.nanoseconds();
|
||||
|
||||
return convert_rcl_time_to_sec_nanos(rcl_time);
|
||||
}
|
||||
|
||||
builtin_interfaces::msg::Time
|
||||
operator-(const builtin_interfaces::msg::Time & lhs, const rclcpp::Duration & rhs)
|
||||
{
|
||||
if (lhs.sec < 0) {
|
||||
throw std::runtime_error("message time is negative");
|
||||
}
|
||||
|
||||
rcl_time_point_value_t rcl_time;
|
||||
rcl_time = RCL_S_TO_NS(static_cast<int64_t>(lhs.sec));
|
||||
rcl_time += lhs.nanosec;
|
||||
|
||||
if (rclcpp::sub_will_overflow(rcl_time, rhs.nanoseconds())) {
|
||||
throw std::overflow_error("addition leads to int64_t overflow");
|
||||
}
|
||||
if (rclcpp::sub_will_underflow(rcl_time, rhs.nanoseconds())) {
|
||||
throw std::underflow_error("addition leads to int64_t underflow");
|
||||
}
|
||||
|
||||
rcl_time -= rhs.nanoseconds();
|
||||
|
||||
return convert_rcl_time_to_sec_nanos(rcl_time);
|
||||
}
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "rcl/error_handling.h"
|
||||
#include "rcl/event.h"
|
||||
|
||||
#include "rclcpp/event_handler.hpp"
|
||||
@@ -56,9 +57,9 @@ EventHandlerBase::get_number_of_ready_events()
|
||||
|
||||
/// Add the Waitable to a wait set.
|
||||
void
|
||||
EventHandlerBase::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
EventHandlerBase::add_to_wait_set(rcl_wait_set_t & wait_set)
|
||||
{
|
||||
rcl_ret_t ret = rcl_wait_set_add_event(wait_set, &event_handle_, &wait_set_event_index_);
|
||||
rcl_ret_t ret = rcl_wait_set_add_event(&wait_set, &event_handle_, &wait_set_event_index_);
|
||||
if (RCL_RET_OK != ret) {
|
||||
exceptions::throw_from_rcl_error(ret, "Couldn't add event to wait set");
|
||||
}
|
||||
@@ -66,9 +67,9 @@ EventHandlerBase::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
|
||||
/// Check if the Waitable is ready.
|
||||
bool
|
||||
EventHandlerBase::is_ready(rcl_wait_set_t * wait_set)
|
||||
EventHandlerBase::is_ready(const rcl_wait_set_t & wait_set)
|
||||
{
|
||||
return wait_set->events[wait_set_event_index_] == &event_handle_;
|
||||
return wait_set.events[wait_set_event_index_] == &event_handle_;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
55
rclcpp/src/rclcpp/executor_options.cpp
Normal file
55
rclcpp/src/rclcpp/executor_options.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#include "rclcpp/executor_options.hpp"
|
||||
|
||||
using rclcpp::ExecutorOptions;
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
|
||||
class ExecutorOptionsImplementation {};
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
ExecutorOptions::ExecutorOptions()
|
||||
: memory_strategy(rclcpp::memory_strategies::create_default_strategy()),
|
||||
context(rclcpp::contexts::get_global_default_context()),
|
||||
max_conditions(0),
|
||||
impl_(nullptr)
|
||||
{}
|
||||
|
||||
ExecutorOptions::~ExecutorOptions()
|
||||
{}
|
||||
|
||||
ExecutorOptions::ExecutorOptions(const ExecutorOptions & other)
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
||||
ExecutorOptions & ExecutorOptions::operator=(const ExecutorOptions & other)
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
this->memory_strategy = other.memory_strategy;
|
||||
this->context = other.context;
|
||||
this->max_conditions = other.max_conditions;
|
||||
if (nullptr != other.impl_) {
|
||||
this->impl_ = std::make_unique<ExecutorOptionsImplementation>(*other.impl_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -19,7 +19,9 @@ rclcpp::spin_all(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
std::chrono::nanoseconds max_duration)
|
||||
{
|
||||
rclcpp::executors::SingleThreadedExecutor exec;
|
||||
rclcpp::ExecutorOptions options;
|
||||
options.context = node_ptr->get_context();
|
||||
rclcpp::executors::SingleThreadedExecutor exec(options);
|
||||
exec.spin_node_all(node_ptr, max_duration);
|
||||
}
|
||||
|
||||
@@ -32,7 +34,9 @@ rclcpp::spin_all(rclcpp::Node::SharedPtr node_ptr, std::chrono::nanoseconds max_
|
||||
void
|
||||
rclcpp::spin_some(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr)
|
||||
{
|
||||
rclcpp::executors::SingleThreadedExecutor exec;
|
||||
rclcpp::ExecutorOptions options;
|
||||
options.context = node_ptr->get_context();
|
||||
rclcpp::executors::SingleThreadedExecutor exec(options);
|
||||
exec.spin_node_some(node_ptr);
|
||||
}
|
||||
|
||||
@@ -45,7 +49,9 @@ rclcpp::spin_some(rclcpp::Node::SharedPtr node_ptr)
|
||||
void
|
||||
rclcpp::spin(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr)
|
||||
{
|
||||
rclcpp::executors::SingleThreadedExecutor exec;
|
||||
rclcpp::ExecutorOptions options;
|
||||
options.context = node_ptr->get_context();
|
||||
rclcpp::executors::SingleThreadedExecutor exec(options);
|
||||
exec.add_node(node_ptr);
|
||||
exec.spin();
|
||||
exec.remove_node(node_ptr);
|
||||
|
||||
@@ -39,6 +39,30 @@ void ExecutorEntitiesCollection::clear()
|
||||
waitables.clear();
|
||||
}
|
||||
|
||||
size_t ExecutorEntitiesCollection::remove_expired_entities()
|
||||
{
|
||||
auto remove_entities = [](auto & collection) -> size_t {
|
||||
size_t removed = 0;
|
||||
for (auto it = collection.begin(); it != collection.end(); ) {
|
||||
if (it->second.entity.expired()) {
|
||||
++removed;
|
||||
it = collection.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
};
|
||||
|
||||
return
|
||||
remove_entities(subscriptions) +
|
||||
remove_entities(timers) +
|
||||
remove_entities(guard_conditions) +
|
||||
remove_entities(clients) +
|
||||
remove_entities(services) +
|
||||
remove_entities(waitables);
|
||||
}
|
||||
|
||||
void
|
||||
build_entities_collection(
|
||||
const std::vector<rclcpp::CallbackGroup::WeakPtr> & callback_groups,
|
||||
@@ -129,7 +153,7 @@ ready_executables(
|
||||
continue;
|
||||
}
|
||||
auto group_info = group_cache(entity_iter->second.callback_group);
|
||||
if (group_info && !group_info->can_be_taken_from().load()) {
|
||||
if (!group_info || !group_info->can_be_taken_from().load()) {
|
||||
continue;
|
||||
}
|
||||
if (!entity->call()) {
|
||||
@@ -152,7 +176,7 @@ ready_executables(
|
||||
continue;
|
||||
}
|
||||
auto group_info = group_cache(entity_iter->second.callback_group);
|
||||
if (group_info && !group_info->can_be_taken_from().load()) {
|
||||
if (!group_info || !group_info->can_be_taken_from().load()) {
|
||||
continue;
|
||||
}
|
||||
rclcpp::AnyExecutable exec;
|
||||
@@ -172,7 +196,7 @@ ready_executables(
|
||||
continue;
|
||||
}
|
||||
auto group_info = group_cache(entity_iter->second.callback_group);
|
||||
if (group_info && !group_info->can_be_taken_from().load()) {
|
||||
if (!group_info || !group_info->can_be_taken_from().load()) {
|
||||
continue;
|
||||
}
|
||||
rclcpp::AnyExecutable exec;
|
||||
@@ -192,7 +216,7 @@ ready_executables(
|
||||
continue;
|
||||
}
|
||||
auto group_info = group_cache(entity_iter->second.callback_group);
|
||||
if (group_info && !group_info->can_be_taken_from().load()) {
|
||||
if (!group_info || !group_info->can_be_taken_from().load()) {
|
||||
continue;
|
||||
}
|
||||
rclcpp::AnyExecutable exec;
|
||||
@@ -203,28 +227,25 @@ ready_executables(
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & [handle, entry] : collection.waitables) {
|
||||
for (const auto & [handle, entry] : collection.waitables) {
|
||||
auto waitable = entry.entity.lock();
|
||||
if (!waitable) {
|
||||
continue;
|
||||
}
|
||||
if (!waitable->is_ready(&rcl_wait_set)) {
|
||||
if (!waitable->is_ready(rcl_wait_set)) {
|
||||
continue;
|
||||
}
|
||||
auto group_info = group_cache(entry.callback_group);
|
||||
if (group_info && !group_info->can_be_taken_from().load()) {
|
||||
if (!group_info || !group_info->can_be_taken_from().load()) {
|
||||
continue;
|
||||
}
|
||||
rclcpp::AnyExecutable exec;
|
||||
exec.waitable = waitable;
|
||||
exec.callback_group = group_info;
|
||||
exec.data = waitable->take_data();
|
||||
executables.push_back(exec);
|
||||
added++;
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
} // namespace executors
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -61,6 +61,12 @@ ExecutorEntitiesCollector::~ExecutorEntitiesCollector()
|
||||
if (group_ptr) {
|
||||
group_ptr->get_associated_with_executor_atomic().store(false);
|
||||
}
|
||||
// Disassociate the guard condition from the executor notify waitable
|
||||
auto guard_condition_it = weak_groups_to_guard_conditions_.find(weak_group_ptr);
|
||||
if (guard_condition_it != weak_groups_to_guard_conditions_.end()) {
|
||||
this->notify_waitable_->remove_guard_condition(guard_condition_it->second);
|
||||
weak_groups_to_guard_conditions_.erase(guard_condition_it);
|
||||
}
|
||||
}
|
||||
pending_manually_added_groups_.clear();
|
||||
pending_manually_removed_groups_.clear();
|
||||
@@ -105,7 +111,8 @@ void
|
||||
ExecutorEntitiesCollector::remove_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr)
|
||||
{
|
||||
if (!node_ptr->get_associated_with_executor_atomic().load()) {
|
||||
std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
|
||||
if (!has_executor.exchange(false)) {
|
||||
throw std::runtime_error(
|
||||
std::string("Node '") + node_ptr->get_fully_qualified_name() +
|
||||
"' needs to be associated with an executor.");
|
||||
@@ -143,6 +150,11 @@ ExecutorEntitiesCollector::add_callback_group(rclcpp::CallbackGroup::SharedPtr g
|
||||
}
|
||||
|
||||
this->pending_manually_added_groups_.insert(group_ptr);
|
||||
|
||||
// Store callback group notify guard condition in map and add it to the notify waitable
|
||||
auto group_guard_condition = group_ptr->get_notify_guard_condition();
|
||||
weak_groups_to_guard_conditions_.insert({group_ptr, group_guard_condition});
|
||||
this->notify_waitable_->add_guard_condition(group_guard_condition);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -161,7 +173,6 @@ ExecutorEntitiesCollector::remove_callback_group(rclcpp::CallbackGroup::SharedPt
|
||||
throw std::runtime_error("Node must not be deleted before its callback group(s).");
|
||||
}
|
||||
*/
|
||||
|
||||
auto weak_group_ptr = rclcpp::CallbackGroup::WeakPtr(group_ptr);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
bool associated = manually_added_groups_.count(group_ptr) != 0;
|
||||
@@ -314,7 +325,11 @@ ExecutorEntitiesCollector::process_queues()
|
||||
if (node_it != weak_nodes_.end()) {
|
||||
remove_weak_node(node_it);
|
||||
} else {
|
||||
throw std::runtime_error("Node needs to be associated with this executor.");
|
||||
// The node may have been destroyed and removed from the colletion before
|
||||
// we processed the queues. Don't throw if the pointer is already expired.
|
||||
if (!weak_node_ptr.expired()) {
|
||||
throw std::runtime_error("Node needs to be associated with this executor.");
|
||||
}
|
||||
}
|
||||
|
||||
auto node_ptr = weak_node_ptr.lock();
|
||||
@@ -337,6 +352,13 @@ ExecutorEntitiesCollector::process_queues()
|
||||
auto group_ptr = weak_group_ptr.lock();
|
||||
if (group_ptr) {
|
||||
this->add_callback_group_to_collection(group_ptr, manually_added_groups_);
|
||||
} else {
|
||||
// Disassociate the guard condition from the executor notify waitable
|
||||
auto guard_condition_it = weak_groups_to_guard_conditions_.find(weak_group_ptr);
|
||||
if (guard_condition_it != weak_groups_to_guard_conditions_.end()) {
|
||||
this->notify_waitable_->remove_guard_condition(guard_condition_it->second);
|
||||
weak_groups_to_guard_conditions_.erase(guard_condition_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
pending_manually_added_groups_.clear();
|
||||
|
||||
@@ -27,15 +27,17 @@ ExecutorNotifyWaitable::ExecutorNotifyWaitable(std::function<void(void)> on_exec
|
||||
{
|
||||
}
|
||||
|
||||
ExecutorNotifyWaitable::ExecutorNotifyWaitable(const ExecutorNotifyWaitable & other)
|
||||
: ExecutorNotifyWaitable(other.execute_callback_)
|
||||
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_;
|
||||
}
|
||||
|
||||
ExecutorNotifyWaitable & ExecutorNotifyWaitable::operator=(const ExecutorNotifyWaitable & other)
|
||||
ExecutorNotifyWaitable & ExecutorNotifyWaitable::operator=(ExecutorNotifyWaitable & other)
|
||||
{
|
||||
if (this != &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_;
|
||||
}
|
||||
@@ -43,43 +45,45 @@ ExecutorNotifyWaitable & ExecutorNotifyWaitable::operator=(const ExecutorNotifyW
|
||||
}
|
||||
|
||||
void
|
||||
ExecutorNotifyWaitable::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
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) {
|
||||
auto rcl_guard_condition = &guard_condition->get_rcl_guard_condition();
|
||||
if (!guard_condition) {continue;}
|
||||
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(
|
||||
wait_set,
|
||||
rcl_guard_condition, NULL);
|
||||
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 (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(
|
||||
ret, "failed to add guard condition to wait set");
|
||||
}
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(
|
||||
ret, "failed to add guard condition to wait set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ExecutorNotifyWaitable::is_ready(rcl_wait_set_t * wait_set)
|
||||
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) {
|
||||
auto rcl_guard_condition = wait_set->guard_conditions[ii];
|
||||
for (size_t ii = 0; ii < wait_set.size_of_guard_conditions; ++ii) {
|
||||
const auto * rcl_guard_condition = wait_set.guard_conditions[ii];
|
||||
|
||||
if (nullptr == rcl_guard_condition) {
|
||||
continue;
|
||||
}
|
||||
for (auto weak_guard_condition : this->notify_guard_conditions_) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +91,7 @@ ExecutorNotifyWaitable::is_ready(rcl_wait_set_t * wait_set)
|
||||
}
|
||||
|
||||
void
|
||||
ExecutorNotifyWaitable::execute(std::shared_ptr<void> & data)
|
||||
ExecutorNotifyWaitable::execute(const std::shared_ptr<void> & data)
|
||||
{
|
||||
(void) data;
|
||||
this->execute_callback_();
|
||||
|
||||
@@ -55,7 +55,7 @@ MultiThreadedExecutor::spin()
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false););
|
||||
std::vector<std::thread> threads;
|
||||
size_t thread_id = 0;
|
||||
{
|
||||
@@ -99,6 +99,18 @@ MultiThreadedExecutor::run(size_t this_thread_number)
|
||||
|
||||
execute_any_executable(any_exec);
|
||||
|
||||
if (any_exec.callback_group &&
|
||||
any_exec.callback_group->type() == CallbackGroupType::MutuallyExclusive)
|
||||
{
|
||||
try {
|
||||
interrupt_guard_condition_->trigger();
|
||||
} catch (const rclcpp::exceptions::RCLError & ex) {
|
||||
throw std::runtime_error(
|
||||
std::string(
|
||||
"Failed to trigger guard condition on callback group change: ") + ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the callback_group to prevent the AnyExecutable destructor from
|
||||
// resetting the callback group `can_be_taken_from`
|
||||
any_exec.callback_group.reset();
|
||||
|
||||
@@ -31,6 +31,11 @@ SingleThreadedExecutor::spin()
|
||||
throw std::runtime_error("spin() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
|
||||
// Clear any previous result and rebuild the waitset
|
||||
this->wait_result_.reset();
|
||||
this->entities_need_rebuild_ = true;
|
||||
|
||||
while (rclcpp::ok(this->context_) && spinning.load()) {
|
||||
rclcpp::AnyExecutable any_executable;
|
||||
if (get_next_executable(any_executable)) {
|
||||
|
||||
@@ -1,524 +0,0 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
#include "rclcpp/executors/static_executor_entities_collector.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/memory_strategy.hpp"
|
||||
#include "rclcpp/executors/static_single_threaded_executor.hpp"
|
||||
#include "rclcpp/detail/add_guard_condition_to_rcl_wait_set.hpp"
|
||||
|
||||
using rclcpp::executors::StaticExecutorEntitiesCollector;
|
||||
|
||||
StaticExecutorEntitiesCollector::~StaticExecutorEntitiesCollector()
|
||||
{
|
||||
// Disassociate all callback groups and thus nodes.
|
||||
for (const auto & pair : weak_groups_associated_with_executor_to_nodes_) {
|
||||
auto group = pair.first.lock();
|
||||
if (group) {
|
||||
std::atomic_bool & has_executor = group->get_associated_with_executor_atomic();
|
||||
has_executor.store(false);
|
||||
}
|
||||
}
|
||||
for (const auto & pair : weak_groups_to_nodes_associated_with_executor_) {
|
||||
auto group = pair.first.lock();
|
||||
if (group) {
|
||||
std::atomic_bool & has_executor = group->get_associated_with_executor_atomic();
|
||||
has_executor.store(false);
|
||||
}
|
||||
}
|
||||
// Disassociate all nodes
|
||||
for (const auto & weak_node : weak_nodes_) {
|
||||
auto node = weak_node.lock();
|
||||
if (node) {
|
||||
std::atomic_bool & has_executor = node->get_associated_with_executor_atomic();
|
||||
has_executor.store(false);
|
||||
}
|
||||
}
|
||||
weak_groups_associated_with_executor_to_nodes_.clear();
|
||||
weak_groups_to_nodes_associated_with_executor_.clear();
|
||||
exec_list_.clear();
|
||||
weak_nodes_.clear();
|
||||
weak_nodes_to_guard_conditions_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::init(
|
||||
rcl_wait_set_t * p_wait_set,
|
||||
rclcpp::memory_strategy::MemoryStrategy::SharedPtr memory_strategy)
|
||||
{
|
||||
// Empty initialize executable list
|
||||
exec_list_ = rclcpp::experimental::ExecutableList();
|
||||
// Get executor's wait_set_ pointer
|
||||
p_wait_set_ = p_wait_set;
|
||||
// Get executor's memory strategy ptr
|
||||
if (memory_strategy == nullptr) {
|
||||
throw std::runtime_error("Received NULL memory strategy in executor waitable.");
|
||||
}
|
||||
memory_strategy_ = memory_strategy;
|
||||
|
||||
// Get memory strategy and executable list. Prepare wait_set_
|
||||
std::shared_ptr<void> shared_ptr;
|
||||
execute(shared_ptr);
|
||||
|
||||
// The entities collector is now initialized
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::fini()
|
||||
{
|
||||
memory_strategy_->clear_handles();
|
||||
exec_list_.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
StaticExecutorEntitiesCollector::take_data()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::execute(std::shared_ptr<void> & data)
|
||||
{
|
||||
(void) data;
|
||||
// Fill memory strategy with entities coming from weak_nodes_
|
||||
fill_memory_strategy();
|
||||
// Fill exec_list_ with entities coming from weak_nodes_ (same as memory strategy)
|
||||
fill_executable_list();
|
||||
// Resize the wait_set_ based on memory_strategy handles (rcl_wait_set_resize)
|
||||
prepare_wait_set();
|
||||
// Add new nodes guard conditions to map
|
||||
std::lock_guard<std::mutex> guard{new_nodes_mutex_};
|
||||
for (const auto & weak_node : new_nodes_) {
|
||||
if (auto node_ptr = weak_node.lock()) {
|
||||
weak_nodes_to_guard_conditions_[node_ptr] =
|
||||
node_ptr->get_shared_notify_guard_condition().get();
|
||||
}
|
||||
}
|
||||
new_nodes_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::fill_memory_strategy()
|
||||
{
|
||||
memory_strategy_->clear_handles();
|
||||
bool has_invalid_weak_groups_or_nodes =
|
||||
memory_strategy_->collect_entities(weak_groups_to_nodes_associated_with_executor_);
|
||||
// Clean up any invalid nodes, if they were detected
|
||||
if (has_invalid_weak_groups_or_nodes) {
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr> invalid_group_ptrs;
|
||||
for (const auto & pair : weak_groups_to_nodes_associated_with_executor_) {
|
||||
auto & weak_group_ptr = pair.first;
|
||||
auto & weak_node_ptr = pair.second;
|
||||
if (weak_group_ptr.expired() || weak_node_ptr.expired()) {
|
||||
invalid_group_ptrs.push_back(weak_group_ptr);
|
||||
}
|
||||
}
|
||||
std::for_each(
|
||||
invalid_group_ptrs.begin(), invalid_group_ptrs.end(),
|
||||
[this](rclcpp::CallbackGroup::WeakPtr group_ptr) {
|
||||
weak_groups_to_nodes_associated_with_executor_.erase(group_ptr);
|
||||
});
|
||||
}
|
||||
has_invalid_weak_groups_or_nodes =
|
||||
memory_strategy_->collect_entities(weak_groups_associated_with_executor_to_nodes_);
|
||||
// Clean up any invalid nodes, if they were detected
|
||||
if (has_invalid_weak_groups_or_nodes) {
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr> invalid_group_ptrs;
|
||||
for (const auto & pair : weak_groups_associated_with_executor_to_nodes_) {
|
||||
auto & weak_group_ptr = pair.first;
|
||||
const auto & weak_node_ptr = pair.second;
|
||||
if (weak_group_ptr.expired() || weak_node_ptr.expired()) {
|
||||
invalid_group_ptrs.push_back(weak_group_ptr);
|
||||
}
|
||||
}
|
||||
std::for_each(
|
||||
invalid_group_ptrs.begin(), invalid_group_ptrs.end(),
|
||||
[this](rclcpp::CallbackGroup::WeakPtr group_ptr) {
|
||||
weak_groups_associated_with_executor_to_nodes_.erase(group_ptr);
|
||||
});
|
||||
}
|
||||
|
||||
// Add the static executor waitable to the memory strategy
|
||||
memory_strategy_->add_waitable_handle(this->shared_from_this());
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::fill_executable_list()
|
||||
{
|
||||
exec_list_.clear();
|
||||
add_callback_groups_from_nodes_associated_to_executor();
|
||||
fill_executable_list_from_map(weak_groups_associated_with_executor_to_nodes_);
|
||||
fill_executable_list_from_map(weak_groups_to_nodes_associated_with_executor_);
|
||||
// Add the executor's waitable to the executable list
|
||||
exec_list_.add_waitable(shared_from_this());
|
||||
}
|
||||
void
|
||||
StaticExecutorEntitiesCollector::fill_executable_list_from_map(
|
||||
const rclcpp::memory_strategy::MemoryStrategy::WeakCallbackGroupsToNodesMap &
|
||||
weak_groups_to_nodes)
|
||||
{
|
||||
for (const auto & pair : weak_groups_to_nodes) {
|
||||
auto group = pair.first.lock();
|
||||
auto node = pair.second.lock();
|
||||
if (!node || !group || !group->can_be_taken_from().load()) {
|
||||
continue;
|
||||
}
|
||||
group->find_timer_ptrs_if(
|
||||
[this](const rclcpp::TimerBase::SharedPtr & timer) {
|
||||
if (timer) {
|
||||
exec_list_.add_timer(timer);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
group->find_subscription_ptrs_if(
|
||||
[this](const rclcpp::SubscriptionBase::SharedPtr & subscription) {
|
||||
if (subscription) {
|
||||
exec_list_.add_subscription(subscription);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
group->find_service_ptrs_if(
|
||||
[this](const rclcpp::ServiceBase::SharedPtr & service) {
|
||||
if (service) {
|
||||
exec_list_.add_service(service);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
group->find_client_ptrs_if(
|
||||
[this](const rclcpp::ClientBase::SharedPtr & client) {
|
||||
if (client) {
|
||||
exec_list_.add_client(client);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
group->find_waitable_ptrs_if(
|
||||
[this](const rclcpp::Waitable::SharedPtr & waitable) {
|
||||
if (waitable) {
|
||||
exec_list_.add_waitable(waitable);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::prepare_wait_set()
|
||||
{
|
||||
// clear wait set
|
||||
if (rcl_wait_set_clear(p_wait_set_) != RCL_RET_OK) {
|
||||
throw std::runtime_error("Couldn't clear wait set");
|
||||
}
|
||||
|
||||
// The size of waitables are accounted for in size of the other entities
|
||||
rcl_ret_t ret = rcl_wait_set_resize(
|
||||
p_wait_set_, memory_strategy_->number_of_ready_subscriptions(),
|
||||
memory_strategy_->number_of_guard_conditions(), memory_strategy_->number_of_ready_timers(),
|
||||
memory_strategy_->number_of_ready_clients(), memory_strategy_->number_of_ready_services(),
|
||||
memory_strategy_->number_of_ready_events());
|
||||
|
||||
if (RCL_RET_OK != ret) {
|
||||
throw std::runtime_error(
|
||||
std::string("Couldn't resize the wait set: ") + rcl_get_error_string().str);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::refresh_wait_set(std::chrono::nanoseconds timeout)
|
||||
{
|
||||
// clear wait set (memset to '0' all wait_set_ entities
|
||||
// but keeps the wait_set_ number of entities)
|
||||
if (rcl_wait_set_clear(p_wait_set_) != RCL_RET_OK) {
|
||||
throw std::runtime_error("Couldn't clear wait set");
|
||||
}
|
||||
|
||||
if (!memory_strategy_->add_handles_to_wait_set(p_wait_set_)) {
|
||||
throw std::runtime_error("Couldn't fill wait set");
|
||||
}
|
||||
|
||||
rcl_ret_t status =
|
||||
rcl_wait(p_wait_set_, std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count());
|
||||
|
||||
if (status == RCL_RET_WAIT_SET_EMPTY) {
|
||||
RCUTILS_LOG_WARN_NAMED(
|
||||
"rclcpp",
|
||||
"empty wait set received in rcl_wait(). This should never happen.");
|
||||
} else if (status != RCL_RET_OK && status != RCL_RET_TIMEOUT) {
|
||||
using rclcpp::exceptions::throw_from_rcl_error;
|
||||
throw_from_rcl_error(status, "rcl_wait() failed");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
{
|
||||
// Add waitable guard conditions (one for each registered node) into the wait set.
|
||||
for (const auto & pair : weak_nodes_to_guard_conditions_) {
|
||||
auto & gc = pair.second;
|
||||
detail::add_guard_condition_to_rcl_wait_set(*wait_set, *gc);
|
||||
}
|
||||
}
|
||||
|
||||
size_t StaticExecutorEntitiesCollector::get_number_of_ready_guard_conditions()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard{new_nodes_mutex_};
|
||||
return weak_nodes_to_guard_conditions_.size() + new_nodes_.size();
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::add_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr)
|
||||
{
|
||||
bool is_new_node = false;
|
||||
// If the node already has an executor
|
||||
std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
|
||||
if (has_executor.exchange(true)) {
|
||||
throw std::runtime_error("Node has already been added to an executor.");
|
||||
}
|
||||
node_ptr->for_each_callback_group(
|
||||
[this, node_ptr, &is_new_node](rclcpp::CallbackGroup::SharedPtr group_ptr)
|
||||
{
|
||||
if (
|
||||
!group_ptr->get_associated_with_executor_atomic().load() &&
|
||||
group_ptr->automatically_add_to_executor_with_node())
|
||||
{
|
||||
is_new_node = (add_callback_group(
|
||||
group_ptr,
|
||||
node_ptr,
|
||||
weak_groups_to_nodes_associated_with_executor_) ||
|
||||
is_new_node);
|
||||
}
|
||||
});
|
||||
weak_nodes_.push_back(node_ptr);
|
||||
return is_new_node;
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
rclcpp::memory_strategy::MemoryStrategy::WeakCallbackGroupsToNodesMap & weak_groups_to_nodes)
|
||||
{
|
||||
// If the callback_group already has an executor
|
||||
std::atomic_bool & has_executor = group_ptr->get_associated_with_executor_atomic();
|
||||
if (has_executor.exchange(true)) {
|
||||
throw std::runtime_error("Callback group has already been added to an executor.");
|
||||
}
|
||||
bool is_new_node = !has_node(node_ptr, weak_groups_associated_with_executor_to_nodes_) &&
|
||||
!has_node(node_ptr, weak_groups_to_nodes_associated_with_executor_);
|
||||
rclcpp::CallbackGroup::WeakPtr weak_group_ptr = group_ptr;
|
||||
auto insert_info = weak_groups_to_nodes.insert(
|
||||
std::make_pair(weak_group_ptr, node_ptr));
|
||||
bool was_inserted = insert_info.second;
|
||||
if (!was_inserted) {
|
||||
throw std::runtime_error("Callback group was already added to executor.");
|
||||
}
|
||||
if (is_new_node) {
|
||||
std::lock_guard<std::mutex> guard{new_nodes_mutex_};
|
||||
new_nodes_.push_back(node_ptr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr)
|
||||
{
|
||||
return add_callback_group(group_ptr, node_ptr, weak_groups_associated_with_executor_to_nodes_);
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::remove_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr)
|
||||
{
|
||||
return this->remove_callback_group_from_map(
|
||||
group_ptr,
|
||||
weak_groups_associated_with_executor_to_nodes_);
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::remove_callback_group_from_map(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::memory_strategy::MemoryStrategy::WeakCallbackGroupsToNodesMap & weak_groups_to_nodes)
|
||||
{
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr;
|
||||
rclcpp::CallbackGroup::WeakPtr weak_group_ptr = group_ptr;
|
||||
auto iter = weak_groups_to_nodes.find(weak_group_ptr);
|
||||
if (iter != weak_groups_to_nodes.end()) {
|
||||
node_ptr = iter->second.lock();
|
||||
if (node_ptr == nullptr) {
|
||||
throw std::runtime_error("Node must not be deleted before its callback group(s).");
|
||||
}
|
||||
weak_groups_to_nodes.erase(iter);
|
||||
} else {
|
||||
throw std::runtime_error("Callback group needs to be associated with executor.");
|
||||
}
|
||||
// If the node was matched and removed, interrupt waiting.
|
||||
if (!has_node(node_ptr, weak_groups_associated_with_executor_to_nodes_) &&
|
||||
!has_node(node_ptr, weak_groups_to_nodes_associated_with_executor_))
|
||||
{
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr node_weak_ptr(node_ptr);
|
||||
weak_nodes_to_guard_conditions_.erase(node_weak_ptr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::remove_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr)
|
||||
{
|
||||
if (!node_ptr->get_associated_with_executor_atomic().load()) {
|
||||
return false;
|
||||
}
|
||||
bool node_found = false;
|
||||
auto node_it = weak_nodes_.begin();
|
||||
while (node_it != weak_nodes_.end()) {
|
||||
bool matched = (node_it->lock() == node_ptr);
|
||||
if (matched) {
|
||||
weak_nodes_.erase(node_it);
|
||||
node_found = true;
|
||||
break;
|
||||
}
|
||||
++node_it;
|
||||
}
|
||||
if (!node_found) {
|
||||
return false;
|
||||
}
|
||||
std::vector<rclcpp::CallbackGroup::SharedPtr> found_group_ptrs;
|
||||
std::for_each(
|
||||
weak_groups_to_nodes_associated_with_executor_.begin(),
|
||||
weak_groups_to_nodes_associated_with_executor_.end(),
|
||||
[&found_group_ptrs, node_ptr](std::pair<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr> key_value_pair) {
|
||||
auto & weak_node_ptr = key_value_pair.second;
|
||||
auto shared_node_ptr = weak_node_ptr.lock();
|
||||
auto group_ptr = key_value_pair.first.lock();
|
||||
if (shared_node_ptr == node_ptr) {
|
||||
found_group_ptrs.push_back(group_ptr);
|
||||
}
|
||||
});
|
||||
std::for_each(
|
||||
found_group_ptrs.begin(), found_group_ptrs.end(), [this]
|
||||
(rclcpp::CallbackGroup::SharedPtr group_ptr) {
|
||||
this->remove_callback_group_from_map(
|
||||
group_ptr,
|
||||
weak_groups_to_nodes_associated_with_executor_);
|
||||
});
|
||||
std::atomic_bool & has_executor = node_ptr->get_associated_with_executor_atomic();
|
||||
has_executor.store(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::is_ready(rcl_wait_set_t * p_wait_set)
|
||||
{
|
||||
// Check wait_set guard_conditions for added/removed entities to/from a node
|
||||
for (size_t i = 0; i < p_wait_set->size_of_guard_conditions; ++i) {
|
||||
if (p_wait_set->guard_conditions[i] != NULL) {
|
||||
auto found_guard_condition = std::find_if(
|
||||
weak_nodes_to_guard_conditions_.begin(), weak_nodes_to_guard_conditions_.end(),
|
||||
[&](std::pair<rclcpp::node_interfaces::NodeBaseInterface::WeakPtr,
|
||||
const GuardCondition *> pair) -> bool {
|
||||
const rcl_guard_condition_t & rcl_gc = pair.second->get_rcl_guard_condition();
|
||||
return &rcl_gc == p_wait_set->guard_conditions[i];
|
||||
});
|
||||
if (found_guard_condition != weak_nodes_to_guard_conditions_.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// None of the guard conditions triggered belong to a registered node
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true iff the weak_groups_to_nodes map has node_ptr as the value in any of its entry.
|
||||
bool
|
||||
StaticExecutorEntitiesCollector::has_node(
|
||||
const rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
const rclcpp::memory_strategy::MemoryStrategy::WeakCallbackGroupsToNodesMap &
|
||||
weak_groups_to_nodes) const
|
||||
{
|
||||
return std::find_if(
|
||||
weak_groups_to_nodes.begin(),
|
||||
weak_groups_to_nodes.end(),
|
||||
[&](const WeakCallbackGroupsToNodesMap::value_type & other) -> bool {
|
||||
auto other_ptr = other.second.lock();
|
||||
return other_ptr == node_ptr;
|
||||
}) != weak_groups_to_nodes.end();
|
||||
}
|
||||
|
||||
void
|
||||
StaticExecutorEntitiesCollector::add_callback_groups_from_nodes_associated_to_executor()
|
||||
{
|
||||
for (const auto & weak_node : weak_nodes_) {
|
||||
auto node = weak_node.lock();
|
||||
if (node) {
|
||||
node->for_each_callback_group(
|
||||
[this, node](rclcpp::CallbackGroup::SharedPtr shared_group_ptr)
|
||||
{
|
||||
if (shared_group_ptr->automatically_add_to_executor_with_node() &&
|
||||
!shared_group_ptr->get_associated_with_executor_atomic().load())
|
||||
{
|
||||
add_callback_group(
|
||||
shared_group_ptr,
|
||||
node,
|
||||
weak_groups_to_nodes_associated_with_executor_);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
StaticExecutorEntitiesCollector::get_all_callback_groups()
|
||||
{
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr> groups;
|
||||
for (const auto & group_node_ptr : weak_groups_associated_with_executor_to_nodes_) {
|
||||
groups.push_back(group_node_ptr.first);
|
||||
}
|
||||
for (const auto & group_node_ptr : weak_groups_to_nodes_associated_with_executor_) {
|
||||
groups.push_back(group_node_ptr.first);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
StaticExecutorEntitiesCollector::get_manually_added_callback_groups()
|
||||
{
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr> groups;
|
||||
for (const auto & group_node_ptr : weak_groups_associated_with_executor_to_nodes_) {
|
||||
groups.push_back(group_node_ptr.first);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
StaticExecutorEntitiesCollector::get_automatically_added_callback_groups_from_nodes()
|
||||
{
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr> groups;
|
||||
for (const auto & group_node_ptr : weak_groups_to_nodes_associated_with_executor_) {
|
||||
groups.push_back(group_node_ptr.first);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
@@ -12,31 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "rclcpp/executors/static_single_threaded_executor.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "rclcpp/executors/executor_entities_collection.hpp"
|
||||
#include "rcpputils/scope_exit.hpp"
|
||||
|
||||
using rclcpp::executors::StaticSingleThreadedExecutor;
|
||||
using rclcpp::experimental::ExecutableList;
|
||||
#include "rclcpp/executors/static_single_threaded_executor.hpp"
|
||||
#include "rclcpp/any_executable.hpp"
|
||||
|
||||
StaticSingleThreadedExecutor::StaticSingleThreadedExecutor(
|
||||
const rclcpp::ExecutorOptions & options)
|
||||
using rclcpp::executors::StaticSingleThreadedExecutor;
|
||||
|
||||
StaticSingleThreadedExecutor::StaticSingleThreadedExecutor(const rclcpp::ExecutorOptions & options)
|
||||
: rclcpp::Executor(options)
|
||||
{
|
||||
entities_collector_ = std::make_shared<StaticExecutorEntitiesCollector>();
|
||||
}
|
||||
|
||||
StaticSingleThreadedExecutor::~StaticSingleThreadedExecutor()
|
||||
{
|
||||
if (entities_collector_->is_init()) {
|
||||
entities_collector_->fini();
|
||||
}
|
||||
}
|
||||
StaticSingleThreadedExecutor::~StaticSingleThreadedExecutor() {}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::spin()
|
||||
@@ -46,14 +35,11 @@ StaticSingleThreadedExecutor::spin()
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
|
||||
// Set memory_strategy_ and exec_list_ based on weak_nodes_
|
||||
// Prepare wait_set_ based on memory_strategy_
|
||||
entities_collector_->init(&wait_set_, memory_strategy_);
|
||||
|
||||
// This is essentially the contents of the rclcpp::Executor::wait_for_work method,
|
||||
// except we need to keep the wait result to reproduce the StaticSingleThreadedExecutor
|
||||
// behavior.
|
||||
while (rclcpp::ok(this->context_) && spinning.load()) {
|
||||
// Refresh wait set and wait for work
|
||||
entities_collector_->refresh_wait_set();
|
||||
execute_ready_executables();
|
||||
this->spin_once_impl(std::chrono::nanoseconds(-1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +50,6 @@ StaticSingleThreadedExecutor::spin_some(std::chrono::nanoseconds max_duration)
|
||||
if (std::chrono::nanoseconds(0) == max_duration) {
|
||||
max_duration = std::chrono::nanoseconds::max();
|
||||
}
|
||||
|
||||
return this->spin_some_impl(max_duration, false);
|
||||
}
|
||||
|
||||
@@ -80,36 +65,32 @@ StaticSingleThreadedExecutor::spin_all(std::chrono::nanoseconds max_duration)
|
||||
void
|
||||
StaticSingleThreadedExecutor::spin_some_impl(std::chrono::nanoseconds max_duration, bool exhaustive)
|
||||
{
|
||||
// Make sure the entities collector has been initialized
|
||||
if (!entities_collector_->is_init()) {
|
||||
entities_collector_->init(&wait_set_, memory_strategy_);
|
||||
}
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
auto max_duration_not_elapsed = [max_duration, start]() {
|
||||
if (std::chrono::nanoseconds(0) == max_duration) {
|
||||
// told to spin forever if need be
|
||||
return true;
|
||||
} else if (std::chrono::steady_clock::now() - start < max_duration) {
|
||||
// told to spin only for some maximum amount of time
|
||||
return true;
|
||||
}
|
||||
// spun too long
|
||||
return false;
|
||||
const auto spin_forever = std::chrono::nanoseconds(0) == max_duration;
|
||||
const auto cur_duration = std::chrono::steady_clock::now() - start;
|
||||
return spin_forever || (cur_duration < max_duration);
|
||||
};
|
||||
|
||||
if (spinning.exchange(true)) {
|
||||
throw std::runtime_error("spin_some() called while already spinning");
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );
|
||||
RCPPUTILS_SCOPE_EXIT(this->spinning.store(false););
|
||||
|
||||
while (rclcpp::ok(context_) && spinning.load() && max_duration_not_elapsed()) {
|
||||
// Get executables that are ready now
|
||||
entities_collector_->refresh_wait_set(std::chrono::milliseconds::zero());
|
||||
// Execute ready executables
|
||||
bool work_available = execute_ready_executables();
|
||||
if (!work_available || !exhaustive) {
|
||||
break;
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
auto wait_result = this->collect_and_wait(std::chrono::nanoseconds(0));
|
||||
if (wait_result.has_value()) {
|
||||
// Execute ready executables
|
||||
bool work_available = this->execute_ready_executables(
|
||||
current_collection_,
|
||||
wait_result.value(),
|
||||
false);
|
||||
if (!work_available || !exhaustive) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,163 +98,106 @@ StaticSingleThreadedExecutor::spin_some_impl(std::chrono::nanoseconds max_durati
|
||||
void
|
||||
StaticSingleThreadedExecutor::spin_once_impl(std::chrono::nanoseconds timeout)
|
||||
{
|
||||
// Make sure the entities collector has been initialized
|
||||
if (!entities_collector_->is_init()) {
|
||||
entities_collector_->init(&wait_set_, memory_strategy_);
|
||||
}
|
||||
|
||||
if (rclcpp::ok(context_) && spinning.load()) {
|
||||
// Wait until we have a ready entity or timeout expired
|
||||
entities_collector_->refresh_wait_set(timeout);
|
||||
// Execute ready executables
|
||||
execute_ready_executables(true);
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
auto wait_result = this->collect_and_wait(timeout);
|
||||
if (wait_result.has_value()) {
|
||||
this->execute_ready_executables(current_collection_, wait_result.value(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::add_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr,
|
||||
bool notify)
|
||||
std::optional<rclcpp::WaitResult<rclcpp::WaitSet>>
|
||||
StaticSingleThreadedExecutor::collect_and_wait(std::chrono::nanoseconds timeout)
|
||||
{
|
||||
bool is_new_node = entities_collector_->add_callback_group(group_ptr, node_ptr);
|
||||
if (is_new_node && notify) {
|
||||
// Interrupt waiting to handle new node
|
||||
interrupt_guard_condition_->trigger();
|
||||
if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {
|
||||
this->collect_entities();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::add_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr, bool notify)
|
||||
{
|
||||
bool is_new_node = entities_collector_->add_node(node_ptr);
|
||||
if (is_new_node && notify) {
|
||||
// Interrupt waiting to handle new node
|
||||
interrupt_guard_condition_->trigger();
|
||||
auto wait_result = wait_set_.wait(std::chrono::nanoseconds(timeout));
|
||||
if (wait_result.kind() == WaitResultKind::Empty) {
|
||||
RCUTILS_LOG_WARN_NAMED(
|
||||
"rclcpp",
|
||||
"empty wait set received in wait(). This should never happen.");
|
||||
return {};
|
||||
} else {
|
||||
if (wait_result.kind() == WaitResultKind::Ready && current_notify_waitable_) {
|
||||
auto & rcl_wait_set = wait_result.get_wait_set().get_rcl_wait_set();
|
||||
if (current_notify_waitable_->is_ready(rcl_wait_set)) {
|
||||
current_notify_waitable_->execute(current_notify_waitable_->take_data());
|
||||
}
|
||||
}
|
||||
}
|
||||
return wait_result;
|
||||
}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::add_node(std::shared_ptr<rclcpp::Node> node_ptr, bool notify)
|
||||
{
|
||||
this->add_node(node_ptr->get_node_base_interface(), notify);
|
||||
}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::remove_callback_group(
|
||||
rclcpp::CallbackGroup::SharedPtr group_ptr, bool notify)
|
||||
{
|
||||
bool node_removed = entities_collector_->remove_callback_group(group_ptr);
|
||||
// If the node was matched and removed, interrupt waiting
|
||||
if (node_removed && notify) {
|
||||
interrupt_guard_condition_->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::remove_node(
|
||||
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr, bool notify)
|
||||
{
|
||||
bool node_removed = entities_collector_->remove_node(node_ptr);
|
||||
if (!node_removed) {
|
||||
throw std::runtime_error("Node needs to be associated with this executor.");
|
||||
}
|
||||
// If the node was matched and removed, interrupt waiting
|
||||
if (notify) {
|
||||
interrupt_guard_condition_->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
StaticSingleThreadedExecutor::get_all_callback_groups()
|
||||
{
|
||||
return entities_collector_->get_all_callback_groups();
|
||||
}
|
||||
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
StaticSingleThreadedExecutor::get_manually_added_callback_groups()
|
||||
{
|
||||
return entities_collector_->get_manually_added_callback_groups();
|
||||
}
|
||||
|
||||
std::vector<rclcpp::CallbackGroup::WeakPtr>
|
||||
StaticSingleThreadedExecutor::get_automatically_added_callback_groups_from_nodes()
|
||||
{
|
||||
return entities_collector_->get_automatically_added_callback_groups_from_nodes();
|
||||
}
|
||||
|
||||
void
|
||||
StaticSingleThreadedExecutor::remove_node(std::shared_ptr<rclcpp::Node> node_ptr, bool notify)
|
||||
{
|
||||
this->remove_node(node_ptr->get_node_base_interface(), notify);
|
||||
}
|
||||
|
||||
bool
|
||||
StaticSingleThreadedExecutor::execute_ready_executables(bool spin_once)
|
||||
// This preserves the "scheduling semantics" of the StaticSingleThreadedExecutor
|
||||
// from the original implementation.
|
||||
bool StaticSingleThreadedExecutor::execute_ready_executables(
|
||||
const rclcpp::executors::ExecutorEntitiesCollection & collection,
|
||||
rclcpp::WaitResult<rclcpp::WaitSet> & wait_result,
|
||||
bool spin_once)
|
||||
{
|
||||
bool any_ready_executable = false;
|
||||
if (wait_result.kind() != rclcpp::WaitResultKind::Ready) {
|
||||
return any_ready_executable;
|
||||
}
|
||||
|
||||
// Execute all the ready subscriptions
|
||||
for (size_t i = 0; i < wait_set_.size_of_subscriptions; ++i) {
|
||||
if (i < entities_collector_->get_number_of_subscriptions()) {
|
||||
if (wait_set_.subscriptions[i]) {
|
||||
execute_subscription(entities_collector_->get_subscription(i));
|
||||
if (spin_once) {
|
||||
return true;
|
||||
}
|
||||
any_ready_executable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Execute all the ready timers
|
||||
for (size_t i = 0; i < wait_set_.size_of_timers; ++i) {
|
||||
if (i < entities_collector_->get_number_of_timers()) {
|
||||
if (wait_set_.timers[i] && entities_collector_->get_timer(i)->is_ready()) {
|
||||
auto timer = entities_collector_->get_timer(i);
|
||||
timer->call();
|
||||
execute_timer(std::move(timer));
|
||||
if (spin_once) {
|
||||
return true;
|
||||
}
|
||||
any_ready_executable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Execute all the ready services
|
||||
for (size_t i = 0; i < wait_set_.size_of_services; ++i) {
|
||||
if (i < entities_collector_->get_number_of_services()) {
|
||||
if (wait_set_.services[i]) {
|
||||
execute_service(entities_collector_->get_service(i));
|
||||
if (spin_once) {
|
||||
return true;
|
||||
}
|
||||
any_ready_executable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Execute all the ready clients
|
||||
for (size_t i = 0; i < wait_set_.size_of_clients; ++i) {
|
||||
if (i < entities_collector_->get_number_of_clients()) {
|
||||
if (wait_set_.clients[i]) {
|
||||
execute_client(entities_collector_->get_client(i));
|
||||
if (spin_once) {
|
||||
return true;
|
||||
}
|
||||
any_ready_executable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Execute all the ready waitables
|
||||
for (size_t i = 0; i < entities_collector_->get_number_of_waitables(); ++i) {
|
||||
auto waitable = entities_collector_->get_waitable(i);
|
||||
if (waitable->is_ready(&wait_set_)) {
|
||||
auto data = waitable->take_data();
|
||||
waitable->execute(data);
|
||||
if (spin_once) {
|
||||
return true;
|
||||
}
|
||||
while (auto subscription = wait_result.next_ready_subscription()) {
|
||||
auto entity_iter = collection.subscriptions.find(subscription->get_subscription_handle().get());
|
||||
if (entity_iter != collection.subscriptions.end()) {
|
||||
execute_subscription(subscription);
|
||||
any_ready_executable = true;
|
||||
if (spin_once) {return any_ready_executable;}
|
||||
}
|
||||
}
|
||||
|
||||
size_t current_timer_index = 0;
|
||||
while (true) {
|
||||
auto [timer, timer_index] = wait_result.peek_next_ready_timer(current_timer_index);
|
||||
if (nullptr == timer) {
|
||||
break;
|
||||
}
|
||||
current_timer_index = timer_index;
|
||||
auto entity_iter = collection.timers.find(timer->get_timer_handle().get());
|
||||
if (entity_iter != collection.timers.end()) {
|
||||
wait_result.clear_timer_with_index(current_timer_index);
|
||||
auto data = timer->call();
|
||||
if (!data) {
|
||||
// someone canceled the timer between is_ready and call
|
||||
continue;
|
||||
}
|
||||
|
||||
execute_timer(std::move(timer), data);
|
||||
any_ready_executable = true;
|
||||
if (spin_once) {return any_ready_executable;}
|
||||
}
|
||||
}
|
||||
|
||||
while (auto client = wait_result.next_ready_client()) {
|
||||
auto entity_iter = collection.clients.find(client->get_client_handle().get());
|
||||
if (entity_iter != collection.clients.end()) {
|
||||
execute_client(client);
|
||||
any_ready_executable = true;
|
||||
if (spin_once) {return any_ready_executable;}
|
||||
}
|
||||
}
|
||||
|
||||
while (auto service = wait_result.next_ready_service()) {
|
||||
auto entity_iter = collection.services.find(service->get_service_handle().get());
|
||||
if (entity_iter != collection.services.end()) {
|
||||
execute_service(service);
|
||||
any_ready_executable = true;
|
||||
if (spin_once) {return any_ready_executable;}
|
||||
}
|
||||
}
|
||||
|
||||
while (auto waitable = wait_result.next_ready_waitable()) {
|
||||
auto entity_iter = collection.waitables.find(waitable.get());
|
||||
if (entity_iter != collection.waitables.end()) {
|
||||
const auto data = waitable->take_data();
|
||||
waitable->execute(data);
|
||||
any_ready_executable = true;
|
||||
if (spin_once) {return any_ready_executable;}
|
||||
}
|
||||
}
|
||||
return any_ready_executable;
|
||||
|
||||
@@ -40,10 +40,12 @@ EventsExecutor::EventsExecutor(
|
||||
// The timers manager can be used either to only track timers (in this case an expired
|
||||
// timer will generate an executor event and then it will be executed by the executor thread)
|
||||
// or it can also take care of executing expired timers in its dedicated thread.
|
||||
std::function<void(const rclcpp::TimerBase *)> timer_on_ready_cb = nullptr;
|
||||
std::function<void(const rclcpp::TimerBase *,
|
||||
const std::shared_ptr<void> &)> timer_on_ready_cb = nullptr;
|
||||
if (!execute_timers_separate_thread) {
|
||||
timer_on_ready_cb = [this](const rclcpp::TimerBase * timer_id) {
|
||||
ExecutorEvent event = {timer_id, -1, ExecutorEventType::TIMER_EVENT, 1};
|
||||
timer_on_ready_cb =
|
||||
[this](const rclcpp::TimerBase * timer_id, const std::shared_ptr<void> & data) {
|
||||
ExecutorEvent event = {timer_id, data, -1, ExecutorEventType::TIMER_EVENT, 1};
|
||||
this->events_queue_->enqueue(event);
|
||||
};
|
||||
}
|
||||
@@ -88,7 +90,7 @@ EventsExecutor::EventsExecutor(
|
||||
}
|
||||
|
||||
ExecutorEvent event =
|
||||
{notify_waitable_entity_id, waitable_data, ExecutorEventType::WAITABLE_EVENT, 1};
|
||||
{notify_waitable_entity_id, nullptr, waitable_data, ExecutorEventType::WAITABLE_EVENT, 1};
|
||||
this->events_queue_->enqueue(event);
|
||||
});
|
||||
|
||||
@@ -206,11 +208,12 @@ EventsExecutor::spin_once_impl(std::chrono::nanoseconds timeout)
|
||||
timeout = std::chrono::nanoseconds::max();
|
||||
}
|
||||
|
||||
// Select the smallest between input timeout and timer timeout
|
||||
// Select the smallest between input timeout and timer timeout.
|
||||
// Cancelled timers are not considered.
|
||||
bool is_timer_timeout = false;
|
||||
auto next_timer_timeout = timers_manager_->get_head_timeout();
|
||||
if (next_timer_timeout < timeout) {
|
||||
timeout = next_timer_timeout;
|
||||
if (next_timer_timeout.has_value() && next_timer_timeout.value() < timeout) {
|
||||
timeout = next_timer_timeout.value();
|
||||
is_timer_timeout = true;
|
||||
}
|
||||
|
||||
@@ -324,7 +327,7 @@ EventsExecutor::execute_event(const ExecutorEvent & event)
|
||||
case ExecutorEventType::TIMER_EVENT:
|
||||
{
|
||||
timers_manager_->execute_ready_timer(
|
||||
static_cast<const rclcpp::TimerBase *>(event.entity_key));
|
||||
static_cast<const rclcpp::TimerBase *>(event.entity_key), event.data);
|
||||
break;
|
||||
}
|
||||
case ExecutorEventType::WAITABLE_EVENT:
|
||||
@@ -338,7 +341,7 @@ EventsExecutor::execute_event(const ExecutorEvent & event)
|
||||
}
|
||||
if (waitable) {
|
||||
for (size_t i = 0; i < event.num_events; i++) {
|
||||
auto data = waitable->take_data_by_entity_id(event.waitable_data);
|
||||
const auto data = waitable->take_data_by_entity_id(event.waitable_data);
|
||||
waitable->execute(data);
|
||||
}
|
||||
}
|
||||
@@ -484,7 +487,7 @@ EventsExecutor::create_entity_callback(
|
||||
{
|
||||
std::function<void(size_t)>
|
||||
callback = [this, entity_key, event_type](size_t num_events) {
|
||||
ExecutorEvent event = {entity_key, -1, event_type, num_events};
|
||||
ExecutorEvent event = {entity_key, nullptr, -1, event_type, num_events};
|
||||
this->events_queue_->enqueue(event);
|
||||
};
|
||||
return callback;
|
||||
@@ -496,7 +499,7 @@ EventsExecutor::create_waitable_callback(const rclcpp::Waitable * entity_key)
|
||||
std::function<void(size_t, int)>
|
||||
callback = [this, entity_key](size_t num_events, int waitable_data) {
|
||||
ExecutorEvent event =
|
||||
{entity_key, waitable_data, ExecutorEventType::WAITABLE_EVENT, num_events};
|
||||
{entity_key, nullptr, waitable_data, ExecutorEventType::WAITABLE_EVENT, num_events};
|
||||
this->events_queue_->enqueue(event);
|
||||
};
|
||||
return callback;
|
||||
|
||||
@@ -27,7 +27,7 @@ using rclcpp::experimental::TimersManager;
|
||||
|
||||
TimersManager::TimersManager(
|
||||
std::shared_ptr<rclcpp::Context> context,
|
||||
std::function<void(const rclcpp::TimerBase *)> on_ready_callback)
|
||||
std::function<void(const rclcpp::TimerBase *, const std::shared_ptr<void> &)> on_ready_callback)
|
||||
: on_ready_callback_(on_ready_callback),
|
||||
context_(context)
|
||||
{
|
||||
@@ -100,7 +100,7 @@ void TimersManager::stop()
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds TimersManager::get_head_timeout()
|
||||
std::optional<std::chrono::nanoseconds> TimersManager::get_head_timeout()
|
||||
{
|
||||
// Do not allow to interfere with the thread running
|
||||
if (running_) {
|
||||
@@ -148,8 +148,12 @@ bool TimersManager::execute_head_timer()
|
||||
if (timer_ready) {
|
||||
// NOTE: here we always execute the timer, regardless of whether the
|
||||
// on_ready_callback is set or not.
|
||||
head_timer->call();
|
||||
head_timer->execute_callback();
|
||||
auto data = head_timer->call();
|
||||
if (!data) {
|
||||
// someone canceled the timer between is_ready and call
|
||||
return false;
|
||||
}
|
||||
head_timer->execute_callback(data);
|
||||
timers_heap.heapify_root();
|
||||
weak_timers_heap_.store(timers_heap);
|
||||
}
|
||||
@@ -157,7 +161,9 @@ bool TimersManager::execute_head_timer()
|
||||
return timer_ready;
|
||||
}
|
||||
|
||||
void TimersManager::execute_ready_timer(const rclcpp::TimerBase * timer_id)
|
||||
void TimersManager::execute_ready_timer(
|
||||
const rclcpp::TimerBase * timer_id,
|
||||
const std::shared_ptr<void> & data)
|
||||
{
|
||||
TimerPtr ready_timer;
|
||||
{
|
||||
@@ -165,11 +171,11 @@ void TimersManager::execute_ready_timer(const rclcpp::TimerBase * timer_id)
|
||||
ready_timer = weak_timers_heap_.get_timer(timer_id);
|
||||
}
|
||||
if (ready_timer) {
|
||||
ready_timer->execute_callback();
|
||||
ready_timer->execute_callback(data);
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds TimersManager::get_head_timeout_unsafe()
|
||||
std::optional<std::chrono::nanoseconds> TimersManager::get_head_timeout_unsafe()
|
||||
{
|
||||
// If we don't have any weak pointer, then we just return maximum timeout
|
||||
if (weak_timers_heap_.empty()) {
|
||||
@@ -191,7 +197,9 @@ std::chrono::nanoseconds TimersManager::get_head_timeout_unsafe()
|
||||
}
|
||||
head_timer = locked_heap.front();
|
||||
}
|
||||
|
||||
if (head_timer->is_canceled()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return head_timer->time_until_trigger();
|
||||
}
|
||||
|
||||
@@ -213,11 +221,16 @@ void TimersManager::execute_ready_timers_unsafe()
|
||||
const size_t number_ready_timers = locked_heap.get_number_ready_timers();
|
||||
size_t executed_timers = 0;
|
||||
while (executed_timers < number_ready_timers && head_timer->is_ready()) {
|
||||
head_timer->call();
|
||||
if (on_ready_callback_) {
|
||||
on_ready_callback_(head_timer.get());
|
||||
auto data = head_timer->call();
|
||||
if (data) {
|
||||
if (on_ready_callback_) {
|
||||
on_ready_callback_(head_timer.get(), data);
|
||||
} else {
|
||||
head_timer->execute_callback(data);
|
||||
}
|
||||
} else {
|
||||
head_timer->execute_callback();
|
||||
// someone canceled the timer between is_ready and call
|
||||
// we don't do anything, as the timer is now 'processed'
|
||||
}
|
||||
|
||||
executed_timers++;
|
||||
@@ -242,17 +255,34 @@ void TimersManager::run_timers()
|
||||
// Lock mutex
|
||||
std::unique_lock<std::mutex> lock(timers_mutex_);
|
||||
|
||||
std::chrono::nanoseconds time_to_sleep = get_head_timeout_unsafe();
|
||||
std::optional<std::chrono::nanoseconds> time_to_sleep = get_head_timeout_unsafe();
|
||||
|
||||
// No need to wait if a timer is already available
|
||||
if (time_to_sleep > std::chrono::nanoseconds::zero()) {
|
||||
if (time_to_sleep != std::chrono::nanoseconds::max()) {
|
||||
// Wait until timeout or notification that timers have been updated
|
||||
timers_cv_.wait_for(lock, time_to_sleep, [this]() {return timers_updated_;});
|
||||
} else {
|
||||
// Wait until notification that timers have been updated
|
||||
timers_cv_.wait(lock, [this]() {return timers_updated_;});
|
||||
}
|
||||
// If head timer was cancelled, try to reheap and get a new head.
|
||||
// This avoids an edge condition where head timer is cancelled, but other
|
||||
// valid timers remain in the heap.
|
||||
if (!time_to_sleep.has_value()) {
|
||||
// Re-heap to (possibly) move cancelled timer from head of heap. If
|
||||
// entire heap is cancelled, this will still result in a nullopt.
|
||||
TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
|
||||
locked_heap.heapify();
|
||||
weak_timers_heap_.store(locked_heap);
|
||||
time_to_sleep = get_head_timeout_unsafe();
|
||||
}
|
||||
|
||||
// If no timers, or all timers cancelled, wait for an update.
|
||||
if (!time_to_sleep.has_value() || (time_to_sleep.value() == std::chrono::nanoseconds::max()) ) {
|
||||
// Wait until notification that timers have been updated
|
||||
timers_cv_.wait(lock, [this]() {return timers_updated_;});
|
||||
|
||||
// Re-heap in case ordering changed due to a cancelled timer
|
||||
// re-activating.
|
||||
TimersHeap locked_heap = weak_timers_heap_.validate_and_lock();
|
||||
locked_heap.heapify();
|
||||
weak_timers_heap_.store(locked_heap);
|
||||
} else if (time_to_sleep.value() != std::chrono::nanoseconds::zero()) {
|
||||
// If time_to_sleep is zero, we immediately execute. Otherwise, wait
|
||||
// until timeout or notification that timers have been updated
|
||||
timers_cv_.wait_for(lock, time_to_sleep.value(), [this]() {return timers_updated_;});
|
||||
}
|
||||
|
||||
// Reset timers updated flag
|
||||
|
||||
164
rclcpp/src/rclcpp/generic_client.cpp
Normal file
164
rclcpp/src/rclcpp/generic_client.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright 2023 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 <future>
|
||||
|
||||
#include "rclcpp/generic_client.hpp"
|
||||
#include "rclcpp/typesupport_helpers.hpp"
|
||||
|
||||
#include "rosidl_runtime_c/service_type_support_struct.h"
|
||||
#include "rosidl_typesupport_introspection_cpp/identifier.hpp"
|
||||
#include "rosidl_typesupport_introspection_cpp/service_introspection.hpp"
|
||||
|
||||
namespace rclcpp
|
||||
{
|
||||
GenericClient::GenericClient(
|
||||
rclcpp::node_interfaces::NodeBaseInterface * node_base,
|
||||
rclcpp::node_interfaces::NodeGraphInterface::SharedPtr node_graph,
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
rcl_client_options_t & client_options)
|
||||
: ClientBase(node_base, node_graph)
|
||||
{
|
||||
ts_lib_ = get_typesupport_library(
|
||||
service_type, "rosidl_typesupport_cpp");
|
||||
|
||||
auto service_ts_ = get_service_typesupport_handle(
|
||||
service_type, "rosidl_typesupport_cpp", *ts_lib_);
|
||||
|
||||
auto response_type_support_intro = get_message_typesupport_handle(
|
||||
service_ts_->response_typesupport,
|
||||
rosidl_typesupport_introspection_cpp::typesupport_identifier);
|
||||
response_members_ = static_cast<const rosidl_typesupport_introspection_cpp::MessageMembers *>(
|
||||
response_type_support_intro->data);
|
||||
|
||||
rcl_ret_t ret = rcl_client_init(
|
||||
this->get_client_handle().get(),
|
||||
this->get_rcl_node_handle(),
|
||||
service_ts_,
|
||||
service_name.c_str(),
|
||||
&client_options);
|
||||
if (ret != RCL_RET_OK) {
|
||||
if (ret == RCL_RET_SERVICE_NAME_INVALID) {
|
||||
auto rcl_node_handle = this->get_rcl_node_handle();
|
||||
// this will throw on any validation problem
|
||||
rcl_reset_error();
|
||||
expand_topic_or_service_name(
|
||||
service_name,
|
||||
rcl_node_get_name(rcl_node_handle),
|
||||
rcl_node_get_namespace(rcl_node_handle),
|
||||
true);
|
||||
}
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "could not create generic client");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
GenericClient::create_response()
|
||||
{
|
||||
void * response = new uint8_t[response_members_->size_of_];
|
||||
response_members_->init_function(response, rosidl_runtime_cpp::MessageInitialization::ZERO);
|
||||
return std::shared_ptr<void>(
|
||||
response,
|
||||
[this](void * p)
|
||||
{
|
||||
response_members_->fini_function(p);
|
||||
delete[] reinterpret_cast<uint8_t *>(p);
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<rmw_request_id_t>
|
||||
GenericClient::create_request_header()
|
||||
{
|
||||
// TODO(wjwwood): This should probably use rmw_request_id's allocator.
|
||||
// (since it is a C type)
|
||||
return std::shared_ptr<rmw_request_id_t>(new rmw_request_id_t);
|
||||
}
|
||||
|
||||
void
|
||||
GenericClient::handle_response(
|
||||
std::shared_ptr<rmw_request_id_t> request_header,
|
||||
std::shared_ptr<void> response)
|
||||
{
|
||||
auto optional_pending_request =
|
||||
this->get_and_erase_pending_request(request_header->sequence_number);
|
||||
if (!optional_pending_request) {
|
||||
return;
|
||||
}
|
||||
auto & value = *optional_pending_request;
|
||||
if (std::holds_alternative<Promise>(value)) {
|
||||
auto & promise = std::get<Promise>(value);
|
||||
promise.set_value(std::move(response));
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
GenericClient::prune_pending_requests()
|
||||
{
|
||||
std::lock_guard guard(pending_requests_mutex_);
|
||||
auto ret = pending_requests_.size();
|
||||
pending_requests_.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
GenericClient::remove_pending_request(int64_t request_id)
|
||||
{
|
||||
std::lock_guard guard(pending_requests_mutex_);
|
||||
return pending_requests_.erase(request_id) != 0u;
|
||||
}
|
||||
|
||||
std::optional<GenericClient::CallbackInfoVariant>
|
||||
GenericClient::get_and_erase_pending_request(int64_t request_number)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(pending_requests_mutex_);
|
||||
auto it = pending_requests_.find(request_number);
|
||||
if (it == pending_requests_.end()) {
|
||||
RCUTILS_LOG_DEBUG_NAMED(
|
||||
"rclcpp",
|
||||
"Received invalid sequence number. Ignoring...");
|
||||
return std::nullopt;
|
||||
}
|
||||
auto value = std::move(it->second.second);
|
||||
pending_requests_.erase(request_number);
|
||||
return value;
|
||||
}
|
||||
|
||||
GenericClient::FutureAndRequestId
|
||||
GenericClient::async_send_request(const Request request)
|
||||
{
|
||||
Promise promise;
|
||||
auto future = promise.get_future();
|
||||
auto req_id = async_send_request_impl(
|
||||
request,
|
||||
std::move(promise));
|
||||
return FutureAndRequestId(std::move(future), req_id);
|
||||
}
|
||||
|
||||
int64_t
|
||||
GenericClient::async_send_request_impl(const Request request, CallbackInfoVariant value)
|
||||
{
|
||||
int64_t sequence_number;
|
||||
std::lock_guard<std::mutex> lock(pending_requests_mutex_);
|
||||
rcl_ret_t ret = rcl_send_request(get_client_handle().get(), request, &sequence_number);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "failed to send request");
|
||||
}
|
||||
pending_requests_.try_emplace(
|
||||
sequence_number,
|
||||
std::make_pair(std::chrono::system_clock::now(), std::move(value)));
|
||||
return sequence_number;
|
||||
}
|
||||
|
||||
} // namespace rclcpp
|
||||
@@ -23,6 +23,10 @@ namespace rclcpp
|
||||
|
||||
void GenericPublisher::publish(const rclcpp::SerializedMessage & message)
|
||||
{
|
||||
TRACETOOLS_TRACEPOINT(
|
||||
rclcpp_publish,
|
||||
nullptr,
|
||||
static_cast<const void *>(&message.get_rcl_serialized_message()));
|
||||
auto return_code = rcl_publish_serialized_message(
|
||||
get_publisher_handle().get(), &message.get_rcl_serialized_message(), NULL);
|
||||
|
||||
@@ -68,6 +72,7 @@ void GenericPublisher::deserialize_message(
|
||||
|
||||
void GenericPublisher::publish_loaned_message(void * loaned_message)
|
||||
{
|
||||
TRACETOOLS_TRACEPOINT(rclcpp_publish, nullptr, static_cast<const void *>(loaned_message));
|
||||
auto return_code = rcl_publish_loaned_message(
|
||||
get_publisher_handle().get(), loaned_message, NULL);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ GenericSubscription::handle_serialized_message(
|
||||
const std::shared_ptr<rclcpp::SerializedMessage> & message,
|
||||
const rclcpp::MessageInfo & message_info)
|
||||
{
|
||||
callback_(message, message_info);
|
||||
any_callback_.dispatch(message, message_info);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -23,16 +23,17 @@ namespace rclcpp
|
||||
{
|
||||
|
||||
GuardCondition::GuardCondition(
|
||||
rclcpp::Context::SharedPtr context,
|
||||
const rclcpp::Context::SharedPtr & context,
|
||||
rcl_guard_condition_options_t guard_condition_options)
|
||||
: context_(context), rcl_guard_condition_{rcl_get_zero_initialized_guard_condition()}
|
||||
: rcl_guard_condition_{rcl_get_zero_initialized_guard_condition()}
|
||||
{
|
||||
if (!context_) {
|
||||
if (!context) {
|
||||
throw std::invalid_argument("context argument unexpectedly nullptr");
|
||||
}
|
||||
|
||||
rcl_ret_t ret = rcl_guard_condition_init(
|
||||
&this->rcl_guard_condition_,
|
||||
context_->get_rcl_context().get(),
|
||||
context->get_rcl_context().get(),
|
||||
guard_condition_options);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(ret, "failed to create guard condition");
|
||||
@@ -53,12 +54,6 @@ GuardCondition::~GuardCondition()
|
||||
}
|
||||
}
|
||||
|
||||
rclcpp::Context::SharedPtr
|
||||
GuardCondition::get_context() const
|
||||
{
|
||||
return context_;
|
||||
}
|
||||
|
||||
rcl_guard_condition_t &
|
||||
GuardCondition::get_rcl_guard_condition()
|
||||
{
|
||||
@@ -97,19 +92,19 @@ GuardCondition::exchange_in_use_by_wait_set_state(bool in_use_state)
|
||||
}
|
||||
|
||||
void
|
||||
GuardCondition::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
GuardCondition::add_to_wait_set(rcl_wait_set_t & wait_set)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(reentrant_mutex_);
|
||||
|
||||
if (exchange_in_use_by_wait_set_state(true)) {
|
||||
if (wait_set != wait_set_) {
|
||||
if (&wait_set != wait_set_) {
|
||||
throw std::runtime_error("guard condition has already been added to a wait set.");
|
||||
}
|
||||
} else {
|
||||
wait_set_ = wait_set;
|
||||
wait_set_ = &wait_set;
|
||||
}
|
||||
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(wait_set, &this->rcl_guard_condition_, NULL);
|
||||
rcl_ret_t ret = rcl_wait_set_add_guard_condition(&wait_set, &this->rcl_guard_condition_, NULL);
|
||||
if (RCL_RET_OK != ret) {
|
||||
rclcpp::exceptions::throw_from_rcl_error(
|
||||
ret, "failed to add guard condition to wait set");
|
||||
|
||||
@@ -32,13 +32,24 @@ IntraProcessManager::~IntraProcessManager()
|
||||
{}
|
||||
|
||||
uint64_t
|
||||
IntraProcessManager::add_publisher(rclcpp::PublisherBase::SharedPtr publisher)
|
||||
IntraProcessManager::add_publisher(
|
||||
rclcpp::PublisherBase::SharedPtr publisher,
|
||||
rclcpp::experimental::buffers::IntraProcessBufferBase::SharedPtr buffer)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
|
||||
|
||||
uint64_t pub_id = IntraProcessManager::get_next_unique_id();
|
||||
|
||||
publishers_[pub_id] = publisher;
|
||||
if (publisher->is_durability_transient_local()) {
|
||||
if (buffer) {
|
||||
publisher_buffers_[pub_id] = buffer;
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
"transient_local publisher needs to pass"
|
||||
"a valid publisher buffer ptr when calling add_publisher()");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the subscriptions storage for this publisher.
|
||||
pub_to_subs_[pub_id] = SplittedSubscriptions();
|
||||
@@ -58,30 +69,6 @@ IntraProcessManager::add_publisher(rclcpp::PublisherBase::SharedPtr publisher)
|
||||
return pub_id;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IntraProcessManager::add_subscription(SubscriptionIntraProcessBase::SharedPtr subscription)
|
||||
{
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
|
||||
|
||||
uint64_t sub_id = IntraProcessManager::get_next_unique_id();
|
||||
|
||||
subscriptions_[sub_id] = subscription;
|
||||
|
||||
// adds the subscription id to all the matchable publishers
|
||||
for (auto & pair : publishers_) {
|
||||
auto publisher = pair.second.lock();
|
||||
if (!publisher) {
|
||||
continue;
|
||||
}
|
||||
if (can_communicate(publisher, subscription)) {
|
||||
uint64_t pub_id = pair.first;
|
||||
insert_sub_id_for_pub(sub_id, pub_id, subscription->use_take_shared_method());
|
||||
}
|
||||
}
|
||||
|
||||
return sub_id;
|
||||
}
|
||||
|
||||
void
|
||||
IntraProcessManager::remove_subscription(uint64_t intra_process_subscription_id)
|
||||
{
|
||||
@@ -112,6 +99,7 @@ IntraProcessManager::remove_publisher(uint64_t intra_process_publisher_id)
|
||||
std::unique_lock<std::shared_timed_mutex> lock(mutex_);
|
||||
|
||||
publishers_.erase(intra_process_publisher_id);
|
||||
publisher_buffers_.erase(intra_process_publisher_id);
|
||||
pub_to_subs_.erase(intra_process_publisher_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "rcl/arguments.h"
|
||||
|
||||
#include "rclcpp/create_generic_client.hpp"
|
||||
#include "rclcpp/detail/qos_parameters.hpp"
|
||||
#include "rclcpp/exceptions.hpp"
|
||||
#include "rclcpp/graph_listener.hpp"
|
||||
@@ -676,3 +677,20 @@ Node::get_node_options() const
|
||||
{
|
||||
return this->node_options_;
|
||||
}
|
||||
|
||||
rclcpp::GenericClient::SharedPtr
|
||||
Node::create_generic_client(
|
||||
const std::string & service_name,
|
||||
const std::string & service_type,
|
||||
const rclcpp::QoS & qos,
|
||||
rclcpp::CallbackGroup::SharedPtr group)
|
||||
{
|
||||
return rclcpp::create_generic_client(
|
||||
node_base_,
|
||||
node_graph_,
|
||||
node_services_,
|
||||
service_name,
|
||||
service_type,
|
||||
qos,
|
||||
group);
|
||||
}
|
||||
|
||||
@@ -218,14 +218,9 @@ NodeBase::create_callback_group(
|
||||
rclcpp::CallbackGroupType group_type,
|
||||
bool automatically_add_to_executor_with_node)
|
||||
{
|
||||
auto weak_context = this->get_context()->weak_from_this();
|
||||
auto get_node_context = [weak_context]() -> rclcpp::Context::SharedPtr {
|
||||
return weak_context.lock();
|
||||
};
|
||||
|
||||
auto group = std::make_shared<rclcpp::CallbackGroup>(
|
||||
group_type,
|
||||
get_node_context,
|
||||
context_->weak_from_this(),
|
||||
automatically_add_to_executor_with_node);
|
||||
std::lock_guard<std::mutex> lock(callback_groups_mutex_);
|
||||
callback_groups_.push_back(group);
|
||||
|
||||
@@ -270,6 +270,13 @@ PublisherBase::get_intra_process_subscription_count() const
|
||||
return ipm->get_subscription_count(intra_process_publisher_id_);
|
||||
}
|
||||
|
||||
bool
|
||||
PublisherBase::is_durability_transient_local() const
|
||||
{
|
||||
return rcl_publisher_get_actual_qos(publisher_handle_.get())->durability ==
|
||||
RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL;
|
||||
}
|
||||
|
||||
rclcpp::QoS
|
||||
PublisherBase::get_actual_qos() const
|
||||
{
|
||||
|
||||
@@ -244,6 +244,9 @@ SubscriptionBase::take_serialized(
|
||||
&message_out.get_rcl_serialized_message(),
|
||||
&message_info_out.get_rmw_message_info(),
|
||||
nullptr);
|
||||
TRACETOOLS_TRACEPOINT(
|
||||
rclcpp_take,
|
||||
static_cast<const void *>(&message_out.get_rcl_serialized_message()));
|
||||
if (RCL_RET_SUBSCRIPTION_TAKE_FAILED == ret) {
|
||||
return false;
|
||||
} else if (RCL_RET_OK != ret) {
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
using rclcpp::experimental::SubscriptionIntraProcessBase;
|
||||
|
||||
void
|
||||
SubscriptionIntraProcessBase::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
SubscriptionIntraProcessBase::add_to_wait_set(rcl_wait_set_t & wait_set)
|
||||
{
|
||||
detail::add_guard_condition_to_rcl_wait_set(*wait_set, gc_);
|
||||
detail::add_guard_condition_to_rcl_wait_set(wait_set, gc_);
|
||||
}
|
||||
|
||||
const char *
|
||||
@@ -34,3 +34,9 @@ SubscriptionIntraProcessBase::get_actual_qos() const
|
||||
{
|
||||
return qos_profile_;
|
||||
}
|
||||
|
||||
bool
|
||||
SubscriptionIntraProcessBase::is_durability_transient_local() const
|
||||
{
|
||||
return qos_profile_.durability() == rclcpp::DurabilityPolicy::TransientLocal;
|
||||
}
|
||||
|
||||
@@ -60,11 +60,17 @@ Time::Time(int32_t seconds, uint32_t nanoseconds, rcl_clock_type_t clock_type)
|
||||
Time::Time(int64_t nanoseconds, rcl_clock_type_t clock_type)
|
||||
: rcl_time_(init_time_point(clock_type))
|
||||
{
|
||||
if (nanoseconds < 0) {
|
||||
throw std::runtime_error("cannot store a negative time point in rclcpp::Time");
|
||||
}
|
||||
|
||||
rcl_time_.nanoseconds = nanoseconds;
|
||||
}
|
||||
|
||||
Time::Time(const Time & rhs) = default;
|
||||
|
||||
Time::Time(Time && rhs) noexcept = default;
|
||||
|
||||
Time::Time(
|
||||
const builtin_interfaces::msg::Time & time_msg,
|
||||
rcl_clock_type_t clock_type)
|
||||
@@ -84,23 +90,11 @@ Time::Time(const rcl_time_point_t & time_point)
|
||||
// noop
|
||||
}
|
||||
|
||||
Time::~Time()
|
||||
{
|
||||
}
|
||||
Time::~Time() = default;
|
||||
|
||||
Time::operator builtin_interfaces::msg::Time() const
|
||||
{
|
||||
builtin_interfaces::msg::Time msg_time;
|
||||
constexpr rcl_time_point_value_t kRemainder = RCL_S_TO_NS(1);
|
||||
const auto result = std::div(rcl_time_.nanoseconds, kRemainder);
|
||||
if (result.rem >= 0) {
|
||||
msg_time.sec = static_cast<std::int32_t>(result.quot);
|
||||
msg_time.nanosec = static_cast<std::uint32_t>(result.rem);
|
||||
} else {
|
||||
msg_time.sec = static_cast<std::int32_t>(result.quot - 1);
|
||||
msg_time.nanosec = static_cast<std::uint32_t>(kRemainder + result.rem);
|
||||
}
|
||||
return msg_time;
|
||||
return convert_rcl_time_to_sec_nanos(rcl_time_.nanoseconds);
|
||||
}
|
||||
|
||||
Time &
|
||||
@@ -113,6 +107,9 @@ Time::operator=(const builtin_interfaces::msg::Time & time_msg)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Time &
|
||||
Time::operator=(Time && rhs) noexcept = default;
|
||||
|
||||
bool
|
||||
Time::operator==(const rclcpp::Time & rhs) const
|
||||
{
|
||||
@@ -256,6 +253,9 @@ Time::operator+=(const rclcpp::Duration & rhs)
|
||||
}
|
||||
|
||||
rcl_time_.nanoseconds += rhs.nanoseconds();
|
||||
if (rcl_time_.nanoseconds < 0) {
|
||||
throw std::runtime_error("cannot store a negative time point in rclcpp::Time");
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -271,6 +271,9 @@ Time::operator-=(const rclcpp::Duration & rhs)
|
||||
}
|
||||
|
||||
rcl_time_.nanoseconds -= rhs.nanoseconds();
|
||||
if (rcl_time_.nanoseconds < 0) {
|
||||
throw std::runtime_error("cannot store a negative time point in rclcpp::Time");
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -281,5 +284,20 @@ Time::max(rcl_clock_type_t clock_type)
|
||||
return Time(std::numeric_limits<int32_t>::max(), 999999999, clock_type);
|
||||
}
|
||||
|
||||
builtin_interfaces::msg::Time
|
||||
convert_rcl_time_to_sec_nanos(const rcl_time_point_value_t & time_point)
|
||||
{
|
||||
builtin_interfaces::msg::Time ret;
|
||||
constexpr rcl_time_point_value_t kRemainder = RCL_S_TO_NS(1);
|
||||
const auto result = std::div(time_point, kRemainder);
|
||||
if (result.rem >= 0) {
|
||||
ret.sec = static_cast<std::int32_t>(result.quot);
|
||||
ret.nanosec = static_cast<std::uint32_t>(result.rem);
|
||||
} else {
|
||||
ret.sec = static_cast<std::int32_t>(result.quot - 1);
|
||||
ret.nanosec = static_cast<std::uint32_t>(kRemainder + result.rem);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace rclcpp
|
||||
|
||||
@@ -288,10 +288,10 @@ public:
|
||||
// Detach the attached node
|
||||
void detachNode()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(node_base_lock_);
|
||||
// destroy_clock_sub() *must* be first here, to ensure that the executor
|
||||
// can't possibly call any of the callbacks as we are cleaning up.
|
||||
destroy_clock_sub();
|
||||
std::lock_guard<std::mutex> guard(node_base_lock_);
|
||||
clocks_state_.disable_ros_time();
|
||||
if (on_set_parameters_callback_) {
|
||||
node_parameters_->remove_on_set_parameters_callback(on_set_parameters_callback_.get());
|
||||
@@ -409,9 +409,14 @@ private:
|
||||
"/clock",
|
||||
qos_,
|
||||
[this](std::shared_ptr<const rosgraph_msgs::msg::Clock> msg) {
|
||||
// We are using node_base_ as an indication if there is a node attached.
|
||||
// Only call the clock_cb if that is the case.
|
||||
if (node_base_ != nullptr) {
|
||||
bool execute_cb = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(node_base_lock_);
|
||||
// We are using node_base_ as an indication if there is a node attached.
|
||||
// Only call the clock_cb if that is the case.
|
||||
execute_cb = node_base_ != nullptr;
|
||||
}
|
||||
if (execute_cb) {
|
||||
clock_cb(msg);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -86,3 +86,82 @@ Waitable::clear_on_ready_callback()
|
||||
"Custom waitables should override clear_on_ready_callback if they "
|
||||
"want to use it and make sure to call it on the waitable destructor.");
|
||||
}
|
||||
|
||||
void
|
||||
Waitable::add_to_wait_set(rcl_wait_set_t * wait_set)
|
||||
{
|
||||
this->add_to_wait_set(*wait_set);
|
||||
}
|
||||
|
||||
void
|
||||
Waitable::add_to_wait_set(rcl_wait_set_t & wait_set)
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#else // !defined(_WIN32)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4996)
|
||||
#endif
|
||||
this->add_to_wait_set(&wait_set);
|
||||
// remove warning suppression
|
||||
#if !defined(_WIN32)
|
||||
# pragma GCC diagnostic pop
|
||||
#else // !defined(_WIN32)
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
Waitable::is_ready(rcl_wait_set_t * wait_set)
|
||||
{
|
||||
const rcl_wait_set_t & const_wait_set_ref = *wait_set;
|
||||
return this->is_ready(const_wait_set_ref);
|
||||
}
|
||||
|
||||
bool
|
||||
Waitable::is_ready(const rcl_wait_set_t & wait_set)
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#else // !defined(_WIN32)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4996)
|
||||
#endif
|
||||
// note this const cast is only required to support a deprecated function
|
||||
return this->is_ready(&const_cast<rcl_wait_set_t &>(wait_set));
|
||||
// remove warning suppression
|
||||
#if !defined(_WIN32)
|
||||
# pragma GCC diagnostic pop
|
||||
#else // !defined(_WIN32)
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
Waitable::execute(std::shared_ptr<void> & data)
|
||||
{
|
||||
const std::shared_ptr<void> & const_data = data;
|
||||
this->execute(const_data);
|
||||
}
|
||||
|
||||
void
|
||||
Waitable::execute(const std::shared_ptr<void> & data)
|
||||
{
|
||||
#if !defined(_WIN32)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#else // !defined(_WIN32)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4996)
|
||||
#endif
|
||||
// note this const cast is only required to support a deprecated function
|
||||
this->execute(const_cast<std::shared_ptr<void> &>(data));
|
||||
// remove warning suppression
|
||||
#if !defined(_WIN32)
|
||||
# pragma GCC diagnostic pop
|
||||
#else // !defined(_WIN32)
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -362,42 +362,3 @@ BENCHMARK_F(PerformanceTestExecutorSimple, spin_until_future_complete)(benchmark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(
|
||||
PerformanceTestExecutorSimple,
|
||||
static_executor_entities_collector_execute)(benchmark::State & st)
|
||||
{
|
||||
rclcpp::executors::StaticExecutorEntitiesCollector::SharedPtr entities_collector_ =
|
||||
std::make_shared<rclcpp::executors::StaticExecutorEntitiesCollector>();
|
||||
entities_collector_->add_node(node->get_node_base_interface());
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
rcl_allocator_t allocator = rcl_get_default_allocator();
|
||||
auto shared_context = node->get_node_base_interface()->get_context();
|
||||
rcl_context_t * context = shared_context->get_rcl_context().get();
|
||||
rcl_ret_t ret = rcl_wait_set_init(&wait_set, 100, 100, 100, 100, 100, 100, context, allocator);
|
||||
if (ret != RCL_RET_OK) {
|
||||
st.SkipWithError(rcutils_get_error_string().str);
|
||||
}
|
||||
RCPPUTILS_SCOPE_EXIT(
|
||||
{
|
||||
rcl_ret_t ret = rcl_wait_set_fini(&wait_set);
|
||||
if (ret != RCL_RET_OK) {
|
||||
st.SkipWithError(rcutils_get_error_string().str);
|
||||
}
|
||||
});
|
||||
|
||||
auto memory_strategy = rclcpp::memory_strategies::create_default_strategy();
|
||||
rclcpp::GuardCondition guard_condition(shared_context);
|
||||
|
||||
entities_collector_->init(&wait_set, memory_strategy);
|
||||
RCPPUTILS_SCOPE_EXIT(entities_collector_->fini());
|
||||
|
||||
reset_heap_counters();
|
||||
|
||||
for (auto _ : st) {
|
||||
(void)_;
|
||||
std::shared_ptr<void> data = entities_collector_->take_data();
|
||||
entities_collector_->execute(data);
|
||||
}
|
||||
}
|
||||
|
||||
3
rclcpp/test/msg/LargeMessage.msg
Normal file
3
rclcpp/test/msg/LargeMessage.msg
Normal file
@@ -0,0 +1,3 @@
|
||||
# A message with a size larger than the default Linux stack size
|
||||
uint8[10485760] data
|
||||
uint64 size
|
||||
@@ -6,6 +6,7 @@ add_definitions(-DTEST_RESOURCES_DIRECTORY="${TEST_RESOURCES_DIRECTORY}")
|
||||
|
||||
rosidl_generate_interfaces(${PROJECT_NAME}_test_msgs
|
||||
../msg/Header.msg
|
||||
../msg/LargeMessage.msg
|
||||
../msg/MessageWithHeader.msg
|
||||
../msg/String.msg
|
||||
DEPENDENCIES builtin_interfaces
|
||||
@@ -30,6 +31,7 @@ endif()
|
||||
ament_add_gtest(
|
||||
test_exceptions
|
||||
exceptions/test_exceptions.cpp)
|
||||
ament_add_test_label(test_exceptions mimick)
|
||||
if(TARGET test_exceptions)
|
||||
target_link_libraries(test_exceptions ${PROJECT_NAME} mimick)
|
||||
endif()
|
||||
@@ -51,9 +53,15 @@ if(TARGET test_any_subscription_callback)
|
||||
target_link_libraries(test_any_subscription_callback ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
ament_add_gtest(test_client test_client.cpp)
|
||||
ament_add_test_label(test_client mimick)
|
||||
if(TARGET test_client)
|
||||
target_link_libraries(test_client ${PROJECT_NAME} mimick ${rcl_interfaces_TARGETS} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
ament_add_gtest(test_clock test_clock.cpp)
|
||||
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_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})
|
||||
@@ -63,6 +71,30 @@ if(TARGET test_create_timer)
|
||||
target_link_libraries(test_create_timer ${PROJECT_NAME})
|
||||
target_include_directories(test_create_timer PRIVATE ./)
|
||||
endif()
|
||||
ament_add_gtest(test_generic_client test_generic_client.cpp)
|
||||
ament_add_test_label(test_generic_client mimick)
|
||||
if(TARGET test_generic_client)
|
||||
target_link_libraries(test_generic_client ${PROJECT_NAME}
|
||||
mimick
|
||||
${rcl_interfaces_TARGETS}
|
||||
rmw::rmw
|
||||
rosidl_runtime_cpp::rosidl_runtime_cpp
|
||||
rosidl_typesupport_cpp::rosidl_typesupport_cpp
|
||||
${test_msgs_TARGETS}
|
||||
)
|
||||
endif()
|
||||
ament_add_gtest(test_client_common test_client_common.cpp)
|
||||
ament_add_test_label(test_client_common mimick)
|
||||
if(TARGET test_client_common)
|
||||
target_link_libraries(test_client_common ${PROJECT_NAME}
|
||||
mimick
|
||||
${rcl_interfaces_TARGETS}
|
||||
rmw::rmw
|
||||
rosidl_runtime_cpp::rosidl_runtime_cpp
|
||||
rosidl_typesupport_cpp::rosidl_typesupport_cpp
|
||||
${test_msgs_TARGETS}
|
||||
)
|
||||
endif()
|
||||
ament_add_gtest(test_create_subscription test_create_subscription.cpp)
|
||||
if(TARGET test_create_subscription)
|
||||
target_link_libraries(test_create_subscription ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
@@ -79,6 +111,7 @@ function(test_add_callback_groups_to_executor_for_rmw_implementation)
|
||||
endfunction()
|
||||
call_for_each_rmw_implementation(test_add_callback_groups_to_executor_for_rmw_implementation)
|
||||
ament_add_gtest(test_expand_topic_or_service_name test_expand_topic_or_service_name.cpp)
|
||||
ament_add_test_label(test_expand_topic_or_service_name mimick)
|
||||
if(TARGET test_expand_topic_or_service_name)
|
||||
target_link_libraries(test_expand_topic_or_service_name ${PROJECT_NAME} mimick rcl::rcl rmw::rmw)
|
||||
endif()
|
||||
@@ -110,6 +143,7 @@ if(TARGET test_intra_process_buffer)
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_loaned_message test_loaned_message.cpp)
|
||||
ament_add_test_label(test_loaned_message mimick)
|
||||
target_link_libraries(test_loaned_message ${PROJECT_NAME} mimick ${test_msgs_TARGETS})
|
||||
|
||||
ament_add_gtest(test_memory_strategy test_memory_strategy.cpp)
|
||||
@@ -119,6 +153,7 @@ ament_add_gtest(test_message_memory_strategy test_message_memory_strategy.cpp)
|
||||
target_link_libraries(test_message_memory_strategy ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
|
||||
ament_add_gtest(test_node test_node.cpp TIMEOUT 240)
|
||||
ament_add_test_label(test_node mimick)
|
||||
if(TARGET test_node)
|
||||
target_link_libraries(test_node ${PROJECT_NAME} mimick rcpputils::rcpputils rmw::rmw ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -130,6 +165,7 @@ if(TARGET test_node_interfaces__get_node_interfaces)
|
||||
endif()
|
||||
ament_add_gtest(test_node_interfaces__node_base
|
||||
node_interfaces/test_node_base.cpp)
|
||||
ament_add_test_label(test_node_interfaces__node_base mimick)
|
||||
if(TARGET test_node_interfaces__node_base)
|
||||
target_link_libraries(test_node_interfaces__node_base ${PROJECT_NAME} mimick rcl::rcl rmw::rmw)
|
||||
endif()
|
||||
@@ -141,6 +177,7 @@ endif()
|
||||
ament_add_gtest(test_node_interfaces__node_graph
|
||||
node_interfaces/test_node_graph.cpp
|
||||
TIMEOUT 120)
|
||||
ament_add_test_label(test_node_interfaces__node_graph mimick)
|
||||
if(TARGET test_node_interfaces__node_graph)
|
||||
target_link_libraries(test_node_interfaces__node_graph ${PROJECT_NAME} mimick rcl::rcl ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -151,21 +188,25 @@ if(TARGET test_node_interfaces__node_interfaces)
|
||||
endif()
|
||||
ament_add_gtest(test_node_interfaces__node_parameters
|
||||
node_interfaces/test_node_parameters.cpp)
|
||||
ament_add_test_label(test_node_interfaces__node_parameters mimick)
|
||||
if(TARGET test_node_interfaces__node_parameters)
|
||||
target_link_libraries(test_node_interfaces__node_parameters ${PROJECT_NAME} mimick rcpputils::rcpputils)
|
||||
endif()
|
||||
ament_add_gtest(test_node_interfaces__node_services
|
||||
node_interfaces/test_node_services.cpp)
|
||||
ament_add_test_label(test_node_interfaces__node_services mimick)
|
||||
if(TARGET test_node_interfaces__node_services)
|
||||
target_link_libraries(test_node_interfaces__node_services ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
ament_add_gtest(test_node_interfaces__node_timers
|
||||
node_interfaces/test_node_timers.cpp)
|
||||
ament_add_test_label(test_node_interfaces__node_timers mimick)
|
||||
if(TARGET test_node_interfaces__node_timers)
|
||||
target_link_libraries(test_node_interfaces__node_timers ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
ament_add_gtest(test_node_interfaces__node_topics
|
||||
node_interfaces/test_node_topics.cpp)
|
||||
ament_add_test_label(test_node_interfaces__node_topics mimick)
|
||||
if(TARGET test_node_interfaces__node_topics)
|
||||
target_link_libraries(test_node_interfaces__node_topics ${PROJECT_NAME} mimick rcl::rcl ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -176,6 +217,7 @@ if(TARGET test_node_interfaces__node_type_descriptions)
|
||||
endif()
|
||||
ament_add_gtest(test_node_interfaces__node_waitables
|
||||
node_interfaces/test_node_waitables.cpp)
|
||||
ament_add_test_label(test_node_interfaces__node_waitables mimick)
|
||||
if(TARGET test_node_interfaces__node_waitables)
|
||||
target_link_libraries(test_node_interfaces__node_waitables ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
@@ -211,10 +253,12 @@ if(TARGET test_node_global_args)
|
||||
target_link_libraries(test_node_global_args ${PROJECT_NAME})
|
||||
endif()
|
||||
ament_add_gtest(test_node_options test_node_options.cpp)
|
||||
ament_add_test_label(test_node_options mimick)
|
||||
if(TARGET test_node_options)
|
||||
target_link_libraries(test_node_options ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
ament_add_gtest(test_init_options test_init_options.cpp)
|
||||
ament_add_test_label(test_init_options mimick)
|
||||
if(TARGET test_init_options)
|
||||
target_link_libraries(test_init_options ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
@@ -243,6 +287,7 @@ if(TARGET test_parameter_map)
|
||||
target_link_libraries(test_parameter_map ${PROJECT_NAME} rcl::rcl rcl_yaml_param_parser::rcl_yaml_param_parser rcutils::rcutils)
|
||||
endif()
|
||||
ament_add_gtest(test_publisher test_publisher.cpp TIMEOUT 120)
|
||||
ament_add_test_label(test_publisher mimick)
|
||||
if(TARGET test_publisher)
|
||||
target_link_libraries(test_publisher ${PROJECT_NAME} mimick rcl::rcl rcutils::rcutils ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -308,6 +353,7 @@ function(test_qos_event_for_rmw_implementation)
|
||||
ament_add_gmock(test_qos_event${target_suffix} test_qos_event.cpp
|
||||
ENV ${rmw_implementation_env_var}
|
||||
)
|
||||
ament_add_test_label(test_qos_event${target_suffix} mimick)
|
||||
if(TARGET test_qos_event${target_suffix})
|
||||
target_link_libraries(test_qos_event${target_suffix} ${PROJECT_NAME} mimick rcutils::rcutils rmw::rmw ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -335,15 +381,18 @@ if(TARGET test_serialized_message)
|
||||
target_link_libraries(test_serialized_message ${PROJECT_NAME} rcpputils::rcpputils ${test_msgs_TARGETS})
|
||||
endif()
|
||||
ament_add_gtest(test_service test_service.cpp)
|
||||
ament_add_test_label(test_service mimick)
|
||||
if(TARGET test_service)
|
||||
target_link_libraries(test_service ${PROJECT_NAME} mimick ${rcl_interfaces_TARGES} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
ament_add_gmock(test_service_introspection test_service_introspection.cpp)
|
||||
ament_add_test_label(test_service_introspection mimick)
|
||||
if(TARGET test_service_introspection)
|
||||
target_link_libraries(test_service_introspection ${PROJECT_NAME} mimick ${service_msgs_TARGETS} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
# Creating and destroying nodes is slow with Connext, so this needs larger timeout.
|
||||
ament_add_gtest(test_subscription test_subscription.cpp TIMEOUT 120)
|
||||
ament_add_test_label(test_subscription mimick)
|
||||
if(TARGET test_subscription)
|
||||
target_link_libraries(test_subscription ${PROJECT_NAME} mimick ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -394,6 +443,7 @@ endif()
|
||||
|
||||
ament_add_gtest(test_timer test_timer.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}")
|
||||
ament_add_test_label(test_timer mimick)
|
||||
if(TARGET test_timer)
|
||||
target_link_libraries(test_timer ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
@@ -412,6 +462,7 @@ endif()
|
||||
|
||||
ament_add_gtest(test_utilities test_utilities.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}")
|
||||
ament_add_test_label(test_utilities mimick)
|
||||
if(TARGET test_utilities)
|
||||
target_link_libraries(test_utilities ${PROJECT_NAME} mimick rcl::rcl)
|
||||
endif()
|
||||
@@ -441,8 +492,36 @@ if(TARGET test_executors)
|
||||
target_link_libraries(test_executors ${PROJECT_NAME} rcl::rcl ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(
|
||||
test_executors_timer_cancel_behavior
|
||||
executors/test_executors_timer_cancel_behavior.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 180)
|
||||
if(TARGET test_executors)
|
||||
target_link_libraries(test_executors_timer_cancel_behavior ${PROJECT_NAME} ${rosgraph_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(
|
||||
test_executors_callback_group_behavior
|
||||
executors/test_executors_callback_group_behavior.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 180)
|
||||
if(TARGET test_executors)
|
||||
target_link_libraries(test_executors_callback_group_behavior ${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(
|
||||
test_executors_intraprocess
|
||||
executors/test_executors_intraprocess.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 180)
|
||||
if(TARGET test_executors)
|
||||
target_link_libraries(test_executors_intraprocess ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_static_single_threaded_executor executors/test_static_single_threaded_executor.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}")
|
||||
ament_add_test_label(test_static_single_threaded_executor mimick)
|
||||
if(TARGET test_static_single_threaded_executor)
|
||||
target_link_libraries(test_static_single_threaded_executor ${PROJECT_NAME} mimick ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -453,12 +532,6 @@ if(TARGET test_multi_threaded_executor)
|
||||
target_link_libraries(test_multi_threaded_executor ${PROJECT_NAME})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_static_executor_entities_collector executors/test_static_executor_entities_collector.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}" TIMEOUT 120)
|
||||
if(TARGET test_static_executor_entities_collector)
|
||||
target_link_libraries(test_static_executor_entities_collector ${PROJECT_NAME} mimick rcpputils::rcpputils ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_entities_collector executors/test_entities_collector.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}" TIMEOUT 120)
|
||||
if(TARGET test_entities_collector)
|
||||
@@ -467,6 +540,7 @@ endif()
|
||||
|
||||
ament_add_gtest(test_executor_notify_waitable executors/test_executor_notify_waitable.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}" TIMEOUT 120)
|
||||
ament_add_test_label(test_executor_notify_waitable mimick)
|
||||
if(TARGET test_executor_notify_waitable)
|
||||
target_link_libraries(test_executor_notify_waitable ${PROJECT_NAME} mimick rcpputils::rcpputils)
|
||||
endif()
|
||||
@@ -484,6 +558,7 @@ endif()
|
||||
|
||||
ament_add_gtest(test_guard_condition test_guard_condition.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}")
|
||||
ament_add_test_label(test_guard_condition mimick)
|
||||
if(TARGET test_guard_condition)
|
||||
target_link_libraries(test_guard_condition ${PROJECT_NAME} mimick)
|
||||
endif()
|
||||
@@ -517,6 +592,7 @@ if(TARGET test_dynamic_storage)
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_storage_policy_common wait_set_policies/test_storage_policy_common.cpp)
|
||||
ament_add_test_label(test_storage_policy_common mimick)
|
||||
if(TARGET test_storage_policy_common)
|
||||
target_link_libraries(test_storage_policy_common ${PROJECT_NAME} mimick ${test_msgs_TARGETS})
|
||||
endif()
|
||||
@@ -531,6 +607,11 @@ if(TARGET test_thread_safe_synchronization)
|
||||
target_link_libraries(test_thread_safe_synchronization ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_intra_process_waitable waitables/test_intra_process_waitable.cpp)
|
||||
if(TARGET test_intra_process_waitable)
|
||||
target_link_libraries(test_intra_process_waitable ${PROJECT_NAME} ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_rosout_qos test_rosout_qos.cpp)
|
||||
if(TARGET test_rosout_qos)
|
||||
target_link_libraries(test_rosout_qos ${PROJECT_NAME} rcl::rcl rmw::rmw)
|
||||
@@ -544,11 +625,13 @@ endif()
|
||||
ament_add_gtest(test_executor test_executor.cpp
|
||||
APPEND_LIBRARY_DIRS "${append_library_dirs}"
|
||||
TIMEOUT 120)
|
||||
ament_add_test_label(test_executor mimick)
|
||||
if(TARGET test_executor)
|
||||
target_link_libraries(test_executor ${PROJECT_NAME} mimick)
|
||||
endif()
|
||||
|
||||
ament_add_gtest(test_graph_listener test_graph_listener.cpp)
|
||||
ament_add_test_label(test_graph_listener mimick)
|
||||
if(TARGET test_graph_listener)
|
||||
target_link_libraries(test_graph_listener ${PROJECT_NAME} mimick)
|
||||
endif()
|
||||
@@ -560,6 +643,7 @@ function(test_subscription_content_filter_for_rmw_implementation)
|
||||
ENV ${rmw_implementation_env_var}
|
||||
TIMEOUT 120
|
||||
)
|
||||
ament_add_test_label(test_subscription_content_filter${target_suffix} mimick)
|
||||
if(TARGET test_subscription_content_filter${target_suffix})
|
||||
target_link_libraries(test_subscription_content_filter${target_suffix} ${PROJECT_NAME} mimick ${test_msgs_TARGETS})
|
||||
endif()
|
||||
|
||||
70
rclcpp/test/rclcpp/executors/executor_types.hpp
Normal file
70
rclcpp/test/rclcpp/executors/executor_types.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2017 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__EXECUTORS__EXECUTOR_TYPES_HPP_
|
||||
#define RCLCPP__EXECUTORS__EXECUTOR_TYPES_HPP_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "rclcpp/experimental/executors/events_executor/events_executor.hpp"
|
||||
#include "rclcpp/executors/single_threaded_executor.hpp"
|
||||
#include "rclcpp/executors/static_single_threaded_executor.hpp"
|
||||
#include "rclcpp/executors/multi_threaded_executor.hpp"
|
||||
|
||||
using ExecutorTypes =
|
||||
::testing::Types<
|
||||
rclcpp::executors::SingleThreadedExecutor,
|
||||
rclcpp::executors::MultiThreadedExecutor,
|
||||
rclcpp::executors::StaticSingleThreadedExecutor,
|
||||
rclcpp::experimental::executors::EventsExecutor>;
|
||||
|
||||
class ExecutorTypeNames
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
static std::string GetName(int idx)
|
||||
{
|
||||
(void)idx;
|
||||
if (std::is_same<T, rclcpp::executors::SingleThreadedExecutor>()) {
|
||||
return "SingleThreadedExecutor";
|
||||
}
|
||||
|
||||
if (std::is_same<T, rclcpp::executors::MultiThreadedExecutor>()) {
|
||||
return "MultiThreadedExecutor";
|
||||
}
|
||||
|
||||
if (std::is_same<T, rclcpp::executors::StaticSingleThreadedExecutor>()) {
|
||||
return "StaticSingleThreadedExecutor";
|
||||
}
|
||||
|
||||
if (std::is_same<T, rclcpp::experimental::executors::EventsExecutor>()) {
|
||||
return "EventsExecutor";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// StaticSingleThreadedExecutor is not included in these tests for now, due to:
|
||||
// https://github.com/ros2/rclcpp/issues/1219
|
||||
using StandardExecutors =
|
||||
::testing::Types<
|
||||
rclcpp::executors::SingleThreadedExecutor,
|
||||
rclcpp::executors::MultiThreadedExecutor,
|
||||
rclcpp::experimental::executors::EventsExecutor>;
|
||||
|
||||
#endif // RCLCPP__EXECUTORS__EXECUTOR_TYPES_HPP_
|
||||
@@ -68,6 +68,7 @@ TEST(TestEventsQueue, SimpleQueueTest)
|
||||
// Lets push an event into the queue and get it back
|
||||
rclcpp::experimental::executors::ExecutorEvent push_event = {
|
||||
simple_queue.get(),
|
||||
nullptr,
|
||||
99,
|
||||
rclcpp::experimental::executors::ExecutorEventType::SUBSCRIPTION_EVENT,
|
||||
1};
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
/**
|
||||
* This test checks all implementations of rclcpp::executor to check they pass they basic API
|
||||
* tests. Anything specific to any executor in particular should go in a separate test file.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -36,9 +36,12 @@
|
||||
#include "rclcpp/duration.hpp"
|
||||
#include "rclcpp/guard_condition.hpp"
|
||||
#include "rclcpp/rclcpp.hpp"
|
||||
#include "rclcpp/time_source.hpp"
|
||||
|
||||
#include "test_msgs/msg/empty.hpp"
|
||||
|
||||
#include "./executor_types.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
template<typename T>
|
||||
@@ -79,56 +82,11 @@ public:
|
||||
int callback_count;
|
||||
};
|
||||
|
||||
// spin_all and spin_some are not implemented correctly in StaticSingleThreadedExecutor, see:
|
||||
// https://github.com/ros2/rclcpp/issues/1219 for tracking
|
||||
template<typename T>
|
||||
class TestExecutorsStable : public TestExecutors<T> {};
|
||||
|
||||
using ExecutorTypes =
|
||||
::testing::Types<
|
||||
rclcpp::executors::SingleThreadedExecutor,
|
||||
rclcpp::executors::MultiThreadedExecutor,
|
||||
rclcpp::executors::StaticSingleThreadedExecutor,
|
||||
rclcpp::experimental::executors::EventsExecutor>;
|
||||
|
||||
class ExecutorTypeNames
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
static std::string GetName(int idx)
|
||||
{
|
||||
(void)idx;
|
||||
if (std::is_same<T, rclcpp::executors::SingleThreadedExecutor>()) {
|
||||
return "SingleThreadedExecutor";
|
||||
}
|
||||
|
||||
if (std::is_same<T, rclcpp::executors::MultiThreadedExecutor>()) {
|
||||
return "MultiThreadedExecutor";
|
||||
}
|
||||
|
||||
if (std::is_same<T, rclcpp::executors::StaticSingleThreadedExecutor>()) {
|
||||
return "StaticSingleThreadedExecutor";
|
||||
}
|
||||
|
||||
if (std::is_same<T, rclcpp::experimental::executors::EventsExecutor>()) {
|
||||
return "EventsExecutor";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// TYPED_TEST_SUITE is deprecated as of gtest 1.9, use TYPED_TEST_SUITE when gtest dependency
|
||||
// is updated.
|
||||
TYPED_TEST_SUITE(TestExecutors, ExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
// StaticSingleThreadedExecutor is not included in these tests for now, due to:
|
||||
// https://github.com/ros2/rclcpp/issues/1219
|
||||
using StandardExecutors =
|
||||
::testing::Types<
|
||||
rclcpp::executors::SingleThreadedExecutor,
|
||||
rclcpp::executors::MultiThreadedExecutor,
|
||||
rclcpp::experimental::executors::EventsExecutor>;
|
||||
TYPED_TEST_SUITE(TestExecutorsStable, StandardExecutors, ExecutorTypeNames);
|
||||
|
||||
// Make sure that executors detach from nodes when destructing
|
||||
@@ -146,10 +104,7 @@ TYPED_TEST(TestExecutors, detachOnDestruction)
|
||||
}
|
||||
|
||||
// Make sure that the executor can automatically remove expired nodes correctly
|
||||
// Currently fails for StaticSingleThreadedExecutor so it is being skipped, see:
|
||||
// https://github.com/ros2/rclcpp/issues/1231
|
||||
TYPED_TEST(TestExecutorsStable, addTemporaryNode)
|
||||
{
|
||||
TYPED_TEST(TestExecutors, addTemporaryNode) {
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
@@ -217,16 +172,19 @@ TYPED_TEST(TestExecutors, spinWhileAlreadySpinning)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
|
||||
std::atomic_bool timer_completed = false;
|
||||
auto timer = this->node->create_wall_timer(
|
||||
1ms, [&]() {
|
||||
timer_completed.store(true);
|
||||
});
|
||||
|
||||
executor.add_node(this->node);
|
||||
|
||||
bool timer_completed = false;
|
||||
auto timer = this->node->create_wall_timer(1ms, [&]() {timer_completed = true;});
|
||||
|
||||
std::thread spinner([&]() {executor.spin();});
|
||||
// Sleep for a short time to verify executor.spin() is going, and didn't throw.
|
||||
|
||||
// Sleep for a short time to verify executor.spin() is going, and didn't throw.
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while (!timer_completed && (std::chrono::steady_clock::now() - start) < 10s) {
|
||||
while (!timer_completed.load() && (std::chrono::steady_clock::now() - start) < 10s) {
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
@@ -379,21 +337,34 @@ public:
|
||||
TestWaitable() = default;
|
||||
|
||||
void
|
||||
add_to_wait_set(rcl_wait_set_t * wait_set) override
|
||||
add_to_wait_set(rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
rclcpp::detail::add_guard_condition_to_rcl_wait_set(*wait_set, gc_);
|
||||
if (trigger_count_ > 0) {
|
||||
// Keep the gc triggered until the trigger count is reduced back to zero.
|
||||
// This is necessary if trigger() results in the wait set waking, but not
|
||||
// executing this waitable, in which case it needs to be re-triggered.
|
||||
gc_.trigger();
|
||||
}
|
||||
rclcpp::detail::add_guard_condition_to_rcl_wait_set(wait_set, gc_);
|
||||
}
|
||||
|
||||
void trigger()
|
||||
{
|
||||
trigger_count_++;
|
||||
gc_.trigger();
|
||||
}
|
||||
|
||||
bool
|
||||
is_ready(rcl_wait_set_t * wait_set) override
|
||||
is_ready(const rcl_wait_set_t & wait_set) override
|
||||
{
|
||||
(void)wait_set;
|
||||
return true;
|
||||
is_ready_count_++;
|
||||
for (size_t i = 0; i < wait_set.size_of_guard_conditions; ++i) {
|
||||
auto rcl_guard_condition = wait_set.guard_conditions[i];
|
||||
if (&gc_.get_rcl_guard_condition() == rcl_guard_condition) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
@@ -410,11 +381,24 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) override
|
||||
execute(const std::shared_ptr<void> &) override
|
||||
{
|
||||
(void) data;
|
||||
trigger_count_--;
|
||||
count_++;
|
||||
std::this_thread::sleep_for(3ms);
|
||||
if (nullptr != on_execute_callback_) {
|
||||
on_execute_callback_();
|
||||
} else {
|
||||
// TODO(wjwwood): I don't know why this was here, but probably it should
|
||||
// not be there, or test cases where that is important should use the
|
||||
// on_execute_callback?
|
||||
std::this_thread::sleep_for(3ms);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
set_on_execute_callback(std::function<void()> on_execute_callback)
|
||||
{
|
||||
on_execute_callback_ = on_execute_callback;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -436,14 +420,23 @@ public:
|
||||
get_number_of_ready_guard_conditions() override {return 1;}
|
||||
|
||||
size_t
|
||||
get_count()
|
||||
get_count() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
|
||||
size_t
|
||||
get_is_ready_call_count() const
|
||||
{
|
||||
return is_ready_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t count_ = 0;
|
||||
std::atomic<size_t> trigger_count_ = 0;
|
||||
std::atomic<size_t> is_ready_count_ = 0;
|
||||
std::atomic<size_t> count_ = 0;
|
||||
rclcpp::GuardCondition gc_;
|
||||
std::function<void()> on_execute_callback_ = nullptr;
|
||||
};
|
||||
|
||||
TYPED_TEST(TestExecutors, spinAll)
|
||||
@@ -488,45 +481,182 @@ TYPED_TEST(TestExecutors, spinAll)
|
||||
spinner.join();
|
||||
}
|
||||
|
||||
TYPED_TEST(TestExecutors, spinSome)
|
||||
// Helper function to convert chrono durations into a scalar that GoogleTest
|
||||
// can more easily compare and print.
|
||||
template<typename DurationT>
|
||||
auto
|
||||
to_nanoseconds_helper(DurationT duration)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
|
||||
}
|
||||
|
||||
// The purpose of this test is to check that the ExecutorT.spin_some() method:
|
||||
// - 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)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
auto waitable_interfaces = this->node->get_node_waitables_interface();
|
||||
auto my_waitable = std::make_shared<TestWaitable>();
|
||||
waitable_interfaces->add_waitable(my_waitable, nullptr);
|
||||
executor.add_node(this->node);
|
||||
|
||||
// Long timeout, doesn't block test from finishing because spin_some should exit after the
|
||||
// first one completes.
|
||||
bool spin_exited = false;
|
||||
std::thread spinner([&spin_exited, &executor, this]() {
|
||||
executor.spin_some(1s);
|
||||
executor.remove_node(this->node, true);
|
||||
spin_exited = true;
|
||||
});
|
||||
// Use an isolated callback group to avoid interference from any housekeeping
|
||||
// items that may be in the default callback group of the node.
|
||||
constexpr bool automatically_add_to_executor_with_node = false;
|
||||
auto isolated_callback_group = this->node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
automatically_add_to_executor_with_node);
|
||||
|
||||
// Do some work until sufficient calls to the waitable occur, but keep going until either
|
||||
// count becomes too large, spin exits, or the 1 second timeout completes.
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while (
|
||||
my_waitable->get_count() <= 1 &&
|
||||
!spin_exited &&
|
||||
(std::chrono::steady_clock::now() - start < 1s))
|
||||
// Check that spin_some() returns quickly when there is no work to be done.
|
||||
// This can be a false positive if there is somehow some work for the executor
|
||||
// to do that has not been considered, but the isolated callback group should
|
||||
// avoid that.
|
||||
{
|
||||
my_waitable->trigger();
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
// The count of "execute" depends on whether the executor starts spinning before (1) or after (0)
|
||||
// the first iteration of the while loop
|
||||
EXPECT_LE(1u, my_waitable->get_count());
|
||||
waitable_interfaces->remove_waitable(my_waitable, nullptr);
|
||||
EXPECT_TRUE(spin_exited);
|
||||
// Cancel if it hasn't exited already.
|
||||
executor.cancel();
|
||||
ExecutorType executor;
|
||||
executor.add_callback_group(isolated_callback_group, this->node->get_node_base_interface());
|
||||
|
||||
spinner.join();
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
// spin_some with some non-trival "max_duration" and check that it does not
|
||||
// take anywhere near that long to execute.
|
||||
constexpr auto max_duration = 10s;
|
||||
executor.spin_some(max_duration);
|
||||
EXPECT_LT(
|
||||
to_nanoseconds_helper(std::chrono::steady_clock::now() - start),
|
||||
to_nanoseconds_helper(max_duration / 2))
|
||||
<< "spin_some() took a long time to execute when it should have done "
|
||||
<< "nothing and should not have blocked either, but this could be a "
|
||||
<< "false negative if the computer is really slow";
|
||||
}
|
||||
|
||||
// Check that having one thing ready gets executed by spin_some().
|
||||
auto waitable_interfaces = this->node->get_node_waitables_interface();
|
||||
auto my_waitable1 = std::make_shared<TestWaitable>();
|
||||
waitable_interfaces->add_waitable(my_waitable1, isolated_callback_group);
|
||||
{
|
||||
ExecutorType executor;
|
||||
executor.add_callback_group(isolated_callback_group, this->node->get_node_base_interface());
|
||||
|
||||
my_waitable1->trigger();
|
||||
|
||||
// The long duration should not matter, as executing the waitable is
|
||||
// non-blocking, and spin_some() should exit after completing the available
|
||||
// work.
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
constexpr auto max_duration = 10s;
|
||||
executor.spin_some(max_duration);
|
||||
EXPECT_LT(
|
||||
to_nanoseconds_helper(std::chrono::steady_clock::now() - start),
|
||||
to_nanoseconds_helper(max_duration / 2))
|
||||
<< "spin_some() took a long time to execute when it should have very "
|
||||
<< "little to do and should not have blocked either, but this could be a "
|
||||
<< "false negative if the computer is really slow";
|
||||
|
||||
EXPECT_EQ(my_waitable1->get_count(), 1u)
|
||||
<< "spin_some() failed to execute a waitable that was triggered";
|
||||
}
|
||||
|
||||
// Check that multiple things being ready are executed by spin_some().
|
||||
auto my_waitable2 = std::make_shared<TestWaitable>();
|
||||
waitable_interfaces->add_waitable(my_waitable2, isolated_callback_group);
|
||||
{
|
||||
ExecutorType executor;
|
||||
executor.add_callback_group(isolated_callback_group, this->node->get_node_base_interface());
|
||||
|
||||
const size_t original_my_waitable1_count = my_waitable1->get_count();
|
||||
my_waitable1->trigger();
|
||||
my_waitable2->trigger();
|
||||
|
||||
// The long duration should not matter, as executing the waitable is
|
||||
// non-blocking, and spin_some() should exit after completing the available
|
||||
// work.
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
constexpr auto max_duration = 10s;
|
||||
executor.spin_some(max_duration);
|
||||
EXPECT_LT(
|
||||
to_nanoseconds_helper(std::chrono::steady_clock::now() - start),
|
||||
to_nanoseconds_helper(max_duration / 2))
|
||||
<< "spin_some() took a long time to execute when it should have very "
|
||||
<< "little to do and should not have blocked either, but this could be a "
|
||||
<< "false negative if the computer is really slow";
|
||||
|
||||
EXPECT_EQ(my_waitable1->get_count(), original_my_waitable1_count + 1)
|
||||
<< "spin_some() failed to execute a waitable that was triggered";
|
||||
EXPECT_EQ(my_waitable2->get_count(), 1u)
|
||||
<< "spin_some() failed to execute a waitable that was triggered";
|
||||
}
|
||||
}
|
||||
|
||||
// The purpose of this test is to check that the ExecutorT.spin_some() method:
|
||||
// - does not continue executing after max_duration has elapsed
|
||||
TYPED_TEST(TestExecutors, spin_some_max_duration)
|
||||
{
|
||||
using ExecutorType = TypeParam;
|
||||
|
||||
// TODO(wjwwood): The `StaticSingleThreadedExecutor`
|
||||
// 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
|
||||
if (
|
||||
std::is_same<ExecutorType, rclcpp::executors::StaticSingleThreadedExecutor>())
|
||||
{
|
||||
GTEST_SKIP();
|
||||
}
|
||||
|
||||
// Use an isolated callback group to avoid interference from any housekeeping
|
||||
// items that may be in the default callback group of the node.
|
||||
constexpr bool automatically_add_to_executor_with_node = false;
|
||||
auto isolated_callback_group = this->node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
automatically_add_to_executor_with_node);
|
||||
|
||||
// Set up a situation with two waitables that take time to execute, such that
|
||||
// the time it takes to execute two waitables far exceeds the max_duration
|
||||
// given to spin_some(), which should result in spin_some() starting to
|
||||
// execute one of them, have the max duration elapse, finish executing one
|
||||
// of them, then returning before starting on the second.
|
||||
constexpr auto max_duration = 100ms; // relatively short because we expect to exceed it
|
||||
constexpr auto waitable_callback_duration = max_duration * 2;
|
||||
auto long_running_callback = [&waitable_callback_duration]() {
|
||||
std::this_thread::sleep_for(waitable_callback_duration);
|
||||
};
|
||||
|
||||
auto waitable_interfaces = this->node->get_node_waitables_interface();
|
||||
|
||||
auto my_waitable1 = std::make_shared<TestWaitable>();
|
||||
my_waitable1->set_on_execute_callback(long_running_callback);
|
||||
waitable_interfaces->add_waitable(my_waitable1, isolated_callback_group);
|
||||
|
||||
auto my_waitable2 = std::make_shared<TestWaitable>();
|
||||
my_waitable2->set_on_execute_callback(long_running_callback);
|
||||
waitable_interfaces->add_waitable(my_waitable2, isolated_callback_group);
|
||||
|
||||
my_waitable1->trigger();
|
||||
my_waitable2->trigger();
|
||||
|
||||
ExecutorType executor;
|
||||
executor.add_callback_group(isolated_callback_group, this->node->get_node_base_interface());
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
// spin_some and check that it does not take longer than two of waitable_callback_duration,
|
||||
// nor significantly less than a single waitable_callback_duration.
|
||||
executor.spin_some(max_duration);
|
||||
auto spin_some_run_time = std::chrono::steady_clock::now() - start;
|
||||
EXPECT_GT(
|
||||
to_nanoseconds_helper(spin_some_run_time),
|
||||
to_nanoseconds_helper(waitable_callback_duration / 2))
|
||||
<< "spin_some() took less than half the expected time to execute a single "
|
||||
<< "waitable, which implies it did not actually execute one when it was "
|
||||
<< "expected to";
|
||||
EXPECT_LT(
|
||||
to_nanoseconds_helper(spin_some_run_time),
|
||||
to_nanoseconds_helper(waitable_callback_duration * 2))
|
||||
<< "spin_some() took longer than expected to execute by a significant margin, but "
|
||||
<< "this could be a false positive on a very slow computer";
|
||||
|
||||
// check that exactly one of the waitables were executed (do not depend on a specific order)
|
||||
size_t number_of_waitables_executed = my_waitable1->get_count() + my_waitable2->get_count();
|
||||
EXPECT_EQ(number_of_waitables_executed, 1u)
|
||||
<< "expected exactly one of the two waitables to be executed, but "
|
||||
<< "my_waitable1->get_count(): " << my_waitable1->get_count() << " and "
|
||||
<< "my_waitable2->get_count(): " << my_waitable2->get_count();
|
||||
}
|
||||
|
||||
// Check spin_node_until_future_complete with node base pointer
|
||||
@@ -718,103 +848,184 @@ TEST(TestExecutors, testSpinUntilFutureCompleteNodePtr)
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
// Check spin functions with non default context
|
||||
TEST(TestExecutors, testSpinWithNonDefaultContext)
|
||||
{
|
||||
auto non_default_context = std::make_shared<rclcpp::Context>();
|
||||
non_default_context->init(0, nullptr);
|
||||
|
||||
{
|
||||
auto node =
|
||||
std::make_unique<rclcpp::Node>("node", rclcpp::NodeOptions().context(non_default_context));
|
||||
|
||||
EXPECT_NO_THROW(rclcpp::spin_some(node->get_node_base_interface()));
|
||||
|
||||
EXPECT_NO_THROW(rclcpp::spin_all(node->get_node_base_interface(), 1s));
|
||||
|
||||
auto check_spin_until_future_complete = [&]() {
|
||||
std::promise<bool> promise;
|
||||
std::future<bool> future = promise.get_future();
|
||||
promise.set_value(true);
|
||||
|
||||
auto shared_future = future.share();
|
||||
auto ret = rclcpp::spin_until_future_complete(
|
||||
node->get_node_base_interface(), shared_future, 1s);
|
||||
EXPECT_EQ(rclcpp::FutureReturnCode::SUCCESS, ret);
|
||||
};
|
||||
EXPECT_NO_THROW(check_spin_until_future_complete());
|
||||
}
|
||||
|
||||
rclcpp::shutdown(non_default_context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class TestIntraprocessExecutors : public ::testing::Test
|
||||
class TestBusyWaiting : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
static void SetUpTestCase()
|
||||
void SetUp() override
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
}
|
||||
|
||||
static void TearDownTestCase()
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void SetUp()
|
||||
{
|
||||
const auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
std::stringstream test_name;
|
||||
test_name << test_info->test_case_name() << "_" << test_info->name();
|
||||
node = std::make_shared<rclcpp::Node>("node", test_name.str());
|
||||
callback_group = node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive,
|
||||
/* automatically_add_to_executor_with_node =*/ false);
|
||||
|
||||
callback_count = 0u;
|
||||
auto waitable_interfaces = node->get_node_waitables_interface();
|
||||
waitable = std::make_shared<TestWaitable>();
|
||||
waitable_interfaces->add_waitable(waitable, callback_group);
|
||||
|
||||
const std::string topic_name = std::string("topic_") + test_name.str();
|
||||
|
||||
rclcpp::PublisherOptions po;
|
||||
po.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
publisher = node->create_publisher<test_msgs::msg::Empty>(topic_name, rclcpp::QoS(1), po);
|
||||
|
||||
auto callback = [this](test_msgs::msg::Empty::ConstSharedPtr) {
|
||||
this->callback_count.fetch_add(1u);
|
||||
};
|
||||
|
||||
rclcpp::SubscriptionOptions so;
|
||||
so.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
subscription =
|
||||
node->create_subscription<test_msgs::msg::Empty>(
|
||||
topic_name, rclcpp::QoS(kNumMessages), std::move(callback), so);
|
||||
executor = std::make_shared<T>();
|
||||
executor->add_callback_group(callback_group, node->get_node_base_interface());
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
void TearDown() override
|
||||
{
|
||||
publisher.reset();
|
||||
subscription.reset();
|
||||
node.reset();
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
const size_t kNumMessages = 100;
|
||||
void
|
||||
set_up_and_trigger_waitable(std::function<void()> extra_callback = nullptr)
|
||||
{
|
||||
this->has_executed = false;
|
||||
this->waitable->set_on_execute_callback([this, extra_callback]() {
|
||||
if (!this->has_executed) {
|
||||
// trigger once to see if the second trigger is handled or not
|
||||
// this follow up trigger simulates new entities becoming ready while
|
||||
// the executor is executing something else, e.g. subscription got data
|
||||
// or a timer expired, etc.
|
||||
// spin_some would not handle this second trigger, since it collects
|
||||
// work only once, whereas spin_all should handle it since it
|
||||
// collects work multiple times
|
||||
this->waitable->trigger();
|
||||
this->has_executed = true;
|
||||
}
|
||||
if (nullptr != extra_callback) {
|
||||
extra_callback();
|
||||
}
|
||||
});
|
||||
this->waitable->trigger();
|
||||
}
|
||||
|
||||
void
|
||||
check_for_busy_waits(std::chrono::steady_clock::time_point start_time)
|
||||
{
|
||||
// rough time based check, since the work to be done was very small it
|
||||
// should be safe to check that we didn't use more than half the
|
||||
// max duration, which itself is much larger than necessary
|
||||
// however, it could still produce a false-positive
|
||||
EXPECT_LT(
|
||||
std::chrono::steady_clock::now() - start_time,
|
||||
max_duration / 2)
|
||||
<< "executor took a long time to execute when it should have done "
|
||||
<< "nothing and should not have blocked either, but this could be a "
|
||||
<< "false negative if the computer is really slow";
|
||||
|
||||
// this check is making some assumptions about the implementation of the
|
||||
// executors, but it should be safe to say that a busy wait may result in
|
||||
// hundreds or thousands of calls to is_ready(), but "normal" executor
|
||||
// behavior should be within an order of magnitude of the number of
|
||||
// times that the waitable was executed
|
||||
ASSERT_LT(waitable->get_is_ready_call_count(), 10u * this->waitable->get_count());
|
||||
}
|
||||
|
||||
static constexpr auto max_duration = 10s;
|
||||
|
||||
rclcpp::Node::SharedPtr node;
|
||||
rclcpp::Publisher<test_msgs::msg::Empty>::SharedPtr publisher;
|
||||
rclcpp::Subscription<test_msgs::msg::Empty>::SharedPtr subscription;
|
||||
std::atomic_size_t callback_count;
|
||||
rclcpp::CallbackGroup::SharedPtr callback_group;
|
||||
std::shared_ptr<TestWaitable> waitable;
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
std::shared_ptr<T> executor;
|
||||
bool has_executed;
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(TestIntraprocessExecutors, ExecutorTypes, ExecutorTypeNames);
|
||||
TYPED_TEST_SUITE(TestBusyWaiting, ExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
TYPED_TEST(TestIntraprocessExecutors, testIntraprocessRetrigger) {
|
||||
// This tests that executors will continue to service intraprocess subscriptions in the case
|
||||
// that publishers aren't continuing to publish.
|
||||
// This was previously broken in that intraprocess guard conditions were only triggered on
|
||||
// publish and the test was added to prevent future regressions.
|
||||
static constexpr size_t kNumMessages = 100;
|
||||
TYPED_TEST(TestBusyWaiting, test_spin_all)
|
||||
{
|
||||
this->set_up_and_trigger_waitable();
|
||||
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
executor.add_node(this->node);
|
||||
|
||||
EXPECT_EQ(0u, this->callback_count.load());
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
|
||||
// Wait for up to 5 seconds for the first message to come available.
|
||||
const std::chrono::milliseconds sleep_per_loop(10);
|
||||
int loops = 0;
|
||||
while (1u != this->callback_count.load() && loops < 500) {
|
||||
rclcpp::sleep_for(sleep_per_loop);
|
||||
executor.spin_some();
|
||||
loops++;
|
||||
}
|
||||
EXPECT_EQ(1u, this->callback_count.load());
|
||||
|
||||
// reset counter
|
||||
this->callback_count.store(0u);
|
||||
|
||||
for (size_t ii = 0; ii < kNumMessages; ++ii) {
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
}
|
||||
|
||||
// Fire a timer every 10ms up to 5 seconds waiting for subscriptions to be read.
|
||||
loops = 0;
|
||||
auto timer = this->node->create_wall_timer(
|
||||
std::chrono::milliseconds(10), [this, &executor, &loops]() {
|
||||
loops++;
|
||||
if (kNumMessages == this->callback_count.load() || loops == 500) {
|
||||
executor.cancel();
|
||||
}
|
||||
});
|
||||
executor.spin();
|
||||
EXPECT_EQ(kNumMessages, this->callback_count.load());
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
this->executor->spin_all(this->max_duration);
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the initial trigger, and the follow up from in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 2u);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin_some)
|
||||
{
|
||||
this->set_up_and_trigger_waitable();
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
this->executor->spin_some(this->max_duration);
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the inital trigger, but not the follow up in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 1u);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestBusyWaiting, test_spin)
|
||||
{
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
bool first_check_passed = false;
|
||||
|
||||
this->set_up_and_trigger_waitable([&cv, &cv_m, &first_check_passed]() {
|
||||
cv.notify_one();
|
||||
if (!first_check_passed) {
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 1s, [&]() {return first_check_passed;});
|
||||
}
|
||||
});
|
||||
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
std::thread t([this]() {
|
||||
this->executor->spin();
|
||||
});
|
||||
|
||||
// wait until thread has started (first execute of waitable)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 10s);
|
||||
}
|
||||
EXPECT_GT(this->waitable->get_count(), 0u);
|
||||
|
||||
first_check_passed = true;
|
||||
cv.notify_one();
|
||||
|
||||
// wait until the executor has finished (second execute of waitable)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cv_m);
|
||||
cv.wait_for(lk, 10s);
|
||||
}
|
||||
EXPECT_EQ(this->waitable->get_count(), 2u);
|
||||
|
||||
this->executor->cancel();
|
||||
t.join();
|
||||
|
||||
this->check_for_busy_waits(start_time);
|
||||
// this should get the initial trigger, and the follow up from in the callback
|
||||
ASSERT_EQ(this->waitable->get_count(), 2u);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
/**
|
||||
* This test checks that when callback groups go out of scope, that their associated executable
|
||||
* entities should not be returned as valid executables.
|
||||
*
|
||||
* The test makes use of a bit of executor internals, but is meant to prevent regressions of behavior.
|
||||
* Ref: https://github.com/ros2/rclcpp/issues/2474
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
#include <rclcpp/callback_group.hpp>
|
||||
#include <rclcpp/executor.hpp>
|
||||
#include <rclcpp/node.hpp>
|
||||
|
||||
std::chrono::milliseconds g_timer_period {1};
|
||||
|
||||
class CustomExecutor : public rclcpp::Executor
|
||||
{
|
||||
public:
|
||||
explicit CustomExecutor(const rclcpp::ExecutorOptions & options = rclcpp::ExecutorOptions())
|
||||
: rclcpp::Executor(options)
|
||||
{}
|
||||
|
||||
~CustomExecutor() override = default;
|
||||
|
||||
void spin() override {}
|
||||
|
||||
void collect()
|
||||
{
|
||||
this->collect_entities();
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
this->wait_for_work(g_timer_period * 10);
|
||||
}
|
||||
|
||||
size_t collected_timers() const
|
||||
{
|
||||
return this->current_collection_.timers.size();
|
||||
}
|
||||
|
||||
rclcpp::AnyExecutable next()
|
||||
{
|
||||
rclcpp::AnyExecutable ret;
|
||||
this->get_next_ready_executable(ret);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST(TestCallbackGroup, valid_callback_group)
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
|
||||
// Create a timer associated with a callback group
|
||||
auto node = std::make_shared<rclcpp::Node>("node");
|
||||
|
||||
std::promise<void> promise;
|
||||
std::future<void> future = promise.get_future();
|
||||
auto timer_callback = [&promise]() {
|
||||
promise.set_value();
|
||||
};
|
||||
|
||||
// Add the callback group to the executor
|
||||
auto executor = CustomExecutor();
|
||||
auto cbg = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, true);
|
||||
auto timer = node->create_wall_timer(g_timer_period, timer_callback, cbg);
|
||||
executor.add_callback_group(cbg, node->get_node_base_interface());
|
||||
|
||||
ASSERT_EQ(
|
||||
rclcpp::FutureReturnCode::SUCCESS,
|
||||
executor.spin_until_future_complete(future, std::chrono::seconds(10)));
|
||||
|
||||
// Collect the entities
|
||||
executor.collect();
|
||||
EXPECT_EQ(1u, executor.collected_timers());
|
||||
|
||||
executor.wait();
|
||||
auto next_executable = executor.next();
|
||||
EXPECT_EQ(timer, next_executable.timer);
|
||||
EXPECT_EQ(cbg, next_executable.callback_group);
|
||||
EXPECT_NE(nullptr, next_executable.data);
|
||||
|
||||
EXPECT_EQ(nullptr, next_executable.client);
|
||||
EXPECT_EQ(nullptr, next_executable.service);
|
||||
EXPECT_EQ(nullptr, next_executable.subscription);
|
||||
EXPECT_EQ(nullptr, next_executable.waitable);
|
||||
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
TEST(TestCallbackGroup, invalid_callback_group)
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
|
||||
// Create a timer associated with a callback group
|
||||
auto node = std::make_shared<rclcpp::Node>("node");
|
||||
|
||||
std::promise<void> promise;
|
||||
std::future<void> future = promise.get_future();
|
||||
auto timer_callback = [&promise]() {
|
||||
promise.set_value();
|
||||
};
|
||||
|
||||
// Add the callback group to the executor
|
||||
auto executor = CustomExecutor();
|
||||
auto cbg = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
|
||||
auto timer = node->create_wall_timer(g_timer_period, timer_callback, cbg);
|
||||
executor.add_callback_group(cbg, node->get_node_base_interface());
|
||||
|
||||
ASSERT_EQ(
|
||||
rclcpp::FutureReturnCode::SUCCESS,
|
||||
executor.spin_until_future_complete(future, std::chrono::seconds(10)));
|
||||
|
||||
// Collect the entities
|
||||
executor.collect();
|
||||
EXPECT_EQ(1u, executor.collected_timers());
|
||||
|
||||
executor.wait();
|
||||
|
||||
cbg.reset();
|
||||
|
||||
// Since the callback group has been reset, this should not be allowed to
|
||||
// be a valid executable (timer and cbg should both be nullptr).
|
||||
// In the regression, timer == next_executable.timer whil
|
||||
// next_executable.callback_group == nullptr, which was incorrect.
|
||||
auto next_executable = executor.next();
|
||||
EXPECT_EQ(nullptr, next_executable.timer);
|
||||
EXPECT_EQ(nullptr, next_executable.callback_group);
|
||||
|
||||
EXPECT_EQ(nullptr, next_executable.client);
|
||||
EXPECT_EQ(nullptr, next_executable.service);
|
||||
EXPECT_EQ(nullptr, next_executable.subscription);
|
||||
EXPECT_EQ(nullptr, next_executable.waitable);
|
||||
EXPECT_EQ(nullptr, next_executable.data);
|
||||
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
125
rclcpp/test/rclcpp/executors/test_executors_intraprocess.cpp
Normal file
125
rclcpp/test/rclcpp/executors/test_executors_intraprocess.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "rclcpp/node.hpp"
|
||||
#include "rclcpp/utilities.hpp"
|
||||
|
||||
#include "test_msgs/msg/empty.hpp"
|
||||
|
||||
#include "./executor_types.hpp"
|
||||
|
||||
template<typename T>
|
||||
class TestIntraprocessExecutors : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
static void SetUpTestCase()
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
}
|
||||
|
||||
static void TearDownTestCase()
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void SetUp()
|
||||
{
|
||||
const auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
std::stringstream test_name;
|
||||
test_name << test_info->test_case_name() << "_" << test_info->name();
|
||||
node = std::make_shared<rclcpp::Node>("node", test_name.str());
|
||||
|
||||
callback_count = 0u;
|
||||
|
||||
const std::string topic_name = std::string("topic_") + test_name.str();
|
||||
|
||||
rclcpp::PublisherOptions po;
|
||||
po.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
publisher = node->create_publisher<test_msgs::msg::Empty>(topic_name, rclcpp::QoS(1), po);
|
||||
|
||||
auto callback = [this](test_msgs::msg::Empty::ConstSharedPtr) {
|
||||
this->callback_count.fetch_add(1u);
|
||||
};
|
||||
|
||||
rclcpp::SubscriptionOptions so;
|
||||
so.use_intra_process_comm = rclcpp::IntraProcessSetting::Enable;
|
||||
subscription =
|
||||
node->create_subscription<test_msgs::msg::Empty>(
|
||||
topic_name, rclcpp::QoS(kNumMessages), std::move(callback), so);
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
publisher.reset();
|
||||
subscription.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
const size_t kNumMessages = 100;
|
||||
|
||||
rclcpp::Node::SharedPtr node;
|
||||
rclcpp::Publisher<test_msgs::msg::Empty>::SharedPtr publisher;
|
||||
rclcpp::Subscription<test_msgs::msg::Empty>::SharedPtr subscription;
|
||||
std::atomic_size_t callback_count;
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(TestIntraprocessExecutors, ExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
TYPED_TEST(TestIntraprocessExecutors, testIntraprocessRetrigger) {
|
||||
// This tests that executors will continue to service intraprocess subscriptions in the case
|
||||
// that publishers aren't continuing to publish.
|
||||
// This was previously broken in that intraprocess guard conditions were only triggered on
|
||||
// publish and the test was added to prevent future regressions.
|
||||
static constexpr size_t kNumMessages = 100;
|
||||
|
||||
using ExecutorType = TypeParam;
|
||||
ExecutorType executor;
|
||||
executor.add_node(this->node);
|
||||
|
||||
EXPECT_EQ(0u, this->callback_count.load());
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
|
||||
// Wait for up to 5 seconds for the first message to come available.
|
||||
const std::chrono::milliseconds sleep_per_loop(10);
|
||||
int loops = 0;
|
||||
while (1u != this->callback_count.load() && loops < 500) {
|
||||
rclcpp::sleep_for(sleep_per_loop);
|
||||
executor.spin_some();
|
||||
loops++;
|
||||
}
|
||||
EXPECT_EQ(1u, this->callback_count.load());
|
||||
|
||||
// reset counter
|
||||
this->callback_count.store(0u);
|
||||
|
||||
for (size_t ii = 0; ii < kNumMessages; ++ii) {
|
||||
this->publisher->publish(test_msgs::msg::Empty());
|
||||
}
|
||||
|
||||
// Fire a timer every 10ms up to 5 seconds waiting for subscriptions to be read.
|
||||
loops = 0;
|
||||
auto timer = this->node->create_wall_timer(
|
||||
std::chrono::milliseconds(10), [this, &executor, &loops]() {
|
||||
loops++;
|
||||
if (kNumMessages == this->callback_count.load() || loops == 500) {
|
||||
executor.cancel();
|
||||
}
|
||||
});
|
||||
executor.spin();
|
||||
EXPECT_EQ(kNumMessages, this->callback_count.load());
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "rclcpp/node.hpp"
|
||||
#include "rclcpp/parameter_client.hpp"
|
||||
#include "rclcpp/utilities.hpp"
|
||||
|
||||
#include "rosgraph_msgs/msg/clock.hpp"
|
||||
|
||||
#include "./executor_types.hpp"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class TimerNode : public rclcpp::Node
|
||||
{
|
||||
public:
|
||||
explicit TimerNode(std::string subname)
|
||||
: Node("timer_node", subname)
|
||||
{
|
||||
}
|
||||
|
||||
void CreateTimer1()
|
||||
{
|
||||
timer1_ = rclcpp::create_timer(
|
||||
this->get_node_base_interface(), get_node_timers_interface(),
|
||||
get_clock(), 1ms,
|
||||
std::bind(&TimerNode::Timer1Callback, this));
|
||||
}
|
||||
|
||||
void CreateTimer2()
|
||||
{
|
||||
timer2_ = rclcpp::create_timer(
|
||||
this->get_node_base_interface(), get_node_timers_interface(),
|
||||
get_clock(), 1ms,
|
||||
std::bind(&TimerNode::Timer2Callback, this));
|
||||
}
|
||||
|
||||
int GetTimer1Cnt()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cnt1_;
|
||||
}
|
||||
int GetTimer2Cnt()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cnt2_;
|
||||
}
|
||||
|
||||
void ResetTimer1()
|
||||
{
|
||||
timer1_->reset();
|
||||
}
|
||||
|
||||
void ResetTimer2()
|
||||
{
|
||||
timer2_->reset();
|
||||
}
|
||||
|
||||
void CancelTimer1()
|
||||
{
|
||||
RCLCPP_DEBUG(this->get_logger(), "Timer 1 cancelling!");
|
||||
timer1_->cancel();
|
||||
}
|
||||
|
||||
void CancelTimer2()
|
||||
{
|
||||
RCLCPP_DEBUG(this->get_logger(), "Timer 2 cancelling!");
|
||||
timer2_->cancel();
|
||||
}
|
||||
|
||||
private:
|
||||
void Timer1Callback()
|
||||
{
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
cnt1_++;
|
||||
}
|
||||
RCLCPP_DEBUG(this->get_logger(), "Timer 1! (%d)", cnt1_);
|
||||
}
|
||||
|
||||
void Timer2Callback()
|
||||
{
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
cnt2_++;
|
||||
}
|
||||
RCLCPP_DEBUG(this->get_logger(), "Timer 2! (%d)", cnt2_);
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
rclcpp::TimerBase::SharedPtr timer1_;
|
||||
rclcpp::TimerBase::SharedPtr timer2_;
|
||||
int cnt1_ = 0;
|
||||
int cnt2_ = 0;
|
||||
};
|
||||
|
||||
// Sets up a separate thread to publish /clock messages.
|
||||
// Clock rate relative to real clock is controlled by realtime_update_rate.
|
||||
// This is set conservatively slow to ensure unit tests are reliable on Windows
|
||||
// environments, where timing performance is subpar.
|
||||
//
|
||||
// Use `sleep_for` in tests to advance the clock. Clock should run and be published
|
||||
// in separate thread continuously to ensure correct behavior in node under test.
|
||||
class ClockPublisher : public rclcpp::Node
|
||||
{
|
||||
public:
|
||||
explicit ClockPublisher(float simulated_clock_step = .001f, float realtime_update_rate = 0.25f)
|
||||
: Node("clock_publisher"),
|
||||
ros_update_duration_(0, 0),
|
||||
realtime_clock_step_(0, 0),
|
||||
rostime_(0, 0)
|
||||
{
|
||||
clock_publisher_ = this->create_publisher<rosgraph_msgs::msg::Clock>("clock", 10);
|
||||
realtime_clock_step_ =
|
||||
rclcpp::Duration::from_seconds(simulated_clock_step / realtime_update_rate);
|
||||
ros_update_duration_ = rclcpp::Duration::from_seconds(simulated_clock_step);
|
||||
|
||||
timer_thread_ = std::thread(&ClockPublisher::RunTimer, this);
|
||||
}
|
||||
|
||||
~ClockPublisher()
|
||||
{
|
||||
running_ = false;
|
||||
if (timer_thread_.joinable()) {
|
||||
timer_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool wait_for_connection(std::chrono::nanoseconds timeout)
|
||||
{
|
||||
auto end_time = std::chrono::steady_clock::now() + timeout;
|
||||
while (clock_publisher_->get_subscription_count() == 0 &&
|
||||
(std::chrono::steady_clock::now() < end_time))
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
return clock_publisher_->get_subscription_count() != 0;
|
||||
}
|
||||
|
||||
void sleep_for(rclcpp::Duration duration)
|
||||
{
|
||||
rclcpp::Time start_time(0, 0, RCL_ROS_TIME);
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
start_time = rostime_;
|
||||
}
|
||||
rclcpp::Time current_time = start_time;
|
||||
|
||||
while (true) {
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
current_time = rostime_;
|
||||
}
|
||||
if ((current_time - start_time) >= duration) {
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(realtime_clock_step_.to_chrono<std::chrono::milliseconds>());
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
rostime_ += ros_update_duration_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void RunTimer()
|
||||
{
|
||||
while (running_) {
|
||||
PublishClock();
|
||||
std::this_thread::sleep_for(realtime_clock_step_.to_chrono<std::chrono::milliseconds>());
|
||||
}
|
||||
}
|
||||
|
||||
void PublishClock()
|
||||
{
|
||||
auto message = rosgraph_msgs::msg::Clock();
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mutex_);
|
||||
message.clock = rostime_;
|
||||
}
|
||||
clock_publisher_->publish(message);
|
||||
}
|
||||
|
||||
rclcpp::Publisher<rosgraph_msgs::msg::Clock>::SharedPtr clock_publisher_;
|
||||
|
||||
rclcpp::Duration ros_update_duration_;
|
||||
rclcpp::Duration realtime_clock_step_;
|
||||
// Rostime must be guarded by a mutex, since accessible in running thread
|
||||
// as well as sleep_for
|
||||
rclcpp::Time rostime_;
|
||||
std::mutex mutex_;
|
||||
std::thread timer_thread_;
|
||||
std::atomic<bool> running_ = true;
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
class TestTimerCancelBehavior : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
static void SetUpTestCase()
|
||||
{
|
||||
rclcpp::init(0, nullptr);
|
||||
}
|
||||
|
||||
static void TearDownTestCase()
|
||||
{
|
||||
rclcpp::shutdown();
|
||||
}
|
||||
|
||||
void SetUp()
|
||||
{
|
||||
const auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
||||
std::stringstream test_name;
|
||||
test_name << test_info->test_case_name() << "_" << test_info->name();
|
||||
node = std::make_shared<TimerNode>(test_name.str());
|
||||
param_client = std::make_shared<rclcpp::SyncParametersClient>(node);
|
||||
ASSERT_TRUE(param_client->wait_for_service(5s));
|
||||
|
||||
auto set_parameters_results = param_client->set_parameters(
|
||||
{rclcpp::Parameter("use_sim_time", true)});
|
||||
for (auto & result : set_parameters_results) {
|
||||
ASSERT_TRUE(result.successful);
|
||||
}
|
||||
|
||||
// Check if the clock type is simulation time
|
||||
EXPECT_EQ(RCL_ROS_TIME, node->get_clock()->get_clock_type());
|
||||
|
||||
// Create timers
|
||||
this->node->CreateTimer1();
|
||||
this->node->CreateTimer2();
|
||||
|
||||
// Run standalone thread to publish clock time
|
||||
sim_clock_node = std::make_shared<ClockPublisher>();
|
||||
|
||||
// Spin the executor in a standalone thread
|
||||
executor.add_node(this->node);
|
||||
standalone_thread = std::thread(
|
||||
[this]() {
|
||||
executor.spin();
|
||||
});
|
||||
|
||||
EXPECT_TRUE(this->sim_clock_node->wait_for_connection(50ms));
|
||||
EXPECT_EQ(RCL_ROS_TIME, node->get_clock()->ros_time_is_active());
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
node.reset();
|
||||
|
||||
// Clean up thread object
|
||||
if (standalone_thread.joinable()) {
|
||||
standalone_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<TimerNode> node;
|
||||
std::shared_ptr<ClockPublisher> sim_clock_node;
|
||||
rclcpp::SyncParametersClient::SharedPtr param_client;
|
||||
std::thread standalone_thread;
|
||||
T executor;
|
||||
};
|
||||
|
||||
using MainExecutorTypes =
|
||||
::testing::Types<
|
||||
rclcpp::executors::SingleThreadedExecutor,
|
||||
rclcpp::executors::MultiThreadedExecutor,
|
||||
rclcpp::executors::StaticSingleThreadedExecutor>;
|
||||
|
||||
// TODO(@fujitatomoya): this test excludes EventExecutor because it does not
|
||||
// support simulation time used for this test to relax the racy condition.
|
||||
// See more details for https://github.com/ros2/rclcpp/issues/2457.
|
||||
TYPED_TEST_SUITE(TestTimerCancelBehavior, MainExecutorTypes, ExecutorTypeNames);
|
||||
|
||||
TYPED_TEST(TestTimerCancelBehavior, testTimer1CancelledWithExecutorSpin) {
|
||||
// Validate that cancelling one timer yields no change in behavior for other
|
||||
// timers. Specifically, this tests the behavior when using spin() to run the
|
||||
// executor, which is the most common usecase.
|
||||
|
||||
// Cancel to stop the spin after some time.
|
||||
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();
|
||||
EXPECT_NE(t1_runs, t2_runs);
|
||||
// Check that t2 has significantly more calls
|
||||
EXPECT_LT(t1_runs + 50, t2_runs);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTimerCancelBehavior, testTimer2CancelledWithExecutorSpin) {
|
||||
// Validate that cancelling one timer yields no change in behavior for other
|
||||
// timers. Specifically, this tests the behavior when using spin() to run the
|
||||
// executor, which is the most common usecase.
|
||||
|
||||
// Cancel to stop the spin after some time.
|
||||
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();
|
||||
EXPECT_NE(t1_runs, t2_runs);
|
||||
// Check that t1 has significantly more calls
|
||||
EXPECT_LT(t2_runs + 50, t1_runs);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTimerCancelBehavior, testHeadTimerCancelThenResetBehavior) {
|
||||
// Validate that cancelling timer doesn't affect operation of other timers,
|
||||
// and that the cancelled timer starts executing normally once reset manually.
|
||||
|
||||
// Cancel to stop the spin after some time.
|
||||
this->sim_clock_node->sleep_for(50ms);
|
||||
this->node->CancelTimer1();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
int t1_runs_initial = this->node->GetTimer1Cnt();
|
||||
int t2_runs_initial = this->node->GetTimer2Cnt();
|
||||
|
||||
// Manually reset timer 1, then sleep again
|
||||
// Counts should update.
|
||||
this->node->ResetTimer1();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
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);
|
||||
|
||||
EXPECT_LT(t1_runs_initial + 50, t2_runs_initial);
|
||||
// Check that t2 has significantly more calls, and keeps getting called.
|
||||
EXPECT_LT(t2_runs_initial + 50, t2_runs_final);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTimerCancelBehavior, testBackTimerCancelThenResetBehavior) {
|
||||
// Validate that cancelling timer doesn't affect operation of other timers,
|
||||
// and that the cancelled timer starts executing normally once reset manually.
|
||||
|
||||
// Cancel to stop the spin after some time.
|
||||
this->sim_clock_node->sleep_for(50ms);
|
||||
this->node->CancelTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
int t1_runs_initial = this->node->GetTimer1Cnt();
|
||||
int t2_runs_initial = this->node->GetTimer2Cnt();
|
||||
|
||||
// Manually reset timer 1, then sleep again
|
||||
// Counts should update.
|
||||
this->node->ResetTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
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);
|
||||
|
||||
EXPECT_LT(t2_runs_initial + 50, t1_runs_initial);
|
||||
// Check that t1 has significantly more calls, and keeps getting called.
|
||||
EXPECT_LT(t1_runs_initial + 50, t1_runs_final);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTimerCancelBehavior, testBothTimerCancelThenResetT1Behavior) {
|
||||
// Validate behavior from cancelling 2 timers, then only re-enabling one of them.
|
||||
// Ensure that only the reset timer is executed.
|
||||
|
||||
// Cancel to stop the spin after some time.
|
||||
this->sim_clock_node->sleep_for(50ms);
|
||||
this->node->CancelTimer1();
|
||||
this->node->CancelTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
int t1_runs_initial = this->node->GetTimer1Cnt();
|
||||
int t2_runs_initial = this->node->GetTimer2Cnt();
|
||||
|
||||
// Manually reset timer 1, then sleep again
|
||||
// Counts should update.
|
||||
this->node->ResetTimer1();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
int t1_runs_intermediate = this->node->GetTimer1Cnt();
|
||||
int t2_runs_intermediate = this->node->GetTimer2Cnt();
|
||||
|
||||
this->node->ResetTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
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);
|
||||
|
||||
// Expect that T1 has up to 15 more calls than t2. Add some buffer
|
||||
// to account for jitter.
|
||||
EXPECT_EQ(t2_runs_initial, t2_runs_intermediate);
|
||||
EXPECT_LT(t1_runs_initial + 50, t1_runs_intermediate);
|
||||
|
||||
// Expect that by end of test, both are running properly again.
|
||||
EXPECT_LT(t1_runs_intermediate + 50, t1_runs_final);
|
||||
EXPECT_LT(t2_runs_intermediate + 50, t2_runs_final);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestTimerCancelBehavior, testBothTimerCancelThenResetT2Behavior) {
|
||||
// Validate behavior from cancelling 2 timers, then only re-enabling one of them.
|
||||
// Ensure that only the reset timer is executed.
|
||||
|
||||
// Cancel to stop the spin after some time.
|
||||
this->sim_clock_node->sleep_for(50ms);
|
||||
this->node->CancelTimer1();
|
||||
this->node->CancelTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
int t1_runs_initial = this->node->GetTimer1Cnt();
|
||||
int t2_runs_initial = this->node->GetTimer2Cnt();
|
||||
|
||||
// Manually reset timer 1, then sleep again
|
||||
// Counts should update.
|
||||
this->node->ResetTimer2();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
int t1_runs_intermediate = this->node->GetTimer1Cnt();
|
||||
int t2_runs_intermediate = this->node->GetTimer2Cnt();
|
||||
|
||||
this->node->ResetTimer1();
|
||||
this->sim_clock_node->sleep_for(150ms);
|
||||
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);
|
||||
|
||||
// Expect that T1 has up to 15 more calls than t2. Add some buffer
|
||||
// to account for jitter.
|
||||
EXPECT_EQ(t1_runs_initial, t1_runs_intermediate);
|
||||
EXPECT_LT(t2_runs_initial + 50, t2_runs_intermediate);
|
||||
|
||||
// Expect that by end of test, both are running properly again.
|
||||
EXPECT_LT(t1_runs_intermediate + 50, t1_runs_final);
|
||||
EXPECT_LT(t2_runs_intermediate + 50, t2_runs_final);
|
||||
}
|
||||
@@ -230,9 +230,9 @@ TEST_F(TestStaticExecutorEntitiesCollector, add_remove_node_out_of_scope) {
|
||||
class TestWaitable : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
void add_to_wait_set(rcl_wait_set_t *) override {}
|
||||
void add_to_wait_set(rcl_wait_set_t &) override {}
|
||||
|
||||
bool is_ready(rcl_wait_set_t *) override {return true;}
|
||||
bool is_ready(const rcl_wait_set_t &) override {return true;}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override
|
||||
@@ -240,10 +240,7 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
void
|
||||
execute(std::shared_ptr<void> & data) override
|
||||
{
|
||||
(void) data;
|
||||
}
|
||||
execute(const std::shared_ptr<void> &) override {}
|
||||
};
|
||||
|
||||
TEST_F(TestStaticExecutorEntitiesCollector, add_remove_node_with_entities) {
|
||||
@@ -513,11 +510,6 @@ TEST_F(TestStaticExecutorEntitiesCollector, add_to_wait_set_nullptr) {
|
||||
entities_collector_->init(&wait_set, memory_strategy);
|
||||
RCPPUTILS_SCOPE_EXIT(entities_collector_->fini());
|
||||
|
||||
EXPECT_THROW(
|
||||
entities_collector_->add_to_wait_set(nullptr),
|
||||
std::invalid_argument);
|
||||
rcl_reset_error();
|
||||
|
||||
EXPECT_TRUE(entities_collector_->remove_node(node->get_node_base_interface()));
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ TEST_F(TestStaticSingleThreadedExecutor, add_callback_group_trigger_guard_failed
|
||||
"lib:rclcpp", rcl_trigger_guard_condition, RCL_RET_ERROR);
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
executor.add_callback_group(cb_group, node->get_node_base_interface(), true),
|
||||
std::runtime_error("error not set"));
|
||||
std::runtime_error("Failed to trigger guard condition on callback group add: error not set"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ TEST_F(TestStaticSingleThreadedExecutor, add_node_trigger_guard_failed) {
|
||||
"lib:rclcpp", rcl_trigger_guard_condition, RCL_RET_ERROR);
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
executor.add_node(node),
|
||||
std::runtime_error("error not set"));
|
||||
std::runtime_error("Failed to trigger guard condition on node add: error not set"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ TEST_F(TestStaticSingleThreadedExecutor, remove_callback_group_trigger_guard_fai
|
||||
"lib:rclcpp", rcl_trigger_guard_condition, RCL_RET_ERROR);
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
executor.remove_callback_group(cb_group, true),
|
||||
std::runtime_error("error not set"));
|
||||
std::runtime_error(
|
||||
"Failed to trigger guard condition on callback group remove: error not set"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ TEST_F(TestStaticSingleThreadedExecutor, remove_node_failed) {
|
||||
"lib:rclcpp", rcl_trigger_guard_condition, RCL_RET_ERROR);
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
executor.remove_node(node, true),
|
||||
std::runtime_error("Node needs to be associated with this executor."));
|
||||
std::runtime_error("Node '/ns/node' needs to be associated with an executor."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +115,7 @@ TEST_F(TestStaticSingleThreadedExecutor, remove_node_trigger_guard_failed) {
|
||||
"lib:rclcpp", rcl_trigger_guard_condition, RCL_RET_ERROR);
|
||||
RCLCPP_EXPECT_THROW_EQ(
|
||||
executor.remove_node(node, true),
|
||||
std::runtime_error("error not set"));
|
||||
std::runtime_error("Failed to trigger guard condition on node remove: error not set"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ public:
|
||||
: TimerBase(node->get_clock(), std::chrono::nanoseconds(1),
|
||||
node->get_node_base_interface()->get_context()) {}
|
||||
|
||||
bool call() override {return true;}
|
||||
void execute_callback() override {}
|
||||
std::shared_ptr<void> call() override {return nullptr;}
|
||||
void execute_callback(const std::shared_ptr<void> &) override {}
|
||||
bool is_steady() override {return false;}
|
||||
};
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
class TestWaitable : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
void add_to_wait_set(rcl_wait_set_t *) override {}
|
||||
bool is_ready(rcl_wait_set_t *) override {return false;}
|
||||
void add_to_wait_set(rcl_wait_set_t &) override {}
|
||||
bool is_ready(const rcl_wait_set_t &) override {return false;}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override
|
||||
@@ -37,10 +37,7 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void execute(std::shared_ptr<void> & data) override
|
||||
{
|
||||
(void) data;
|
||||
}
|
||||
void execute(const std::shared_ptr<void> &) override {}
|
||||
};
|
||||
|
||||
class TestNodeWaitables : public ::testing::Test
|
||||
|
||||
@@ -39,14 +39,14 @@ static bool test_waitable_result = false;
|
||||
class TestWaitable : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
void add_to_wait_set(rcl_wait_set_t *) override
|
||||
void add_to_wait_set(rcl_wait_set_t &) override
|
||||
{
|
||||
if (!test_waitable_result) {
|
||||
throw std::runtime_error("TestWaitable add_to_wait_set failed");
|
||||
}
|
||||
}
|
||||
|
||||
bool is_ready(rcl_wait_set_t *) override
|
||||
bool is_ready(const rcl_wait_set_t &) override
|
||||
{
|
||||
return test_waitable_result;
|
||||
}
|
||||
@@ -57,61 +57,7 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void execute(std::shared_ptr<void> & data) override
|
||||
{
|
||||
(void) data;
|
||||
}
|
||||
};
|
||||
|
||||
static bool test_waitable_result2 = false;
|
||||
|
||||
class TestWaitable2 : public rclcpp::Waitable
|
||||
{
|
||||
public:
|
||||
explicit TestWaitable2(rcl_publisher_t * pub_ptr)
|
||||
: pub_ptr_(pub_ptr),
|
||||
pub_event_(rcl_get_zero_initialized_event())
|
||||
{
|
||||
EXPECT_EQ(
|
||||
rcl_publisher_event_init(&pub_event_, pub_ptr_, RCL_PUBLISHER_OFFERED_DEADLINE_MISSED),
|
||||
RCL_RET_OK);
|
||||
}
|
||||
|
||||
~TestWaitable2()
|
||||
{
|
||||
EXPECT_EQ(rcl_event_fini(&pub_event_), RCL_RET_OK);
|
||||
}
|
||||
|
||||
void add_to_wait_set(rcl_wait_set_t * wait_set) override
|
||||
{
|
||||
EXPECT_EQ(rcl_wait_set_add_event(wait_set, &pub_event_, &wait_set_event_index_), RCL_RET_OK);
|
||||
}
|
||||
|
||||
bool is_ready(rcl_wait_set_t *) override
|
||||
{
|
||||
return test_waitable_result2;
|
||||
}
|
||||
|
||||
std::shared_ptr<void>
|
||||
take_data() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void execute(std::shared_ptr<void> & data) override
|
||||
{
|
||||
(void) data;
|
||||
}
|
||||
|
||||
size_t get_number_of_ready_events() override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
rcl_publisher_t * pub_ptr_;
|
||||
rcl_event_t pub_event_;
|
||||
size_t wait_set_event_index_;
|
||||
void execute(const std::shared_ptr<void> &) override {}
|
||||
};
|
||||
|
||||
struct RclWaitSetSizes
|
||||
@@ -200,9 +146,9 @@ protected:
|
||||
|
||||
std::shared_ptr<rclcpp::Node> create_node_with_service(const std::string & name)
|
||||
{
|
||||
auto service_callback =
|
||||
[](const test_msgs::srv::Empty::Request::SharedPtr,
|
||||
test_msgs::srv::Empty::Response::SharedPtr) {};
|
||||
auto service_callback = [](
|
||||
const test_msgs::srv::Empty::Request::SharedPtr,
|
||||
test_msgs::srv::Empty::Response::SharedPtr) {};
|
||||
auto node_with_service = create_node_with_disabled_callback_groups(name);
|
||||
|
||||
auto callback_group =
|
||||
@@ -708,129 +654,20 @@ TEST_F(TestAllocatorMemoryStrategy, get_next_timer) {
|
||||
}
|
||||
|
||||
TEST_F(TestAllocatorMemoryStrategy, get_next_waitable) {
|
||||
auto node1 = std::make_shared<rclcpp::Node>("waitable_node", "ns");
|
||||
auto node2 = std::make_shared<rclcpp::Node>("waitable_node2", "ns");
|
||||
rclcpp::Waitable::SharedPtr waitable1 = std::make_shared<TestWaitable>();
|
||||
rclcpp::Waitable::SharedPtr waitable2 = std::make_shared<TestWaitable>();
|
||||
node1->get_node_waitables_interface()->add_waitable(waitable1, nullptr);
|
||||
node2->get_node_waitables_interface()->add_waitable(waitable2, nullptr);
|
||||
|
||||
auto get_next_entity = [this](const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) {
|
||||
rclcpp::AnyExecutable result;
|
||||
allocator_memory_strategy()->get_next_waitable(result, weak_groups_to_nodes);
|
||||
return result;
|
||||
};
|
||||
|
||||
{
|
||||
auto node1 = std::make_shared<rclcpp::Node>(
|
||||
"waitable_node", "ns",
|
||||
rclcpp::NodeOptions()
|
||||
.start_parameter_event_publisher(false)
|
||||
.start_parameter_services(false));
|
||||
|
||||
rclcpp::PublisherOptions pub_options;
|
||||
pub_options.use_default_callbacks = false;
|
||||
|
||||
auto pub1 = node1->create_publisher<test_msgs::msg::Empty>(
|
||||
"test_topic_1", rclcpp::QoS(10), pub_options);
|
||||
|
||||
auto waitable1 =
|
||||
std::make_shared<TestWaitable2>(pub1->get_publisher_handle().get());
|
||||
node1->get_node_waitables_interface()->add_waitable(waitable1, nullptr);
|
||||
|
||||
auto basic_node = create_node_with_disabled_callback_groups("basic_node");
|
||||
WeakCallbackGroupsToNodesMap weak_groups_to_nodes;
|
||||
basic_node->for_each_callback_group(
|
||||
[basic_node, &weak_groups_to_nodes](rclcpp::CallbackGroup::SharedPtr group_ptr)
|
||||
{
|
||||
weak_groups_to_nodes.insert(
|
||||
std::pair<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>(
|
||||
group_ptr,
|
||||
basic_node->get_node_base_interface()));
|
||||
});
|
||||
node1->for_each_callback_group(
|
||||
[node1, &weak_groups_to_nodes](rclcpp::CallbackGroup::SharedPtr group_ptr)
|
||||
{
|
||||
weak_groups_to_nodes.insert(
|
||||
std::pair<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>(
|
||||
group_ptr,
|
||||
node1->get_node_base_interface()));
|
||||
});
|
||||
allocator_memory_strategy()->collect_entities(weak_groups_to_nodes);
|
||||
|
||||
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
|
||||
ASSERT_EQ(
|
||||
rcl_wait_set_init(
|
||||
&wait_set,
|
||||
allocator_memory_strategy()->number_of_ready_subscriptions(),
|
||||
allocator_memory_strategy()->number_of_guard_conditions(),
|
||||
allocator_memory_strategy()->number_of_ready_timers(),
|
||||
allocator_memory_strategy()->number_of_ready_clients(),
|
||||
allocator_memory_strategy()->number_of_ready_services(),
|
||||
allocator_memory_strategy()->number_of_ready_events(),
|
||||
rclcpp::contexts::get_global_default_context()->get_rcl_context().get(),
|
||||
allocator_memory_strategy()->get_allocator()),
|
||||
RCL_RET_OK);
|
||||
|
||||
ASSERT_TRUE(allocator_memory_strategy()->add_handles_to_wait_set(&wait_set));
|
||||
|
||||
ASSERT_EQ(
|
||||
rcl_wait(
|
||||
&wait_set,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(100))
|
||||
.count()),
|
||||
RCL_RET_OK);
|
||||
test_waitable_result2 = true;
|
||||
allocator_memory_strategy()->remove_null_handles(&wait_set);
|
||||
|
||||
rclcpp::AnyExecutable result = get_next_entity(weak_groups_to_nodes);
|
||||
EXPECT_EQ(result.node_base, node1->get_node_base_interface());
|
||||
test_waitable_result2 = false;
|
||||
|
||||
EXPECT_EQ(rcl_wait_set_fini(&wait_set), RCL_RET_OK);
|
||||
}
|
||||
|
||||
{
|
||||
auto node2 = std::make_shared<rclcpp::Node>(
|
||||
"waitable_node2", "ns",
|
||||
rclcpp::NodeOptions()
|
||||
.start_parameter_services(false)
|
||||
.start_parameter_event_publisher(false));
|
||||
|
||||
rclcpp::PublisherOptions pub_options;
|
||||
pub_options.use_default_callbacks = false;
|
||||
|
||||
auto pub2 = node2->create_publisher<test_msgs::msg::Empty>(
|
||||
"test_topic_2", rclcpp::QoS(10), pub_options);
|
||||
|
||||
auto waitable2 =
|
||||
std::make_shared<TestWaitable2>(pub2->get_publisher_handle().get());
|
||||
node2->get_node_waitables_interface()->add_waitable(waitable2, nullptr);
|
||||
|
||||
auto basic_node2 = std::make_shared<rclcpp::Node>(
|
||||
"basic_node2", "ns",
|
||||
rclcpp::NodeOptions()
|
||||
.start_parameter_services(false)
|
||||
.start_parameter_event_publisher(false));
|
||||
WeakCallbackGroupsToNodesMap weak_groups_to_uncollected_nodes;
|
||||
basic_node2->for_each_callback_group(
|
||||
[basic_node2, &weak_groups_to_uncollected_nodes](rclcpp::CallbackGroup::SharedPtr group_ptr)
|
||||
{
|
||||
weak_groups_to_uncollected_nodes.insert(
|
||||
std::pair<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>(
|
||||
group_ptr,
|
||||
basic_node2->get_node_base_interface()));
|
||||
});
|
||||
node2->for_each_callback_group(
|
||||
[node2,
|
||||
&weak_groups_to_uncollected_nodes](rclcpp::CallbackGroup::SharedPtr group_ptr)
|
||||
{
|
||||
weak_groups_to_uncollected_nodes.insert(
|
||||
std::pair<rclcpp::CallbackGroup::WeakPtr,
|
||||
rclcpp::node_interfaces::NodeBaseInterface::WeakPtr>(
|
||||
group_ptr,
|
||||
node2->get_node_base_interface()));
|
||||
});
|
||||
|
||||
rclcpp::AnyExecutable failed_result = get_next_entity(weak_groups_to_uncollected_nodes);
|
||||
EXPECT_EQ(failed_result.node_base, nullptr);
|
||||
}
|
||||
EXPECT_TRUE(TestGetNextEntity(node1, node2, get_next_entity));
|
||||
}
|
||||
|
||||
TEST_F(TestAllocatorMemoryStrategy, get_next_subscription_mutually_exclusive) {
|
||||
@@ -949,9 +786,9 @@ TEST_F(TestAllocatorMemoryStrategy, get_next_service_out_of_scope) {
|
||||
node->create_callback_group(
|
||||
rclcpp::CallbackGroupType::MutuallyExclusive);
|
||||
|
||||
auto service_callback =
|
||||
[](const test_msgs::srv::Empty::Request::SharedPtr,
|
||||
test_msgs::srv::Empty::Response::SharedPtr) {};
|
||||
auto service_callback = [](
|
||||
const test_msgs::srv::Empty::Request::SharedPtr,
|
||||
test_msgs::srv::Empty::Response::SharedPtr) {};
|
||||
auto service = node->create_service<test_msgs::srv::Empty>(
|
||||
"service", std::move(service_callback), rclcpp::ServicesQoS(), callback_group);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user