Skip to content

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.

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("output", 10); sub_ = create_subscription( "input", 10, this { / ... / }); }

private: autoware::agnocast_wrapper::Publisher::SharedPtr pub_; autoware::agnocast_wrapper::Subscription::SharedPtr sub_; }; ```

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:

  1. Registers the component with rclcpp_components (for component container support)
  2. Creates a standalone executable that can switch between rclcpp::Node and agnocast::Node at runtime based on the ENABLE_AGNOCAST environment 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, MultiThreadedExecutor
  • AGNOCAST_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
Runtime
ENABLE_AGNOCAST=0
Runtime
ENABLE_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 ApproximateTime synchronization policy is supported (no ExactTime).
  • Maximum 2 message types per Synchronizer.
  • connectInput() is not supported; pass Subscriber references at construction time.

Usage example#

```cpp

include #

using namespace autoware::agnocast_wrapper::message_filters;

// 1. Create subscribers Subscriber image_sub; Subscriber info_sub; image_sub.subscribe(node, "/camera/image", rmw_qos_profile_sensor_data); info_sub.subscribe(node, "/camera/info", rmw_qos_profile_sensor_data);

// 2. Create synchronizer using Policy = sync_policies::ApproximateTime< sensor_msgs::msg::Image, sensor_msgs::msg::CameraInfo>; Synchronizer sync(Policy(10), image_sub, info_sub);

// 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=0 will 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

\((var container_package)" exec="\)(var container_executable)" name="my_container"> ```

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.