Skip to content

feat(LibCarla/ros2): [1/7] add DDS middleware abstraction layer for multi-vendor support - #9608

Merged
LuisPovedaCano merged 1 commit into
carla-simulator:ue4-devfrom
JArmandoAnaya:feature/dds-middleware-abstraction-layer
Apr 7, 2026
Merged

feat(LibCarla/ros2): [1/7] add DDS middleware abstraction layer for multi-vendor support#9608
LuisPovedaCano merged 1 commit into
carla-simulator:ue4-devfrom
JArmandoAnaya:feature/dds-middleware-abstraction-layer

Conversation

@JArmandoAnaya

@JArmandoAnaya JArmandoAnaya commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Introduce a strategy-pattern abstraction that decouples PublisherImpl and SubscriberImpl from the FastDDS API, enabling future integration of additional DDS middleware implementations (e.g. CycloneDDS).

ros2-carla-pr1

New abstraction layer (LibCarla/source/carla/ros2/dds/):

  • DDSMiddleware enum and string conversion utilities
  • IDDSPublisherMiddleware / IDDSSubscriberMiddleware pure virtual interfaces
  • DDSMiddlewareFactory with thread-safe middleware selection and creation
  • FastDDSPublisherMiddleware / FastDDSSubscriberMiddleware<S> template implementations behind the new interfaces
  • FastDDSTypeMap identity-mapping type traits (to be replaced with real conversions when POD message types are introduced)

Modified files:

  • PublisherImpl.h: replaced direct FastDDS inheritance and members with delegation to IDDSPublisherMiddleware via the factory (public API unchanged)
  • SubscriberImpl.h: same pattern for subscriber side
  • cmake/fast_dds/CMakeLists.txt: added CARLA_ROS2_DDS_FASTDDS compile definition, new header globs, and fixed missing include directories in the debug build target

No behavior change - FastDDS remains the only compiled middleware. Concrete publishers and subscribers are unaffected.

Description

This PR introduces a DDS middleware abstraction layer that decouples PublisherImpl and SubscriberImpl from the FastDDS API, laying the groundwork for supporting multiple DDS middleware implementations in CARLA's native ROS2 integration.

This is the first in a series of PRs that incrementally implements the decoupled DDS middleware architecture originally prototyped in draft PR #9589. The end goal is to support multiple DDS backends, starting with CycloneDDS, so users can choose the most appropriate middleware for their deployment.

These changes do not affect current behavior. FastDDS remains the only compiled middleware, and all concrete publishers and subscribers remain unchanged.

What's new

Abstraction layer (LibCarla/source/carla/ros2/dds/):

  • DDSMiddleware - enum and string conversion utilities for middleware selection
  • IDDSPublisherMiddleware / IDDSSubscriberMiddleware - type-erased pure virtual interfaces
  • DDSMiddlewareFactory - static factory with thread-safe middleware selection and creation via SetMiddleware(), CreatePublisher<T>(), and CreateSubscriber<S>()
  • FastDDSPublisherMiddleware<T> / FastDDSSubscriberMiddleware<S> - FastDDS implementations behind the new interfaces
  • FastDDSTypeMap<T> - identity-mapping type traits that will be replaced with real conversions when POD message types are introduced in a future PR

Modified files:

  • PublisherImpl.h - removed direct FastDDS inheritance and raw DDS entity members; now delegates to std::unique_ptr<IDDSPublisherMiddleware> via the factory while keeping the public API unchanged
  • SubscriberImpl.h - same delegation pattern for the subscriber side
  • cmake/fast_dds/CMakeLists.txt - added CARLA_ROS2_DDS_FASTDDS compile definition for both release and debug targets, added new header globs for dds/ and dds/fastdds/, and fixed pre-existing missing include directories in the debug build target

