Skip to content

Add rosidl_buffer for native Buffer type support#938

Closed
nvcyc wants to merge 1 commit intoros2:rollingfrom
nvcyc:pl/rosidl_buffer
Closed

Add rosidl_buffer for native Buffer type support#938
nvcyc wants to merge 1 commit intoros2:rollingfrom
nvcyc:pl/rosidl_buffer

Conversation

@nvcyc
Copy link
Copy Markdown
Contributor

@nvcyc nvcyc commented Mar 10, 2026

Description

This pull request adds the rosidl_buffer package - core C++ buffer types for the ROS 2 native buffer feature. This package introduces rosidl::Buffer<T>, a polymorphic container that's designed to replace std::vector<T> for uint8[] message fields (to be adopted in later pull requests; we focus on only the core buffer types in this pull request.) The native buffer type enables vendor-specific memory backends (CUDA, ROCm, etc.) while maintaining backward compatibility for existing CPU-based std::vector<T> user code.

This pull request consists of the following key components:

  • Buffer<T>: PIMPL-based container providing std::vector<T>-compatible API. All vector-compatible operations (element access, iterators, modifiers) are CPU-only and throw std::runtime_error for non-CPU backends. Backend management APIs (get_backend_type(), set_impl(), get_impl(), to_vector()) and copy/move semantics work for all backends.
  • BufferImplBase<T>: Minimal abstract base class of buffer implementation. All backend-specific APIs are to be provided by the vendor-specific backend implementations.
  • CpuBufferImpl<T>: CPU-based buffer implementation wrapping std::vector<T>.

Is this user-facing behavior change?

This pull request does not change existing behavior.
When adopted in later pull requests, message types with uint8[] fields (e.g., sensor_msgs/msg/Image.data) will use rosidl::Buffer<uint8_t> instead of std::vector<uint8_t>. For CPU backends (the default), Buffer<T> is a transparent drop-in replacement.

Did you use Generative AI?

Yes. Claude (claude-4.6-opus) via Cursor was used to assist with the std::vector<T> compatibility feature in the rosidl::Buffer<T> class and generate portions of the unit tests.

Additional Information

This package/PL is part of the broader ROS2 native buffer feature introduced in this post.

Signed-off-by: CY Chen <cyc@nvidia.com>

