Skip to content

Commit d3f2a45

Browse files
committed
feat(LibCarla/ros2): add CycloneDDS CDR middleware, fix FastDDS large-message publish, add ROS2 smoke test
Replace CycloneDDS publisher/subscriber stubs with real implementations that transport raw CDR bytes via dds_writecdr() / dds_takecdr(), bypassing the CycloneDDS type system entirely. No IDL-generated files, no cyclonedds-cxx. Add CycloneDDSSertype.h, a custom ddsi_sertype + ddsi_serdata for raw CDR passthrough. CDR bytes (including the 4-byte DDS encapsulation header) are stored inline after the ddsi_serdata header in a single malloc block. Full ddsi_serdata_ops (15 entries) and ddsi_sertype_ops (14 entries) tables are implemented against CycloneDDS 0.10.5. Public API: carla_cdr_create_topic() registers the sertype with a participant, carla_cdr_wrap() wraps pre-serialized CDR in a ddsi_serdata* for dds_writecdr(), carla_cdr_data()/carla_cdr_size() expose received bytes for deserialize_from_cdr(). CycloneDDSPublisherMiddleware<T>: Init() creates participant/topic/writer, Publish() calls serialize_to_cdr(*msg) then dds_writecdr(). CycloneDDSSubscriberMiddleware<T>: Init() creates participant/topic/reader with data_available listener, listener calls dds_takecdr() then deserialize_from_cdr() into the caller-supplied message pointer. Both FastDDS and CycloneDDS are always built when --ros2 is specified. FastDDS is a hard dependency (libfastcdr.a is used by CdrSerialization.h for both backends). Setup.sh downloads and builds both libraries unconditionally, LibCarla/cmake/CMakeLists.txt unconditionally adds both fast_dds and cyclone_dds subdirs. New LibCarla/cmake/cyclone_dds/CMakeLists.txt builds libcarla_cyclonedds.a linking libddsc.a + libfastcdr.a. Add -DBUILD_DDSPERF=OFF to CycloneDDS cmake flags in Setup.sh to prevent ddsperf build failure when -DBUILD_IDLC=OFF (ddsperf depends on idlc). Fix GenericCdrPubSubType::getSerializedSizeProvider() to return the actual per-instance serialized size instead of a fixed CdrTopicInfo::max_serialized_size() value. The fixed value (648 bytes for Image, 27597 for PointCloud2) was far smaller than real camera frames (~1-8 MB) or LiDAR scans (~1-20 MB), causing FastDDS DataWriter::write() to fail with RETCODE_ERROR (code 1) on every tick. Add cdr_serialized_size() to CdrSerialization.h to compute the actual size, and change serialize() to use an auto-growing FastBuffer to eliminate the fixed-size buffer constraint entirely. Fix MultiStreamState::IsEnabledForROS() to check the _enable_for_ros set directly instead of iterating sessions that all return false from the default virtual implementation. The Python is_enabled_for_ros() API was always returning false even after enable_for_ros() because of this. Fix CarlaCameraPublisher.cpp and CarlaRadarPublisher.cpp: add explicit #include <cmath>, the CycloneDDS build exposed a pre-existing missing include (FastDDS headers were providing std::tan/sin/cos transitively). Add PythonAPI/test/smoke/test_ros2.py: two smoke tests for the native ROS2 publish path. test_ros2_api verifies the enable/disable/is_enabled lifecycle. test_ros2_sensor_publish spawns an 800x600 RGB camera and LiDAR on a vehicle, enables ROS2, ticks 20 synchronous frames, and asserts Python callbacks fire, exercising the full GenericCdrPubSubType::serialize() -> DataWriter path with realistic large payloads. Tests: 120/120 server + 56/56 client. 2/2 smoke tests (smoke.test_ros2).
1 parent 0a9ca58 commit d3f2a45

File tree

16 files changed

+1020
-53
lines changed

16 files changed

+1020
-53
lines changed