Design decisions

  • "Middleware" naming instead of "Backend" to align with ROS2 ecosystem terminology (RMW = ROS Middleware)
  • Strategy pattern so middleware can be selected once at startup and instantiated through the factory
  • Identity TypeMap because msg_type is currently already the FastDDS type; this will evolve into real conversion logic once POD message types are introduced
  • No CycloneDDS references in this PR to keep the scope focused on the abstraction and FastDDS reimplementation only

PR series

This PR is part of the DDS middleware decoupling series (#9294). Each PR in the chain inherits the commits of all prior PRs. The Commits column lists only the commits introduced by that PR, and the Files column counts only the files changed by those new commits.

# PR Title Branch Commits Files
1/7 #9608 DDS middleware abstraction layer (this PR) feature/dds-middleware-abstraction-layer 0ab35ee6 10
2/7 #9612 POD message types, FastDDS conversions, and TypeMap specializations feature/ros2-pod-types-and-fastdds-conversions e665d2af 39
3/7 #9619 Migrate publishers and subscribers to POD message types feature/ros2-publishers-pod-types-migration 98b6f7aa 27
4/7 #9620 CycloneDDS enum, factory, stubs, and tests feature/ros2-cyclonedds-enum-factory 2a4a8db3 5
5/7 #9643 Unified CDR serialization + GenericCdrPubSubType feature/ros2-cdr-serialization 5a9dd3b4, 0a9ca585 8
6/7 #9644 CycloneDDS CDR middleware, UE4 runtime selection, and correctness fixes feature/ros2-cyclonedds-cdr-middleware d3f2a45b, 2d80046b 26
7/7 #9645 Remove legacy FastDDS-generated type files feature/ros2-remove-fastdds-generated-types eea4e0a4 125

Related

Fixes #9294

Where has this been tested?

  • Platform(s): Linux Ubuntu 22.04
  • Python version(s): 3.10, 3.11, 3.12
  • Unreal Engine version(s): UE4

Possible Drawbacks

  • This PR introduces an additional abstraction layer, which slightly increases architectural complexity.
  • The new factory and interface structure adds indirection that may make debugging more involved compared to the previous direct FastDDS coupling.
  • At this stage, the abstraction only wraps FastDDS, so the immediate functional benefit is architectural preparation rather than new user-visible functionality.

Compilation steps

# 1. Setup all dependencies (FastDDS, CycloneDDS, Chrono, etc.)
make setup ARGS="--python-version=3.10,3.11,3.12 --ros2 --target-wheel-platform=manylinux_2_31_x86_64"

# 2. Build LibCarla (server + client, debug + release, both DDS backends)
make LibCarla ARGS="--python-version=3.10,3.11,3.12 --ros2 --target-wheel-platform=manylinux_2_31_x86_64"

# 3. Build PythonAPI
make PythonAPI ARGS="--python-version=3.10,3.11,3.12 --ros2 --target-wheel-platform=manylinux_2_31_x86_64"

# 4. Build CarlaUE4Editor
make CarlaUE4Editor ARGS="--python-version=3.10,3.11,3.12--ros2 --target-wheel-platform=manylinux_2_31_x86_64"

# 5. Build the final package
make package ARGS="--python-version=3.10,3.11,3.12 --ros2 --target-wheel-platform=manylinux_2_31_x86_64 --no-zip"

Test plan

  • Verify make LibCarla ARGS="--ros2" compiles successfully (release build)
  • Verify debug build compiles with the fixed include directories
  • Verify make check.LibCarla passes (no regressions)
  • Verify no concrete publisher/subscriber files were modified (only PublisherImpl.h and SubscriberImpl.h)
  • Verify file count: 10 files changed (7 new + 3 modified)

This change is Reviewable

…ackend support

Introduce a strategy-pattern abstraction that decouples PublisherImpl and
SubscriberImpl from the FastDDS API, enabling future integration of
additional DDS middleware implementations (e.g. CycloneDDS).

New abstraction layer (LibCarla/source/carla/ros2/dds/):
- DDSMiddleware enum and string conversion utilities
- IDDSPublisherMiddleware / IDDSSubscriberMiddleware pure virtual interfaces
- DDSMiddlewareFactory with thread-safe middleware selection and creation
- FastDDSPublisherMiddleware<T> / FastDDSSubscriberMiddleware<S> template
  implementations behind the new interfaces
- FastDDSTypeMap<T> identity-mapping type traits (to be replaced with real
  conversions when POD message types are introduced)

Modified files:
- PublisherImpl.h: replaced direct FastDDS inheritance and members with
  delegation to IDDSPublisherMiddleware via the factory (public API unchanged)
- SubscriberImpl.h: same pattern for subscriber side
- cmake/fast_dds/CMakeLists.txt: added CARLA_ROS2_DDS_FASTDDS compile
  definition, new header globs, and fixed missing include directories in
  the debug build target

No behavior change — FastDDS remains the only compiled middleware.
Concrete publishers and subscribers are unaffected.
@JArmandoAnaya
JArmandoAnaya requested a review from a team as a code owner March 27, 2026 09:17
@update-docs

update-docs Bot commented Mar 27, 2026

Copy link
Copy Markdown

Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would update our CHANGELOG.md based on your changes.

@JArmandoAnaya

Copy link
Copy Markdown
Contributor Author

The smoke tests in the Ubuntu CI/CD are failing, but I'm not sure if this is an internal problem with the GitHub Actions, since I ran the same tests locally and all of them passed without problems. https://github.com/carla-simulator/carla/actions/runs/23674779274/job/69127370062?pr=9608

@JArmandoAnaya JArmandoAnaya changed the title feat(LibCarla/ros2): Adding DDS middleware abstraction layer for multi-vendor support feat(LibCarla/ros2): [1/7] add DDS middleware abstraction layer for multi-vendor support Apr 6, 2026
@JArmandoAnaya

Copy link
Copy Markdown
Contributor Author

Hi to the review team,

The DDS middleware decoupling series tracked in #9294 is now complete. All 7 PRs in the chain are pushed, compile cleanly, pass unit and smoke tests, and build a full package. I would like to request a formal review.

Goal

Decouple CARLA's native ROS2 integration from FastDDS so that a second DDS middleware (CycloneDDS) can be selected at runtime, without duplicating any publisher or subscriber code. End users will be able to pick their middleware with --dds-middleware=fastdds or --dds-middleware=cyclonedds at server startup.

Strategy

The approach is a strategy pattern with unified CDR serialization. Publishers and subscribers never see a DDS vendor type. Instead:

  1. Publishers and subscribers operate on vendor-neutral POD structs (carla::ros2::msg::*).
  2. A single CDR serialization layer (CdrSerialization.h) converts those POD structs to raw CDR bytes using Fast-CDR. This produces XCDR1 LE output that is wire-compatible with every ROS2 distro.
  3. The CDR byte buffer is handed to whichever DDS middleware is selected:
    • FastDDS receives it through a GenericCdrPubSubType<T> that delegates to CdrSerialization.h, no more per-type IDL-generated files.
    • CycloneDDS receives it through a custom ddsi_sertype + ddsi_serdata that calls dds_writecdr() / dds_takecdr(), bypassing the CycloneDDS type system entirely.
  4. The factory (DDSMiddlewareFactory) chooses the implementation at startup. UE4 runtime selection is wired through ROS2::Enable(bool, DDSMiddleware).

The key simplification is that neither backend needs per-vendor generated type files. One CDR serializer, one POD struct per message, two thin transport adapters.

How to navigate the series

The PRs are linear, each branch contains every prior commit. Review them in order. Each PR description now carries the same "PR series" table with commit IDs and file counts so you can see at a glance which commits are new to each PR.

# PR Scope New commits New files
1/7 #9608 DDS middleware abstraction layer (interfaces, factory, FastDDS adapter, PublisherImpl/SubscriberImpl rewrite) 0ab35ee6 10
2/7 #9612 31 vendor-neutral POD message structs, FastDDS conversions, real TypeMap specializations, 45 unit tests e665d2af 39
3/7 #9619 Migrate every publisher and subscriber to the new POD types, remove identity TypeMap specializations 98b6f7aa 27
4/7 #9620 Register CycloneDDS in the enum and factory, compilable stubs, 7 new tests 2a4a8db3 5
5/7 #9643 Unified CDR serialization (CdrSerialization.h, CdrTopicInfo.h) + GenericCdrPubSubType for FastDDS, CDR round-trip tests 5a9dd3b4, 0a9ca585 8
6/7 #9644 Real CycloneDDS CDR middleware via dds_writecdr/dds_takecdr, UE4 runtime middleware selection (--dds-middleware=), CycloneDDS and FastDDS correctness fixes, 5 ROS2 smoke tests d3f2a45b, 2d80046b 26
7/7 #9645 Remove 125 legacy FastDDS-generated type files that are now dead code eea4e0a4 125

Suggested reading order: review 1/7 and 2/7 together (they set up the abstraction and type layer), then 3/7 (mechanical migration, zero behavior change), then 4/7 (small, just registers CycloneDDS). PRs 5/7 and 6/7 contain the core of the new serialization and CycloneDDS transport, they are the most interesting. 7/7 is a pure cleanup of dead code.

Status and testing

  • make LibCarla ARGS="--ros2", passes on all PRs.
  • make check.LibCarla, passes on all PRs (56/56 unit tests on the final PR).
  • make package ARGS="--python-version=3.12 --ros2 ...", passes on the final PR.
  • smoke.test_ros2 (5 tests, covering API lifecycle, camera + LiDAR large-payload publish, radar/DVS/semantic LiDAR, enable/disable cycling, and a 100-tick multi-sensor stress run), 5/5 pass.
  • Tested on Ubuntu 22.04, Python 3.12, UE4.

Thanks for the review.

@youtalk

youtalk commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

As a ROS 2 developer, I am very excited about the DDS multi-vendor support here and look forward to using it soon.

However, I have a question regarding the CARLA community's focus. Why is the community currently concentrating its maintenance efforts on the ue4-dev branch?

@JArmandoAnaya

Copy link
Copy Markdown
Contributor Author

Hi @youtalk, I'm glad you see value in this effort to add multipurpose DDS support, just as I do.

Regarding your comments, I can't speak for everyone who actively contributes to Carla. But I do believe that the ue4-dev branch is still more stable for experimentation and less resource-intensive. Not to mention that it still has the most features. Therefore, for me, at least, the workflow that allows me to iterate an idea more easily and quickly is to develop features like this in the ue4 branch, and if I see that they work, move the change to the ue5 branch. But I agree with you that more effort is needed to finish migrating all the desynchronized development that already existed between the two branches. I also believe that Carla with ue5 should become the final and only version. But the resource consumption challenges it still faces are significant. For now, I can help you review all the changes that are still pending to be migrated from the ue4-dev branch to ue5-dev; after that, development in ue5-dev could improve considerably.

@youtalk

youtalk commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

@JesusAnaya Thank you for your quick response!

But the resource consumption challenges it still faces are significant.

This feedback was a very important notice for me.
I plan to focus on resolving the resource consumption challenges moving forward. I believe this will help accelerate our transition to UE5 development.

@Blyron

Blyron commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

From me the code looks fine. I will add someone to have a look and test, could we add tests in the near future PRs?
Some new unit tests.

@JArmandoAnaya

Copy link
Copy Markdown
Contributor Author

Hello @Blyron, I added new unit tests and smoke tests in the subsequent PRs to this one. But I can see how to add a unit test to this one too if you require it. This pull request series (7 in total) is thought to be merged all, and they are complementing the changes in each step.

@Blyron

Blyron commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

I have seen those. For this step we are ok. But in next steps we will need more exhaustive tests for incoming PRs related with ROS2

@JArmandoAnaya

Copy link
Copy Markdown
Contributor Author

@Blyron, great! Thanks. I'll check any comments in the code review in case the team requests any changes.

@LuisPovedaCano LuisPovedaCano left a comment

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.

Code looks good.
I have tested it.

Merging. I will look for your next PR.

@LuisPovedaCano
LuisPovedaCano merged commit 82c28e2 into carla-simulator:ue4-dev Apr 7, 2026
4 of 8 checks passed
germanros1987 pushed a commit that referenced this pull request Aug 19, 2026
Introduce the vendor-neutral middleware strategy layer of the ROS 2
middleware decoupling series and its FastDDS implementation, compiled
into libcarla-ros2-native.so. Ported from ue4-dev:

- middleware/Middleware.h: the Middleware enum plus the string and
  ROS 2 type-name helpers; the CycloneDDS value and its availability
  branches are present but stay compiled out until the CycloneDDS
  middleware lands (upstream ue4-dev #9608).
- middleware/IPublisherMiddleware.h, ISubscriberMiddleware.h: the
  type-erased publisher/subscriber strategy interfaces. Subscribers
  write received samples straight into caller-owned storage to avoid a
  copy (upstream ue4-dev #9608).
- middleware/MiddlewareFactory.h: creates the active middleware for a
  traits type; each vendor arm is double-gated on its
  CARLA_ROS2_MIDDLEWARE_* macro and CARLA_ROS2_MIDDLEWARE_TESTING so
  the suite exercises the availability logic without linking DDS
  (upstream ue4-dev #9608).
- middleware/ActiveMiddleware.{h,cpp}: a DDS-free bridge
  (SetActiveMiddleware) so ROS2.cpp, the only ROS 2 translation unit in
  carla-server, can select the middleware without any DDS header
  crossing the shared-library boundary. Nothing calls it until the
  cutover; the definition ships now so the shared lib has a translation
  unit that compiles MiddlewareFactory.h and the FastDDS headers with
  the real vendor macros.
- middleware/fastdds/GenericCdrPubSubType.h: one FastDDS TopicDataType
  that serializes every carla::ros2::msg::* struct through the unified
  CdrSerialization.h path, replacing the generated per-type PubSubType
  classes; getSerializedSizeProvider reports the actual instance size
  so variable-length payloads (camera frames, point clouds) are not
  bounded by the static max size (upstream ue4-dev #9643).
- middleware/fastdds/FastDDS{Publisher,Subscriber}Middleware.h: the
  FastDDS strategy implementations. Each endpoint advertises the
  REP-2016 "typehash=RIHS01_<hex>;" USER_DATA so Jazzy RMWs match on
  the REP-2011 type hash (upstream ue4-dev #9681).
- middleware/fastdds/FastDDSSharedParticipant.{h,cpp}: a refcounted
  process-wide DomainParticipant shared across all FastDDS endpoints,
  avoiding the discovery storm that destroying N participants back to
  back caused on shutdown (upstream ue4-dev #9681).

UE5 adaptation: ue5-dev pins FastDDS 2.11.2 with bundled Fast-CDR 1.x,
so GenericCdrPubSubType.h keeps the Fast-CDR 1.x spellings
(eprosima::fastcdr::Cdr::DDS_CDR, getSerializedDataLength()) matching
CdrSerialization.h from the previous PR of the series. The wire format
is classic CDR little-endian and is unchanged.

The existing publishers keep using the generated FastDDS types; the
cutover to this abstraction and the PublisherImpl/SubscriberImpl
rewrite land in the next PR of the series. No behavior change.

(adapted from ue4-dev 82c28e2)
(adapted from ue4-dev f53144c)
(adapted from ue4-dev 02a83ef)
(adapted from ue4-dev c64e8f4)

(cherry picked from commit f6d9a5b)
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