autoware_agnocast_wrapper#
The purpose of this package is to integrate Agnocast, a zero-copy middleware, into each topic in Autoware with minimal side effects. Agnocast is a library designed to work alongside ROS 2, enabling true zero-copy publish/subscribe communication for all ROS 2 message types, including unsized message types.
- Agnocast Repository: https://github.com/tier4/agnocast
- Discussion on Agnocast Integration into Autoware: https://github.com/orgs/autowarefoundation/discussions/5835
- Review Guide for Agnocast Wrapper PRs
This package provides macros that wrap functions for publish/subscribe operations and smart pointer types for handling ROS 2 messages. When Autoware is built using the default build command, Agnocast is not enabled. However, setting the environment variable ENABLE_AGNOCAST=1 enables Agnocast and results in a build that includes its integration. This design ensures backward compatibility for users who are unaware of Agnocast, minimizing disruption.
Two Integration Approaches#
This package provides two approaches for integrating Agnocast. Both will coexist for the foreseeable future.
1. Node Wrapper (agnocast_wrapper::Node)#
Use this when you want the entire node to transparently switch between rclcpp::Node and agnocast::Node at runtime. The node wrapper automatically selects the correct underlying implementation based on the ENABLE_AGNOCAST environment variable.
Currently supported APIs:
- Publisher / Subscription / PollingSubscriber
- Parameters
- Logger, Clock
- Callback groups
- Node interfaces (partial:
get_node_base_interface(),get_node_topics_interface(),get_node_parameters_interface())
Note: Timer (
create_wall_timer,create_timer) is not yet supported and will be added in a future update.
```cpp
include #
class MyNode : public autoware::agnocast_wrapper::Node
{
public:
explicit MyNode(const rclcpp::NodeOptions & options)
: Node("my_node", options)
{
pub_ = create_publisher
private:
autoware::agnocast_wrapper::Publisher
To use the Node wrapper in your package, add the following to your CMakeLists.txt:
cmake
find_package(autoware_agnocast_wrapper REQUIRED)
ament_target_dependencies(my_node_component autoware_agnocast_wrapper)
Registering a Node with autoware_agnocast_wrapper_register_node#
Instead of calling rclcpp_components_register_node directly, use the autoware_agnocast_wrapper_register_node macro to register your component node. This macro:
- Registers the component with
rclcpp_components(for component container support) - Creates a standalone executable that can switch between
rclcpp::Nodeandagnocast::Nodeat runtime based on theENABLE_AGNOCASTenvironment variable
When ENABLE_AGNOCAST is not set or set to 0, this macro falls back to standard rclcpp_components_register_node behavior.
```cmake find_package(autoware_agnocast_wrapper REQUIRED)
ament_auto_add_library(my_node_component SHARED src/my_node.cpp) ament_target_dependencies(my_node_component autoware_agnocast_wrapper)
autoware_agnocast_wrapper_register_node(my_node_component PLUGIN "my_package::MyNode" EXECUTABLE my_node ) ```
Parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
PLUGIN |
Yes | - | Fully qualified class name of the component |
EXECUTABLE |
Yes | - | Executable name for the node |
ROS2_EXECUTOR |
No | SingleThreadedExecutor |
Executor to use when ENABLE_AGNOCAST=0 at runtime |
AGNOCAST_EXECUTOR |
No | SingleThreadedAgnocastExecutor |
Executor to use when ENABLE_AGNOCAST=1 at runtime |
Valid executor values:
ROS2_EXECUTOR:SingleThreadedExecutor,MultiThreadedExecutorAGNOCAST_EXECUTOR:SingleThreadedAgnocastExecutor,MultiThreadedAgnocastExecutor,CallbackIsolatedAgnocastExecutor,AgnocastOnlySingleThreadedExecutor,AgnocastOnlyMultiThreadedExecutor,AgnocastOnlyCallbackIsolatedExecutor
Node class requirements:
The required PLUGIN base class depends on the AGNOCAST_EXECUTOR type. The generated template enforces this via if constexpr at compile time:
| AGNOCAST_EXECUTOR | Required PLUGIN base class |
|---|---|
AgnocastOnly* executors |
autoware::agnocast_wrapper::Node |
| Other agnocast executors | Any rclcpp::Node-compatible class |
Non-AgnocastOnly executors use NodeInstanceWrapper::get_node_base_interface() directly, which works with any node type (rclcpp::Node, agnocast_wrapper::Node, etc.) without requiring a cast. AgnocastOnly executors require get_agnocast_node(), which is only available on autoware::agnocast_wrapper::Node.
Behavior reference:
The tables below show the complete behavior for each configuration. When ENABLE_AGNOCAST=0 at build time, only ROS2_EXECUTOR matters. When ENABLE_AGNOCAST=1, the behavior depends on both ROS2_EXECUTOR and AGNOCAST_EXECUTOR, and can be switched at runtime via the ENABLE_AGNOCAST environment variable.
Build-time ENABLE_AGNOCAST=0 (or unset):
| ROS2_EXECUTOR | Runtime behavior |
|---|---|
SingleThreadedExecutor |
SingleThreadedExecutor |
MultiThreadedExecutor |
MultiThreadedExecutor |
Runtime ENABLE_AGNOCAST has no effect in this mode — no switchable template is generated.
Build-time ENABLE_AGNOCAST=1:
| ROS 2 _EXECUTOR |
AGNOCAST _EXECUTOR |
RuntimeENABLE_AGNOCAST=0 |
RuntimeENABLE_AGNOCAST=1 |
|---|---|---|---|
SingleThreadedExecutor |
SingleThreadedAgnocastExecutor |
SingleThreadedExecutor |
SingleThreadedAgnocastExecutor |
MultiThreadedExecutor |
MultiThreadedAgnocastExecutor |
MultiThreadedExecutor |
MultiThreadedAgnocastExecutor |
MultiThreadedExecutor |
CallbackIsolatedAgnocastExecutor |
MultiThreadedExecutor |
CallbackIsolatedAgnocastExecutor |
SingleThreadedExecutor |
AgnocastOnlySingleThreadedExecutor |
SingleThreadedExecutor |
AgnocastOnlySingleThreadedExecutor |
MultiThreadedExecutor |
AgnocastOnlyMultiThreadedExecutor |
MultiThreadedExecutor |
AgnocastOnlyMultiThreadedExecutor |
MultiThreadedExecutor |
AgnocastOnlyCallbackIsolatedExecutor |
MultiThreadedExecutor |
AgnocastOnlyCallbackIsolatedExecutor |
SingleThreadedExecutor |
MultiThreadedAgnocastExecutor |
SingleThreadedExecutor |
MultiThreadedAgnocastExecutor |
SingleThreadedExecutor |
CallbackIsolatedAgnocastExecutor |
SingleThreadedExecutor |
CallbackIsolatedAgnocastExecutor |
SingleThreadedExecutor |
AgnocastOnlyMultiThreadedExecutor |
SingleThreadedExecutor |
AgnocastOnlyMultiThreadedExecutor |
SingleThreadedExecutor |
AgnocastOnlyCallbackIsolatedExecutor |
SingleThreadedExecutor |
AgnocastOnlyCallbackIsolatedExecutor |
MultiThreadedExecutor |
SingleThreadedAgnocastExecutor |
MultiThreadedExecutor |
SingleThreadedAgnocastExecutor |
MultiThreadedExecutor |
AgnocastOnlySingleThreadedExecutor |
MultiThreadedExecutor |
AgnocastOnlySingleThreadedExecutor |
Example with agnocast_wrapper::Node (AgnocastOnly executor):
cmake
autoware_agnocast_wrapper_register_node(my_node_component
PLUGIN "my_package::MyNode"
EXECUTABLE my_node
AGNOCAST_EXECUTOR AgnocastOnlyCallbackIsolatedExecutor
)
Example with rclcpp::Node (no node changes required):
cmake
autoware_agnocast_wrapper_register_node(my_node_component
PLUGIN "my_package::MyNode"
EXECUTABLE my_node
AGNOCAST_EXECUTOR CallbackIsolatedAgnocastExecutor
)
2. Macro + Free Function API#
Use this when only specific topics need Agnocast on an existing rclcpp::Node, without converting the entire node to agnocast_wrapper::Node.
You can immediately understand how to use the macros just by looking at autoware_agnocast_wrapper.hpp. A typical callback and publisher setup looks like this:
```cpp
include #
pub_output_ = AUTOWARE_CREATE_PUBLISHER3( PointCloud2, "output", rclcpp::SensorDataQoS().keep_last(max_queue_size_), pub_options );
void onPointCloud(AUTOWARE_MESSAGE_UNIQUE_PTR(const PointCloud2) && input_msg) { auto output = ALLOCATE_OUTPUT_MESSAGE_UNIQUE(pub_output_); ... pub_output_->publish(std::move(output)); } ```
To use the macros provided by this package in your own package, include the following lines in your CMakeLists.txt:
cmake
find_package(autoware_agnocast_wrapper REQUIRED)
ament_target_dependencies(target autoware_agnocast_wrapper)
target_include_directories(target PRIVATE ${autoware_agnocast_wrapper_INCLUDE_DIRS})
autoware_agnocast_wrapper_setup(target)
Message Filters Support#
This package provides wrapper types for message_filters (Subscriber, Synchronizer, ApproximateTimeSynchronizer) in the autoware::agnocast_wrapper::message_filters namespace. These wrappers transparently switch between ::message_filters and agnocast::message_filters at runtime.
Current limitations#
- Only
ApproximateTimesynchronization policy is supported (noExactTime). - Maximum 2 message types per
Synchronizer. connectInput()is not supported; passSubscriberreferences at construction time.
Usage example#
```cpp
include #
using namespace autoware::agnocast_wrapper::message_filters;
// 1. Create subscribers
Subscriber
// 2. Create synchronizer
using Policy = sync_policies::ApproximateTime<
sensor_msgs::msg::Image, sensor_msgs::msg::CameraInfo>;
Synchronizer
// 3. Register callback (use std::bind, not a lambda — see migration note below) sync.registerCallback( std::bind(&MyNode::onSynchronized, this, std::placeholders::_1, std::placeholders::_2)); ```
The callback method signature should use const references:
cpp
void onSynchronized(
const AUTOWARE_MESSAGE_CONST_SHARED_PTR(sensor_msgs::msg::Image) & img,
const AUTOWARE_MESSAGE_CONST_SHARED_PTR(sensor_msgs::msg::CameraInfo) & info);
Migration guide (from ::message_filters)#
| Before | After |
|---|---|
#include <message_filters/subscriber.h> |
#include <autoware/agnocast_wrapper/message_filters.hpp> |
message_filters::Subscriber<M> |
autoware::agnocast_wrapper::message_filters::Subscriber<M> |
message_filters::Synchronizer<Policy> |
autoware::agnocast_wrapper::message_filters::Synchronizer<Policy> |
message_filters::sync_policies::ApproximateTime<M0, M1> |
autoware::agnocast_wrapper::message_filters::sync_policies::ApproximateTime<M0, M1> |
How to Enable/Disable Agnocast on Build#
To build Autoware with Agnocast:
bash
export ENABLE_AGNOCAST=1
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
To build Autoware without Agnocast (default behavior):
bash
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
To explicitly disable Agnocast when it has been previously enabled:
```bash unset ENABLE_AGNOCAST
or#
export ENABLE_AGNOCAST=0 ```
To rebuild a specific package without Agnocast after it was previously built with Agnocast:
bash
rm -Rf ./install/<package_name> ./build/<package_name>
export ENABLE_AGNOCAST=0
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release --package-select <package_name>
To rebuild a specific package with Agnocast after it was previously built without it:
bash
rm -Rf ./install/<package_name> ./build/<package_name>
export ENABLE_AGNOCAST=1
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release --package-select <package_name>
Please note that the ENABLE_AGNOCAST environment variable may not behave as expected in the following scenario:
- Package A depends on build artifacts from Package B
- Both A and B were previously built with Agnocast enabled
- Rebuilding only Package A with
ENABLE_AGNOCAST=0will not be sufficient, as compile options enabling Agnocast may propagate from Package B
Example:
- A =
autoware_occupancy_grid_map_outlier_filter - B =
autoware_pointcloud_preprocessor
In such cases, rebuild both A and B with Agnocast disabled to ensure consistency. As a best practice, we recommend keeping the value of ENABLE_AGNOCAST consistent within a workspace to avoid unintentional mismatches and simplify build management.
How to Enable Agnocast at Runtime#
When Agnocast is enabled at build time, the heaphook shared library must be preloaded at runtime via LD_PRELOAD, and component containers must be replaced with their Agnocast equivalents. This package provides agnocast_env.launch.xml (and its Python equivalent agnocast_env.launch.py) which handles both of these concerns based on the ENABLE_AGNOCAST environment variable.
Provided Variables#
After including agnocast_env.launch.xml (or agnocast_env.launch.py), the following variables are available (in Python launch files, reference them via LaunchConfiguration):
| Variable | Description |
|---|---|
ld_preload_value |
LD_PRELOAD value with the heaphook library prepended (when Agnocast is enabled) |
container_package |
Resolved component container package name (rclcpp_components or agnocast_components) |
container_executable |
Resolved component container executable name |
Launch Arguments#
| Argument | Default | Description |
|---|---|---|
agnocast_heaphook_path |
/opt/ros/humble/lib/libagnocast_heaphook.so |
Path to the heaphook shared library |
use_multithread |
false |
Use the multi-threaded component container (component_container_mt) |
The container_executable is resolved as follows:
use_multithread |
ENABLE_AGNOCAST=0 |
ENABLE_AGNOCAST=1 |
|---|---|---|
false |
component_container |
agnocast_component_container |
true |
component_container_mt |
agnocast_component_container_cie |
Examples (XML)#
Basic usage with a single node:
```xml
Using a component container with multi-threading:
```xml
Examples (Python)#
A Python launch file (agnocast_env.launch.py) is also provided with the same functionality. It sets the same launch configurations (ld_preload_value, container_package, container_executable) that can be referenced via LaunchConfiguration.
Basic usage with a single node:
```python from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch.substitutions import PathJoinSubstitution from launch_ros.actions import Node from launch_ros.substitutions import FindPackageShare
def generate_launch_description(): agnocast_env = IncludeLaunchDescription( PythonLaunchDescriptionSource( PathJoinSubstitution([ FindPackageShare("autoware_agnocast_wrapper"), "launch", "agnocast_env.launch.py", ]) ), )
my_node = Node(
package="my_package",
executable="my_node",
name="my_node",
additional_env={"LD_PRELOAD": LaunchConfiguration("ld_preload_value")},
)
return LaunchDescription([agnocast_env, my_node])
```
Using a component container with multi-threading:
```python from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch.substitutions import LaunchConfiguration from launch.substitutions import PathJoinSubstitution from launch_ros.actions import ComposableNodeContainer from launch_ros.substitutions import FindPackageShare
def generate_launch_description(): agnocast_env = IncludeLaunchDescription( PythonLaunchDescriptionSource( PathJoinSubstitution([ FindPackageShare("autoware_agnocast_wrapper"), "launch", "agnocast_env.launch.py", ]) ), launch_arguments={"use_multithread": "true"}.items(), )
container = ComposableNodeContainer(
name="my_container",
namespace="",
package=LaunchConfiguration("container_package"),
executable=LaunchConfiguration("container_executable"),
additional_env={"LD_PRELOAD": LaunchConfiguration("ld_preload_value")},
composable_node_descriptions=[],
)
return LaunchDescription([agnocast_env, container])
```
This ensures that only the intended nodes receive the heaphook, rather than all nodes in the launch tree.