LibCarla/cmake/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ elseif (CMAKE_BUILD_TYPE STREQUAL "Server")
2626
elseif (CMAKE_BUILD_TYPE STREQUAL "Pytorch")
2727
add_subdirectory("pytorch")
2828
elseif (CMAKE_BUILD_TYPE STREQUAL "ros2")
29-
add_subdirectory("fast_dds")
29+
add_subdirectory("fast_dds")
30+
add_subdirectory("cyclone_dds")
3031
else ()
3132
message(FATAL_ERROR "Unknown build type '${CMAKE_BUILD_TYPE}'")
3233
endif ()
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
cmake_minimum_required(VERSION 3.5.1)
2+
project(libcarla_cyclonedds)
3+
4+
# Install headers.
5+
6+
file(GLOB libcarla_carla_cyclonedds_headers
7+
"${libcarla_source_path}/carla/ros2/*.h"
8+
"${libcarla_source_path}/carla/ros2/publishers/*.h"
9+
"${libcarla_source_path}/carla/ros2/subscribers/*.h"
10+
"${libcarla_source_path}/carla/ros2/listeners/*.h"
11+
"${libcarla_source_path}/carla/ros2/types/*.h"
12+
"${libcarla_source_path}/carla/ros2/types/msg/*.h"
13+
"${libcarla_source_path}/carla/ros2/dds/*.h"
14+
"${libcarla_source_path}/carla/ros2/dds/cyclonedds/*.h"
15+
)
16+
install(FILES ${libcarla_carla_cyclonedds_headers} DESTINATION include/carla/ros2)
17+
18+
file(GLOB cyclonedds_dependencies "${CYCLONEDDS_LIB_PATH}/*.a")
19+
install(FILES ${cyclonedds_dependencies} DESTINATION lib)
20+
21+
file(GLOB libcarla_cyclonedds_sources
22+
"${libcarla_source_path}/carla/ros2/*.cpp"
23+
"${libcarla_source_path}/carla/ros2/publishers/*.cpp"
24+
"${libcarla_source_path}/carla/ros2/subscribers/*.cpp"
25+
"${libcarla_source_path}/carla/ros2/listeners/*.cpp"
26+
"${libcarla_source_path}/carla/ros2/types/*.cpp")
27+
28+
# ==============================================================================
29+
# Create targets for debug and release in the same build type.
30+
# ==============================================================================
31+
32+
if (LIBCARLA_BUILD_RELEASE)
33+
add_library(carla_cyclonedds STATIC ${libcarla_cyclonedds_sources})
34+
35+
target_compile_definitions(carla_cyclonedds PRIVATE CARLA_ROS2_DDS_CYCLONEDDS)
36+
target_compile_options(carla_cyclonedds PRIVATE -fexceptions)
37+
38+
target_include_directories(carla_cyclonedds SYSTEM PRIVATE
39+
"${BOOST_INCLUDE_PATH}"
40+
"${RPCLIB_INCLUDE_PATH}")
41+
42+
# CycloneDDS C headers (dds/dds.h, dds/ddsi/*.h)
43+
target_include_directories(carla_cyclonedds PRIVATE "${CYCLONEDDS_INCLUDE_PATH}")
44+
# Fast-CDR headers (fastcdr/Cdr.h) — used by CdrSerialization.h
45+
target_include_directories(carla_cyclonedds PRIVATE "${FASTDDS_INCLUDE_PATH}")
46+
47+
target_link_libraries(carla_cyclonedds
48+
"${CYCLONEDDS_LIB_PATH}/libddsc.a"
49+
"${FASTDDS_LIB_PATH}/libfastcdr.a")
50+
install(TARGETS carla_cyclonedds DESTINATION lib)
51+
set_target_properties(carla_cyclonedds PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_RELEASE}")
52+
53+
endif()
54+
55+
if (LIBCARLA_BUILD_DEBUG)
56+
57+
add_library(carla_cyclonedds_debug STATIC ${libcarla_cyclonedds_sources})
58+
59+
target_compile_definitions(carla_cyclonedds_debug PRIVATE CARLA_ROS2_DDS_CYCLONEDDS)
60+
target_compile_options(carla_cyclonedds_debug PRIVATE -fexceptions)
61+
62+
target_include_directories(carla_cyclonedds_debug SYSTEM PRIVATE
63+
"${BOOST_INCLUDE_PATH}"
64+
"${RPCLIB_INCLUDE_PATH}")
65+
66+
target_include_directories(carla_cyclonedds_debug PRIVATE "${CYCLONEDDS_INCLUDE_PATH}")
67+
target_include_directories(carla_cyclonedds_debug PRIVATE "${FASTDDS_INCLUDE_PATH}")
68+
69+
target_link_libraries(carla_cyclonedds_debug
70+
"${CYCLONEDDS_LIB_PATH}/libddsc.a"
71+
"${FASTDDS_LIB_PATH}/libfastcdr.a")
72+
install(TARGETS carla_cyclonedds_debug DESTINATION lib)
73+
set_target_properties(carla_cyclonedds_debug PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS_DEBUG}")
74+
target_compile_definitions(carla_cyclonedds_debug PUBLIC -DBOOST_ASIO_ENABLE_BUFFER_DEBUGGING)
75+
76+
endif()

LibCarla/source/carla/ros2/dds/cyclonedds/CycloneDDSPublisherMiddleware.h

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,131 @@
55
#pragma once
66

77
#include "carla/ros2/dds/IDDSPublisherMiddleware.h"
8+
#include "carla/ros2/dds/cyclonedds/CycloneDDSSertype.h"
9+
#include "carla/ros2/types/CdrSerialization.h"
10+
#include "carla/ros2/types/CdrTopicInfo.h"
811
#include "carla/Logging.h"
912

