CARLA
Loading...
Searching...
No Matches
FastDDSPublisherMiddleware.h
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
2// This work is licensed under the terms of the MIT license.
3// For a copy, see <https://opensource.org/licenses/MIT>.
4
5#pragma once
6
11#include "carla/Logging.h"
12
13#include <fastdds/dds/domain/DomainParticipant.hpp>
14#include <fastdds/dds/topic/Topic.hpp>
15#include <fastdds/dds/topic/TypeSupport.hpp>
16#include <fastdds/dds/publisher/Publisher.hpp>
17#include <fastdds/dds/publisher/DataWriter.hpp>
18#include <fastdds/dds/publisher/DataWriterListener.hpp>
19#include <fastdds/dds/publisher/qos/DataWriterQos.hpp>
20#include <fastdds/dds/core/status/PublicationMatchedStatus.hpp>
21#include <fastdds/dds/topic/qos/TopicQos.hpp>
22#include <fastrtps/attributes/ParticipantAttributes.h>
23#include <fastrtps/qos/QosPolicies.h>
24
25#include <atomic>
26
27namespace carla {
28namespace ros2 {
29
30namespace efd = eprosima::fastdds::dds;
31using erc = eprosima::fastrtps::types::ReturnCode_t;
32
33/// FastDDS implementation of IPublisherMiddleware.
34/// Parameterized on a traits type T that provides:
35/// T::msg_type — the message type (a carla::ros2::msg::* POD struct)
36/// Serialization is handled by GenericCdrPubSubType<msg_type> via CdrSerialization.h.
37///
38/// Uses FastDDSSharedParticipant for a single refcounted DomainParticipant
39/// across all FastDDS endpoints, mirroring CycloneDDS's shared-participant
40/// model. This avoids the discovery storm that occurred when N independent
41/// participants were destroyed back-to-back on ROS2::Shutdown().
42///
43/// Sets USER_DATA QoS on the DataWriter to the REP-2016 KV payload
44/// "typehash=RIHS01_<hex>;" (PID_USER_DATA = 0x002c in DDSI-RTPS v2.5
45/// §9.6.2.2.2) so that Jazzy rmw_cyclonedds_cpp and rmw_fastrtps_cpp can
46/// perform REP-2011 type-hash-based endpoint matching without warning.
47template<typename T>
49 : public IPublisherMiddleware,
51 public:
52 using msg_type = typename T::msg_type;
53
57
59 efd::DataWriter* writer,
60 const efd::PublicationMatchedStatus& info) override {
61 _alive.store(info.current_count > 0, std::memory_order_relaxed);
62 }
63
65 if (_datawriter) {
66 // Detach listener before deletion so that on_publication_matched cannot
67 // fire on a partially-destroyed object.
68 _datawriter->set_listener(nullptr);
69 _publisher->delete_datawriter(_datawriter);
70 }
71 if (_publisher) {
72 _participant->delete_publisher(_publisher);
73 }
74 if (_topic) {
75 _participant->delete_topic(_topic);
76 }
77 if (_participant) {
78 // Release the per-type refcount so unregister_type() fires when the last
79 // user of this TypeSupport goes away, before the shared participant drops.
82 _participant = nullptr;
83 }
84 }
85
86 bool Init(const std::string& topic_name) override {
87 return InitImpl(topic_name, nullptr);
88 }
89
90 bool Init(const std::string& topic_name, const QosProfile& qos) override {
91 return InitImpl(topic_name, &qos);
92 }
93
94 private:
95 /// Shared Init body. When @a qos is non-null the profile overrides the
96 /// FastDDS DataWriter defaults; when null the defaults are kept, preserving
97 /// the pre-QoS behavior for every existing publisher.
98 bool InitImpl(const std::string& topic_name, const QosProfile* qos) {
99 if (_type == nullptr) {
100 log_error("FastDDSPublisherMiddleware: Invalid TypeSupport");
101 return false;
102 }
103
105 if (_participant == nullptr) {
106 log_error("FastDDSPublisherMiddleware: Shared participant unavailable");
107 return false;
108 }
109
110 _type.register_type(_participant);
112
113 efd::PublisherQos pubqos = efd::PUBLISHER_QOS_DEFAULT;
114 _publisher = _participant->create_publisher(pubqos, nullptr);
115 if (_publisher == nullptr) {
116 log_error("FastDDSPublisherMiddleware: Failed to create Publisher");
117 return false;
118 }
119
120 // Multiple endpoints may share the same topic on the shared participant
121 // (e.g. every actor publishes on rt/tf). FastDDS's create_topic rejects a
122 // duplicate name, so probe first and reuse via find_topic if it already
123 // exists; find_topic increments an internal refcount that the matching
124 // delete_topic in the destructor balances.
125 if (_participant->lookup_topicdescription(topic_name) != nullptr) {
126 _topic = _participant->find_topic(topic_name, eprosima::fastrtps::Duration_t{0, 0});
127 } else {
128 efd::TopicQos tqos = efd::TOPIC_QOS_DEFAULT;
129 _topic = _participant->create_topic(topic_name, _type->getName(), tqos);
130 }
131 if (_topic == nullptr) {
132 log_error("FastDDSPublisherMiddleware: Failed to create Topic");
133 return false;
134 }
135
136 efd::DataWriterQos wqos = efd::DATAWRITER_QOS_DEFAULT;
137 wqos.endpoint().history_memory_policy =
138 eprosima::fastrtps::rtps::PREALLOCATED_WITH_REALLOC_MEMORY_MODE;
139
140 // ASYNCHRONOUS_PUBLISH_MODE: write() must never do network I/O on the
141 // calling thread. The FastDDS default (synchronous) runs the whole
142 // RTPS/UDP send inline in write(); with the simulator publishing multi-MB
143 // camera frames from engine threads, UE's task scheduler sees those
144 // threads blocked in sendto(), spins up standby workers and reaps them
145 // again, and the destabilized worker pool stalls every render-thread
146 // ParallelFor (SceneCapture InitViews) -- the tick rate collapses to a
147 // few Hz with CPU and GPU idle. In asynchronous mode write() only
148 // enqueues; the FastDDS flow-controller thread does the sends. The
149 // default (unthrottled) flow controller is intentional: we want the
150 // I/O off-thread, not rate-limited.
151 wqos.publish_mode().kind = efd::ASYNCHRONOUS_PUBLISH_MODE;
152
153 if (qos != nullptr) {
154 wqos.reliability().kind =
156 ? efd::RELIABLE_RELIABILITY_QOS
157 : efd::BEST_EFFORT_RELIABILITY_QOS;
158 wqos.durability().kind =
160 ? efd::TRANSIENT_LOCAL_DURABILITY_QOS
161 : efd::VOLATILE_DURABILITY_QOS;
162 wqos.history().kind =
164 ? efd::KEEP_LAST_HISTORY_QOS
165 : efd::KEEP_ALL_HISTORY_QOS;
166 wqos.history().depth = qos->history_depth;
167 }
168
169 // Set USER_DATA (PID_USER_DATA = 0x002c per OMG DDSI-RTPS v2.5 §9.6.2.2.2)
170 // to the REP-2011/REP-2016 type-hash KV payload "typehash=RIHS01_<hex>;".
171 // Jazzy rmw_cyclonedds_cpp / rmw_fastrtps_cpp parse this during SEDP
172 // endpoint discovery to verify type compatibility.
173 auto ud = build_user_data_for<msg_type>();
174 if (!ud.empty()) {
175 wqos.user_data().data_vec(ud);
176 }
177
178 efd::DataWriterListener* listener =
179 static_cast<efd::DataWriterListener*>(this);
180 _datawriter = _publisher->create_datawriter(_topic, wqos, listener);
181 if (_datawriter == nullptr) {
182 log_error("FastDDSPublisherMiddleware: Failed to create DataWriter");
183 return false;
184 }
185
186 _topic_name = topic_name;
187 return true;
188 }
189
190 public:
191 bool Publish(void* message_data) override {
192 eprosima::fastrtps::rtps::InstanceHandle_t instance_handle;
193 erc rcode = _datawriter->write(message_data, instance_handle);
194 if (rcode == erc::ReturnCodeValue::RETCODE_OK) {
195 return true;
196 }
197 log_error("FastDDSPublisherMiddleware::Publish (",
198 _topic_name, ") failed with code:", rcode());
199 return false;
200 }
201
202 bool IsAlive() const override {
203 return _alive.load(std::memory_order_relaxed);
204 }
205
206 std::string GetTopicName() const override {
207 return _topic_name;
208 }
209
210 private:
211 efd::DomainParticipant* _participant { nullptr };
212 efd::Publisher* _publisher { nullptr };
213 efd::Topic* _topic { nullptr };
214 efd::DataWriter* _datawriter { nullptr };
215 efd::TypeSupport _type { new GenericCdrPubSubType<msg_type>() };
216
217 std::string _topic_name;
218 std::atomic<bool> _alive { false };
219};
220
221} // namespace ros2
222} // namespace carla
FastDDS implementation of IPublisherMiddleware.
bool Publish(void *message_data) override
Serialize and write a message to the network.
FastDDSPublisherMiddleware(const FastDDSPublisherMiddleware &)=delete
FastDDSPublisherMiddleware & operator=(const FastDDSPublisherMiddleware &)=delete
bool InitImpl(const std::string &topic_name, const QosProfile *qos)
Shared Init body.
void on_publication_matched(efd::DataWriter *writer, const efd::PublicationMatchedStatus &info) override
bool Init(const std::string &topic_name, const QosProfile &qos) override
Initialize with an explicit per-topic QoS profile (see QosProfile.h).
bool Init(const std::string &topic_name) override
Initialize the underlying middleware entities.
static void release_type(const std::string &type_name)
Release one use of type_name.
static eprosima::fastdds::dds::DomainParticipant * acquire()
Increment the refcount and return the shared DomainParticipant.
static void retain_type(const std::string &type_name)
Notify the singleton that one more endpoint is using type_name so that unregister_type() is deferred ...
static void release()
Decrement the refcount.
Generic FastDDS TopicDataType that serializes carla::ros2::msg::* POD structs directly to CDR via Cdr...
Type-erased abstract interface for a publisher middleware.
eprosima::fastrtps::types::ReturnCode_t erc
This file contains definitions of common data structures used in traffic manager.
static void log_error(Args &&... args)
Definition Logging.h:110
Middleware-neutral QoS profile passed to IPublisherMiddleware::Init / ISubscriberMiddleware::Init whe...
Definition QosProfile.h:27
Reliability reliability
Definition QosProfile.h:32
std::int32_t history_depth
Definition QosProfile.h:35