// Test default construction
TEST(TestBuffer, default_construction) {
Buffer<int> buffer;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the goal of the feature is to handle buffers of uint8_t, I think it makes sense to test with that type instead of int.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions. Tests are updated to test uint8_t.

EXPECT_EQ(buffer1.size(), buffer2.size());
EXPECT_EQ(buffer1.get_backend_type(), buffer2.get_backend_type());
for (size_t i = 0; i < buffer1.size(); ++i) {
EXPECT_EQ(buffer1[i], buffer2[i]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we check that they are in different addresses?

Suggested change
EXPECT_EQ(buffer1[i], buffer2[i]);
EXPECT_EQ(buffer1[i], buffer2[i]);
EXPECT_NE(&buffer1[i], &buffer2[i]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in new update.


EXPECT_EQ(buffer1.size(), buffer2.size());
for (size_t i = 0; i < buffer1.size(); ++i) {
EXPECT_EQ(buffer1[i], buffer2[i]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in new update.

std::vector<Point> copied = buffer.to_vector();
EXPECT_DOUBLE_EQ(3.0, copied[0].z);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some tests that check std::runtime_exception is thrown for certain operations when the implementation is not cpu

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test implementation class NonCpuBufferImpl and tests to check the throwing exception cases.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions in this file shall be checked in tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test_c_helpers.cpp for testing functions in c_helpers.cpp.

{
impl_ = std::move(impl);
backend_type_ = backend_type;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is called with "cpu" as the backend type, we should check that the implementation can be cast to CpuBufferImpl

Suggested change
}
void set_impl(
std::unique_ptr<BufferImplBase<T>> impl,
const std::string & backend_type)
{
if ((backend_type == "cpu") && (nullptr == dynamic_cast<CpuBufferImpl<T>*>(impl.get()))) {
throw std::runtime_error("CPU backend shall inherit from CpuBufferImpl.");
}
impl_ = std::move(impl);
backend_type_ = backend_type;
}

Copy link
Copy Markdown
Contributor

@hidmic hidmic Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A follow up question would be, why store the backend identifier here instead of querying the underlying implementation for it? Why not let the underlying implementation decide what it does and doesn't support? You can follow that line of thought and say that rosidl::Buffer could simply forward calls to the implementation +/- CPU backend checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a valid suggestion. I'm updating the code so that the buffer implementation is the source of truth for the buffer backend type.

@ahcorde
Copy link
Copy Markdown
Contributor

ahcorde commented Mar 11, 2026

Pulls: #938
Gist: https://gist.githubusercontent.com/ahcorde/189205809e28fadd8f0c809e165b7f09/raw/f345f50215a60fba5be8164ecd41c2d1ff9df91b/ros2.repos
BUILD args: --packages-above-and-dependencies rosidl_buffer
TEST args: --packages-above rosidl_buffer
ROS Distro: rolling
Job: ci_launcher
ci_launcher ran: https://ci.ros2.org/job/ci_launcher/18438

  • Linux Build Status
  • Linux-aarch64 Build Status
  • Linux-rhel Build Status
  • Windows Build Status

// Type aliases for std::vector compatibility
using value_type = T;
using allocator_type = Allocator;
using size_type = size_t;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include <cstddef>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in new update.

Comment thread rosidl_buffer/package.xml
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>rosidl_buffer</name>
<version>1.0.0</version>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version should match with the current packages 5.1.2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in new update.

Comment thread rosidl_buffer/package.xml
Provides Buffer container type with support for multiple memory backends (CPU, GPU, custom).
</description>

<maintainer email="cyc@nvidia.com">CY Chen</maintainer>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing <author> tag

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in new update.

Copy link
Copy Markdown
Contributor

@hidmic hidmic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass, meta comments.

{
impl_ = std::move(impl);
backend_type_ = backend_type;
}
Copy link
Copy Markdown
Contributor

@hidmic hidmic Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A follow up question would be, why store the backend identifier here instead of querying the underlying implementation for it? Why not let the underlying implementation decide what it does and doesn't support? You can follow that line of thought and say that rosidl::Buffer could simply forward calls to the implementation +/- CPU backend checks.

/// implementations.
/// @param impl Backend implementation instance.
/// @param backend_type Backend identifier string.
void set_impl(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nvcyc meta: hmm, have we considered passing implementation on buffer construction? Allowing swapping implementations on the fly creates race conditions in the other methods.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right and this is also not allowed by my initial design. Removing this function to keep it clean and aligned.

/// Resize a Buffer<uint8_t>. Throws for non-CPU backends.
/// @param buffer_ptr Opaque pointer to an rosidl::Buffer<uint8_t>
/// @param size New size
void rosidl_buffer_uint8_resize(void * buffer_ptr, size_t size);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nvcyc hmm, I would've expected a rosidl_uint8_buffer_t, even if it's just a type erased wrapper for rosidl::Buffer<uint8_t>. Mind to explain the use case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment.
These C helpers are mainly for the C introspection typesupport path (in later PLs) but also they are not intended to be used against non-CPU-based buffers (and hence we throw exception if accessed with these APIs). In C path, the current design is that CPU-based uint8[] will always be the data field in rosidl_runtime_c__uint8__Sequence as it originally does, so we won't have the case where CPU-based utin8[] is stored in rosidl::Buffer in the C path.

For this reason, I think we actually don't need these introspection C helper functions here as they are not supported by backend buffers. I'm optimizing from the C introspection typesupport side to simply call rosidl_buffer_uint8_throw_if_not_cpu() as guards there.

I'll make the changes to keep them simple in my new update.

@nvcyc
Copy link
Copy Markdown
Contributor Author

nvcyc commented Mar 14, 2026

Thanks for your review.

To facilitate the review process with cleaner future PR dependencies, I'm creating a new PR (#941) that's based on a branch in this repo (in contrast to a fork) to replace this one. All the changes for addressing the comments in this PR have been included in the new PR.

Please continue our review in #941 and I'll close this PR subsequently.

@nvcyc nvcyc closed this Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants