Skip to content
Merged
8 changes: 8 additions & 0 deletions LibCarla/cmake/fast_dds/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ file(GLOB libcarla_carla_fastdds_headers
"${libcarla_source_path}/carla/ros2/subscribers/*.h"
"${libcarla_source_path}/carla/ros2/listeners/*.h"
"${libcarla_source_path}/carla/ros2/types/*.h"
"${libcarla_source_path}/carla/ros2/types/msg/*.h"
"${libcarla_source_path}/carla/ros2/dds/*.h"
"${libcarla_source_path}/carla/ros2/dds/fastdds/*.h"
)
install(FILES ${libcarla_carla_fastdds_headers} DESTINATION include/carla/ros2)

Expand All @@ -30,6 +33,7 @@ file(GLOB libcarla_fastdds_sources
if (LIBCARLA_BUILD_RELEASE)
add_library(carla_fastdds STATIC ${libcarla_fastdds_sources})

target_compile_definitions(carla_fastdds PRIVATE CARLA_ROS2_DDS_FASTDDS)
target_compile_options(carla_fastdds PRIVATE -fexceptions)

target_include_directories(carla_fastdds SYSTEM PRIVATE
Expand All @@ -48,11 +52,15 @@ if (LIBCARLA_BUILD_DEBUG)

add_library(carla_fastdds_debug STATIC ${libcarla_fastdds_sources})

target_compile_definitions(carla_fastdds_debug PRIVATE CARLA_ROS2_DDS_FASTDDS)
target_compile_options(carla_fastdds_debug PRIVATE -fexceptions)

target_include_directories(carla_fastdds_debug SYSTEM PRIVATE
"${BOOST_INCLUDE_PATH}"
"${RPCLIB_INCLUDE_PATH}")

target_include_directories(carla_fastdds_debug PRIVATE "${FASTDDS_INCLUDE_PATH}")
target_include_directories(carla_fastdds_debug PRIVATE "${libcarla_source_path}/carla/ros2")
install(TARGETS carla_fastdds_debug DESTINATION lib)
set_target_properties(carla_fastdds_debug PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_DEBUG}")
target_compile_definitions(carla_fastdds_debug PUBLIC -DBOOST_ASIO_ENABLE_BUFFER_DEBUGGING)
Expand Down
11 changes: 11 additions & 0 deletions LibCarla/cmake/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ foreach(target ${build_targets})
target_include_directories(${target} PRIVATE
"${libcarla_source_path}/test")

# Server tests exercise CDR serialization (CdrSerialization.h) and
# GenericCdrPubSubType (inherits TopicDataType from fastrtps).
# Enable exceptions because Fast-CDR templates use try/catch internally.
if (CMAKE_BUILD_TYPE STREQUAL "Server")
target_include_directories(${target} SYSTEM PRIVATE "${FASTDDS_INCLUDE_PATH}")
target_compile_options(${target} PRIVATE -fexceptions)
target_link_libraries(${target} "${FASTDDS_LIB_PATH}/libfastrtps.a")
target_link_libraries(${target} "${FASTDDS_LIB_PATH}/libfastcdr.a")
target_link_libraries(${target} "${FASTDDS_LIB_PATH}/libfoonathan_memory-0.7.3.a")
endif()

if (WIN32)
target_link_libraries(${target} "gtest_main.lib")
target_link_libraries(${target} "gtest.lib")
Expand Down
80 changes: 80 additions & 0 deletions LibCarla/source/carla/ros2/dds/DDSMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#pragma once

#include <string>

namespace carla {
namespace ros2 {

/// Enumeration of available DDS middleware implementations.
/// Passed to ROS2::Enable() to select the middleware at startup.
/// Once set, the middleware cannot be changed without restarting.
enum class DDSMiddleware {
FastDDS,
CycloneDDS
};

/// Convert a DDSMiddleware enum value to a readable string.
inline const char* DDSMiddlewareToString(DDSMiddleware middleware) {
switch (middleware) {
case DDSMiddleware::FastDDS:
return "FastDDS";
case DDSMiddleware::CycloneDDS:
return "CycloneDDS";
}
return "Unknown";
}

/// Result of parsing a middleware name string.
struct DDSMiddlewareParseResult {
bool valid;
DDSMiddleware middleware;
};

/// Parse a middleware name string (lowercase). Returns {true, middleware} on match,
/// {false, FastDDS} for unrecognized values.
inline DDSMiddlewareParseResult DDSMiddlewareFromString(const std::string& name) {
if (name == "fastdds") {
return {true, DDSMiddleware::FastDDS};
}
if (name == "cyclonedds") {
return {true, DDSMiddleware::CycloneDDS};
}
return {false, DDSMiddleware::FastDDS};
}

/// Return a readable list of middleware implementations compiled into this binary.
inline std::string GetAvailableMiddlewareString() {
std::string result;
#if defined(CARLA_ROS2_DDS_FASTDDS)
result += "FastDDS";
#endif
#if defined(CARLA_ROS2_DDS_CYCLONEDDS)
if (!result.empty()) {
result += ", ";
}
result += "CycloneDDS";
#endif
if (result.empty()) {
result = "none";
}
return result;
}

/// Mangle a DDS type name into the ROS2-compatible format.
/// "sensor_msgs::msg::Image" becomes "sensor_msgs::msg::dds_::Image_".
/// A bare name like "Image" becomes "dds_::Image_".
inline std::string ToROS2DDSTypeName(const std::string& dds_type_name) {
auto pos = dds_type_name.rfind("::");
if (pos == std::string::npos) {
return "dds_::" + dds_type_name + "_";
}
return dds_type_name.substr(0, pos) +
"::dds_::" + dds_type_name.substr(pos + 2) + "_";
}

} // namespace ros2
} // namespace carla
146 changes: 146 additions & 0 deletions LibCarla/source/carla/ros2/dds/DDSMiddlewareFactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#pragma once

#include <memory>
#include <string>

#include "carla/ros2/dds/DDSMiddleware.h"
#include "carla/ros2/dds/IDDSPublisherMiddleware.h"
#include "carla/ros2/dds/IDDSSubscriberMiddleware.h"
#include "carla/Logging.h"

#if defined(CARLA_ROS2_DDS_FASTDDS) && !defined(CARLA_ROS2_DDS_TESTING)
# include "carla/ros2/dds/fastdds/FastDDSPublisherMiddleware.h"
# include "carla/ros2/dds/fastdds/FastDDSSubscriberMiddleware.h"
#endif

#if defined(CARLA_ROS2_DDS_CYCLONEDDS) && !defined(CARLA_ROS2_DDS_TESTING)
# include "carla/ros2/dds/cyclonedds/CycloneDDSPublisherMiddleware.h"
# include "carla/ros2/dds/cyclonedds/CycloneDDSSubscriberMiddleware.h"
#endif

namespace carla {
namespace ros2 {

/// Factory that creates DDS publisher/subscriber middleware based on the active middleware selection.
/// The middleware is set once at startup via SetMiddleware() before any DDS entities are created.
/// After the first entity is created, changing the middleware has undefined behavior.
class DDSMiddlewareFactory {
public:
/// Select the DDS middleware for all subsequent publisher/subscriber creation.
/// Must be called before any publisher or subscriber is initialized.
static void SetMiddleware(DDSMiddleware middleware) {
GetActiveMiddleware() = middleware;
}

/// @return The currently selected DDS middleware.
static DDSMiddleware GetMiddleware() {
return GetActiveMiddleware();
}

/// Check whether a specific middleware was compiled into this binary.
static bool IsMiddlewareAvailable(DDSMiddleware middleware) {
switch (middleware) {
case DDSMiddleware::FastDDS:
#if defined(CARLA_ROS2_DDS_FASTDDS)
return true;
#else
return false;
#endif
case DDSMiddleware::CycloneDDS:
#if defined(CARLA_ROS2_DDS_CYCLONEDDS)
return true;
#else
return false;
#endif
}
return false;
}

/// Result of middleware resolution — whether resolution succeeded and which middleware to use.
struct MiddlewareResolution {
bool success;
DDSMiddleware middleware;
};

/// Resolve the requested middleware strictly — no fallback to other middleware.
/// Returns {true, requested} if available, {false, requested} otherwise.
static MiddlewareResolution ResolveMiddleware(DDSMiddleware requested) {
if (IsMiddlewareAvailable(requested)) {
return {true, requested};
}
return {false, requested};
}

/// Return a readable list of middleware implementations compiled into this binary.
/// Delegates to the free function in DDSMiddleware.h.
static std::string GetAvailableMiddlewareString() {
return carla::ros2::GetAvailableMiddlewareString();
}

/// Create a publisher middleware for traits type T.
/// T must provide:
/// T::msg_type — the message type
template<typename T>
static std::unique_ptr<IDDSPublisherMiddleware> CreatePublisher() {
switch (GetActiveMiddleware()) {
case DDSMiddleware::FastDDS:
#if defined(CARLA_ROS2_DDS_FASTDDS) && !defined(CARLA_ROS2_DDS_TESTING)
return std::unique_ptr<IDDSPublisherMiddleware>(
new FastDDSPublisherMiddleware<T>());
#else
log_error("DDSMiddlewareFactory: FastDDS not compiled in");
return nullptr;
#endif
case DDSMiddleware::CycloneDDS:
#if defined(CARLA_ROS2_DDS_CYCLONEDDS) && !defined(CARLA_ROS2_DDS_TESTING)
return std::unique_ptr<IDDSPublisherMiddleware>(
new CycloneDDSPublisherMiddleware<T>());
#else
log_error("DDSMiddlewareFactory: CycloneDDS not compiled in");
return nullptr;
#endif
}
return nullptr;
}

/// Create a subscriber middleware for traits type S.
/// S must provide:
/// S::msg_type — the message type
template<typename S>
static std::unique_ptr<IDDSSubscriberMiddleware> CreateSubscriber() {
switch (GetActiveMiddleware()) {
case DDSMiddleware::FastDDS:
#if defined(CARLA_ROS2_DDS_FASTDDS) && !defined(CARLA_ROS2_DDS_TESTING)
return std::unique_ptr<IDDSSubscriberMiddleware>(
new FastDDSSubscriberMiddleware<S>());
#else
log_error("DDSMiddlewareFactory: FastDDS not compiled in");
return nullptr;
#endif
case DDSMiddleware::CycloneDDS:
#if defined(CARLA_ROS2_DDS_CYCLONEDDS) && !defined(CARLA_ROS2_DDS_TESTING)
return std::unique_ptr<IDDSSubscriberMiddleware>(
new CycloneDDSSubscriberMiddleware<S>());
#else
log_error("DDSMiddlewareFactory: CycloneDDS not compiled in");
return nullptr;
#endif
}
return nullptr;
}

private:
/// Returns reference to the active middleware selection
/// (function-local static for C++11 thread safety).
static DDSMiddleware& GetActiveMiddleware() {
static DDSMiddleware active_middleware = DDSMiddleware::FastDDS;
return active_middleware;
}
};

} // namespace ros2
} // namespace carla
37 changes: 37 additions & 0 deletions LibCarla/source/carla/ros2/dds/IDDSPublisherMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#pragma once

#include <string>

namespace carla {
namespace ros2 {

/// Type-erased abstract interface for a DDS publisher middleware.
/// Concrete implementations handle all vendor-specific DDS entity creation,
/// type registration, and data writing.
class IDDSPublisherMiddleware {
public:
virtual ~IDDSPublisherMiddleware() = default;

/// Initialize DDS entities (participant, publisher, topic, writer).
/// @param topic_name Full DDS topic name including "rt/" prefix.
/// @return true on success.
virtual bool Init(const std::string& topic_name) = 0;

/// Serialize and write a message to the DDS network.
/// @param message_data Pointer to the message object (type-erased, cast internally).
/// @return true if the write succeeded.
virtual bool Publish(void* message_data) = 0;

/// @return true if at least one subscriber is matched.
virtual bool IsAlive() const = 0;

/// @return The DDS topic name this publisher is bound to.
virtual std::string GetTopicName() const = 0;
};

} // namespace ros2
} // namespace carla
38 changes: 38 additions & 0 deletions LibCarla/source/carla/ros2/dds/IDDSSubscriberMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#pragma once

#include <string>

namespace carla {
namespace ros2 {

/// Type-erased abstract interface for a DDS subscriber middleware.
/// Concrete implementations write received messages directly into the caller-provided
/// storage (message_ptr / new_message_flag) to avoid an extra copy.
class IDDSSubscriberMiddleware {
public:
virtual ~IDDSSubscriberMiddleware() = default;

/// Initialize DDS entities (participant, subscriber, topic, reader).
/// The middleware writes incoming messages to *message_ptr and sets *new_message_flag = true.
/// @param topic_name Full DDS topic name.
/// @param message_ptr Pointer to the message storage owned by SubscriberImpl<S>.
/// @param new_message_flag Pointer to the new-message flag owned by SubscriberImpl<S>.
/// @return true on success.
virtual bool Init(
const std::string& topic_name,
void* message_ptr,
bool* new_message_flag) = 0;

/// @return true if at least one publisher is matched.
virtual bool IsAlive() const = 0;

/// @return The DDS topic name this subscriber is bound to.
virtual std::string GetTopicName() const = 0;
};

} // namespace ros2
} // namespace carla
Loading