13+
#ifndef CARLA_ROS2_DDS_TESTING
14+
#ifdef CARLA_ROS2_DDS_CYCLONEDDS
15+
16+
#include <dds/dds.h>
17+
#include <dds/ddsi/ddsi_serdata.h>
18+
#include <dds/ddsi/ddsi_sertype.h>
19+
1020
namespace carla {
1121
namespace ros2 {
1222

13-
/// CycloneDDS implementation of IDDSPublisherMiddleware (stub).
23+
/// CycloneDDS implementation of IDDSPublisherMiddleware.
1424
///
15-
/// This is a placeholder that compiles and satisfies the factory interface
16-
/// but does not contain a real CycloneDDS implementation. All operations
17-
/// log an error and return failure. The real implementation will replace
18-
/// this file once CycloneDDS types and the C API are available.
25+
/// Serializes msg::* POD structs to CDR via CdrSerialization.h and
26+
/// publishes them as raw CDR bytes using dds_writecdr(). No IDL-generated
27+
/// type files or CycloneDDS C++ bindings are required.
1928
///
2029
/// Parameterized on a traits type T that provides:
21-
/// T::msg_type — middleware-neutral POD message struct
30+
/// T::msg_type — the message type (a carla::ros2::msg::* POD struct)
2231
template<typename T>
2332
class CycloneDDSPublisherMiddleware : public IDDSPublisherMiddleware {
2433
public:
34+
using msg_type = typename T::msg_type;
35+
36+
~CycloneDDSPublisherMiddleware() override {
37+
if (_participant != DDS_ENTITY_NIL) {
38+
dds_delete(_participant); // cascades to writer, topic, sertype
39+
}
40+
}
41+
2542
bool Init(const std::string& topic_name) override {
26-
log_error("CycloneDDSPublisherMiddleware: stub — not yet implemented "
27-
"(topic '", topic_name, "')");
28-
return false;
43+
_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, nullptr, nullptr);
44+
if (_participant < 0) {
45+
log_error("CycloneDDSPublisherMiddleware: Failed to create participant "
46+
"(topic '", topic_name, "', code:", _participant, ")");
47+
return false;
48+
}
49+
50+
const char* type_name = CdrTopicInfo<msg_type>::type_name();
51+
_topic = carla_cdr_create_topic(
52+
_participant, topic_name.c_str(), type_name, &_sertype);
53+
if (_topic < 0) {
54+
log_error("CycloneDDSPublisherMiddleware: Failed to create topic '",
55+
topic_name, "' (code:", _topic, ")");
56+
return false;
57+
}
58+
59+
dds_qos_t* qos = dds_create_qos();
60+
dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE,
61+
DDS_SECS(1));
62+
dds_qset_history(qos, DDS_HISTORY_KEEP_LAST, 1);
63+
dds_listener_t* listener = dds_create_listener(this);
64+
dds_lset_publication_matched(listener, carla_on_publication_matched);
65+
66+
_writer = dds_create_writer(_participant, _topic, qos, listener);
67+
dds_delete_listener(listener);
68+
dds_delete_qos(qos);
69+
if (_writer < 0) {
70+
log_error("CycloneDDSPublisherMiddleware: Failed to create writer "
71+
"(topic '", topic_name, "', code:", _writer, ")");
72+
return false;
73+
}
74+
75+
_topic_name = topic_name;
76+
return true;
2977
}
3078

31-
bool Publish(void* /*message_data*/) override {
32-
return false;
79+
bool Publish(void* message_data) override {
80+
const msg_type* msg = static_cast<const msg_type*>(message_data);
81+
std::vector<uint8_t> cdr = serialize_to_cdr(*msg);
82+
83+
struct ddsi_serdata* sd = carla_cdr_wrap(
84+
_sertype,
85+
cdr.data(),
86+
static_cast<uint32_t>(cdr.size()));
87+
if (!sd) {
88+
log_error("CycloneDDSPublisherMiddleware::Publish (", _topic_name,
89+
"): allocation failed");
90+
return false;
91+
}
92+
93+
dds_return_t rc = dds_writecdr(_writer, sd);
94+
ddsi_serdata_unref(sd);
95+
if (rc != DDS_RETCODE_OK) {
96+
log_error("CycloneDDSPublisherMiddleware::Publish (", _topic_name,
97+
"): dds_writecdr failed (code:", rc, ")");
98+
return false;
99+
}
100+
return true;
33101
}
34102

35103
bool IsAlive() const override {
36-
return false;
104+
return _alive;
37105
}
38106

39107
std::string GetTopicName() const override {
40-
return {};
108+
return _topic_name;
109+
}
110+
111+
private:
112+
static void carla_on_publication_matched(
113+
dds_entity_t /*writer*/,
114+
const dds_publication_matched_status_t status,
115+
void* arg)
116+
{
117+
CycloneDDSPublisherMiddleware* self =
118+
static_cast<CycloneDDSPublisherMiddleware*>(arg);
119+
self->_alive = (status.current_count > 0);
41120
}
121+
122+
dds_entity_t _participant { DDS_ENTITY_NIL };
123+
dds_entity_t _topic { DDS_ENTITY_NIL };
124+
dds_entity_t _writer { DDS_ENTITY_NIL };
125+
struct ddsi_sertype* _sertype { nullptr };
126+
127+
std::string _topic_name;
128+
bool _alive { false };
42129
};
43130

44131
} // namespace ros2
45132
} // namespace carla
133+
134+
#endif // CARLA_ROS2_DDS_CYCLONEDDS
135+
#endif // !CARLA_ROS2_DDS_TESTING

0 commit comments

Comments
 (0)