VLink  2.0.0
A high-performance communication middleware
publisher.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file publisher.h
26  * @brief Write-side primitive of the VLink event communication model.
27  *
28  * @details
29  * @c Publisher<MsgT, SecT> is the message emitter for VLink topics. Each
30  * call to @c publish() serialises an instance of @c MsgT according to the
31  * compile-time-detected codec and hands the resulting payload to the active
32  * transport back-end, which fans the bytes out to every connected
33  * @c Subscriber that shares the same topic URL.
34  *
35  * The class is a thin, header-only template wrapper around @c PublisherImpl;
36  * all runtime work is delegated to the impl. Codec selection is fully
37  * resolved at compile time so the runtime dispatch is zero-cost.
38  *
39  * @par Event-model Data Flow
40  * @verbatim
41  * Publisher<MsgT> Transport Back-end Subscriber<MsgT>
42  * ---------------- ------------------ -----------------
43  * | | |
44  * |-- publish(msg, force) ---------->| |
45  * | Serializer::serialize | |
46  * | (loan-aware on shm/zenoh) | |
47  * | |--- frame on wire ----------->|
48  * | | |
49  * | | |--> callback
50  * | | | Serializer::
51  * | | | deserialize
52  * @endverbatim
53  *
54  * @par Supported Message Types
55  * | Category | Example C++ Type | @c Serializer::Type | Notes |
56  * | ----------------- | --------------------------------- | ------------------- | ------------------------------ |
57  * | Raw bytes | @c Bytes | @c kBytesType | Pass-through, no codec call. |
58  * | Protobuf value | @c MyProto | @c kProtoType | Uses SerializeToArray. |
59  * | Protobuf pointer | @c MyProto* | @c kProtoPtrType | Caller owns lifetime. |
60  * | FlatBuffers obj | @c MyTableT (NativeTable) | @c kFlatTableType | Object API. |
61  * | FlatBuffers ptr | @c MyTable* (Table pointer) | @c kFlatPtrType | Zero-copy read view. |
62  * | FlatBuffers build | @c MyBuilder (@c fbb_ + Finish) | @c kFlatBuilderType | Finishes builder on publish. |
63  * | DDS CDR | type with @c serialize(Cdr&) | @c kCdrType | DDS fast path. |
64  * | POD struct | trivial standard-layout type | @c kStandardType | @c sizeof(T) byte copy. |
65  * | UTF-8 text | @c std::string | @c kStringType | Length-prefixed. |
66  * | Custom | type with @c operator>>/<< | @c kCustomType | User-supplied codec. |
67  *
68  * @par Subscriber Detection
69  * Use the detection API to gate writes on the presence of at least one
70  * matching subscriber. This is purely informational; pass @c force=true to
71  * @c publish() if you wish to write regardless of detection state.
72  *
73  * @code
74  * vlink::Publisher<MyMsg> pub("dds://sensor/imu");
75  *
76  * pub.detect_subscribers([&pub](bool present) {
77  * if (present) { pub.publish(MyMsg{}); }
78  * });
79  *
80  * if (pub.wait_for_subscribers(std::chrono::milliseconds(500))) {
81  * pub.publish(MyMsg{});
82  * }
83  *
84  * if (pub.has_subscribers()) { pub.publish(MyMsg{}); }
85  * @endcode
86  *
87  * @par Zero-copy Loan on Loan-capable Transports
88  * @code
89  * vlink::Publisher<vlink::Bytes> pub("shm://image/raw");
90  * if (pub.is_support_loan()) {
91  * vlink::Bytes buf = pub.loan(sizeof(FrameHeader) + payload_size);
92  * ::memcpy(buf.data(), &header, sizeof(header));
93  * pub.publish(buf);
94  * }
95  * @endcode
96  *
97  * @note Loans bypass an extra copy by writing directly into transport-managed
98  * memory. Loans are not used when security is enabled because
99  * ciphertext length differs from plaintext length.
100  *
101  * @see subscriber.h, node.h, serializer.h, extension/security.h
102  */
103 
104 #pragma once
105 
106 #include <memory>
107 #include <string>
108 #include <type_traits>
109 
110 #include "./impl/publisher_impl.h"
111 #include "./node.h"
112 
113 namespace vlink {
114 
115 /**
116  * @class Publisher
117  * @brief Type-safe topic emitter for the VLink event communication model.
118  *
119  * @details
120  * Inherits the full @c Node API (lifecycle, loans, properties, profiler) and
121  * adds publish-specific operations: subscriber detection, blocking wait, and
122  * the @c publish() entry point itself. The transport implementation
123  * (@c PublisherImpl) is selected by the URL scheme or by the typed
124  * configuration object supplied at construction time.
125  *
126  * @tparam MsgT C++ message type. Must satisfy @c Serializer::is_supported().
127  * @tparam SecT Security mode; defaults to @c SecurityType::kWithoutSecurity.
128  */
129 template <typename MsgT, SecurityType SecT = SecurityType::kWithoutSecurity>
130 class Publisher : public Node<PublisherImpl, SecT> {
131  public:
132  using UniquePtr = std::unique_ptr<Publisher<MsgT, SecT>>; ///< Owning unique-pointer alias.
133  using SharedPtr = std::shared_ptr<Publisher<MsgT, SecT>>; ///< Owning shared-pointer alias.
134  using ConnectCallback = NodeImpl::ConnectCallback; ///< Callback type for subscriber presence transitions.
135 
136  static constexpr ImplType kImplType = kPublisher; ///< Node role tag (@c kPublisher).
137  static constexpr Serializer::Type kMsgType = Serializer::get_type_of<MsgT>(); ///< Codec resolved from @c MsgT.
138 
139  static_assert(Serializer::is_supported(kMsgType), "<MsgT> is not a supported Serializer type.");
140 
141  /**
142  * @brief Heap-allocates a @c Publisher and wraps it in a @c std::unique_ptr.
143  *
144  * @param url_str Topic URL string, e.g. @c "dds://vehicle/speed".
145  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
146  * @return Owning @c UniquePtr to the newly constructed publisher.
147  */
148  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, InitType type = InitType::kWithInit);
149 
150  /**
151  * @brief Heap-allocates a @c Publisher and wraps it in a @c std::shared_ptr.
152  *
153  * @param url_str Topic URL string.
154  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
155  * @return Owning @c SharedPtr to the newly constructed publisher.
156  */
157  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, InitType type = InitType::kWithInit);
158 
159  /**
160  * @brief Constructs a publisher from a typed transport configuration object.
161  *
162  * @details
163  * Accepts any @c Conf-derived configuration (@c DdsConf, @c ShmConf,
164  * @c ZenohConf, etc.). A compile-time check enforces that the chosen
165  * configuration permits the publisher role. Invalid configuration or a
166  * @c nullptr factory result triggers a fatal log.
167  *
168  * @tparam ConfT Concrete configuration type derived from @c Conf.
169  * @param conf Populated configuration aggregate.
170  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
171  */
172  // NOLINTNEXTLINE(modernize-use-constraints)
173  template <typename ConfT, typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
174  explicit Publisher(const ConfT& conf, InitType type = InitType::kWithInit);
175 
176  /**
177  * @brief Constructs a publisher from a URL string.
178  *
179  * @details
180  * The URL scheme selects the transport (e.g. @c "shm://" maps to the
181  * Iceoryx back-end, @c "dds://" maps to FastDDS). Internally the string is
182  * parsed into a @c Url and dispatched to the matching @c ConfT.
183  *
184  * @param url_str Topic URL such as @c "shm://vehicle/speed".
185  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
186  */
187  explicit Publisher(const std::string& url_str, InitType type = InitType::kWithInit);
188 
189  /**
190  * @brief Registers a callback invoked whenever subscriber presence transitions.
191  *
192  * @details
193  * The callback fires synchronously immediately if a subscriber is already
194  * present at registration time, then asynchronously each time the matching
195  * subscriber set transitions between empty and non-empty. The boolean is
196  * @c true when at least one peer is present.
197  *
198  * @param callback @c void(bool) callable -- @c true means at least one subscriber.
199  */
200  void detect_subscribers(ConnectCallback&& callback);
201 
202  /**
203  * @brief Blocks until at least one subscriber is detected or @p timeout expires.
204  *
205  * @details
206  * A @p timeout of @c 0 is interpreted as infinite (with a warning log). A
207  * negative value also waits indefinitely. Calling @c interrupt() causes the
208  * wait to abort and return @c false.
209  *
210  * @param timeout Maximum wait duration. Default: @c Timeout::kDefaultInterval.
211  * @return @c true if a subscriber appeared; @c false on timeout or interrupt.
212  */
213  bool wait_for_subscribers(std::chrono::milliseconds timeout = Timeout::kDefaultInterval);
214 
215  /**
216  * @brief Non-blocking query of subscriber presence.
217  *
218  * @return @c true when the transport currently knows of at least one matching subscriber.
219  */
220  [[nodiscard]] bool has_subscribers() const;
221 
222  /**
223  * @brief Serialises and emits @p msg to every connected subscriber.
224  *
225  * @details
226  * Serialisation is dispatched to the codec selected by @c kMsgType. On
227  * loan-capable transports the output buffer is a loaned segment in
228  * transport-managed memory; the loan is returned automatically once the
229  * publish completes. Loans are skipped when @c SecT == @c kWithSecurity
230  * because the ciphertext size is unknown ahead of encryption.
231  *
232  * When @c force is @c false (the default) the call is skipped and returns
233  * @c false if no subscribers are present. Pass @c force=true to write
234  * unconditionally, e.g. for bag recording or field-style semantics.
235  *
236  * Over @c intra:// with a message whose @c element_type derives from
237  * @c IntraDataType (declared via @c VLINK_INTRA_DATA_DECLARE) the shared
238  * pointer is forwarded zero-copy and no serialisation occurs.
239  *
240  * @param msg Message to publish.
241  * @param force @c true to publish even when no subscribers are connected.
242  * @return @c true if the transport accepted the message.
243  */
244  bool publish(const MsgT& msg, bool force = false);
245 
246  /**
247  * @brief Publishes the raw payload of a pre-finished @c flatbuffers::FlatBufferBuilder.
248  *
249  * @details
250  * Useful in pipelines where the FlatBuffer is constructed externally and
251  * the bytes can be shallow-borrowed without re-serialisation. The pointer
252  * must reference a builder on which @c Finish() has been called.
253  *
254  * @param fbb Pointer to a finished @c flatbuffers::FlatBufferBuilder.
255  * @param force @c true to publish even when no subscribers are connected.
256  * @return @c true on success.
257  */
258  bool publish_fbb(const void* fbb, bool force = false);
259 
260  /**
261  * @brief Promotes this publisher to behave as a @c Setter (field-writer) at the transport layer.
262  *
263  * @details
264  * Switches the underlying @c impl_type from @c kPublisher to @c kSetter
265  * so that field-mode semantics (late-joiner sync, latest-value retention)
266  * are activated. When called post-@c init(), the transport extension is
267  * reinitialised automatically. Used internally by @c Setter.
268  */
269  void mark_as_setter();
270 
271  private:
272  bool write_bytes(const Bytes& data);
273 
274  bool write_intra(const IntraData& intra_data);
275 };
276 
277 /**
278  * @class SecurityPublisher
279  * @brief Convenience alias of @c Publisher with per-message security enabled.
280  *
281  * @details
282  * Equivalent to @c Publisher<MsgT, SecurityType::kWithSecurity>. Every
283  * outgoing payload is encrypted with the configured @c Security::Config
284  * before transmission; an empty config falls back to the built-in default
285  * symmetric slot.
286  *
287  * @note Security is not supported on @c intra:// or on @c dds:// CDR
288  * payloads -- using a @c SecurityPublisher in those configurations is
289  * a constructor-time fatal.
290  *
291  * @tparam MsgT C++ message type to publish.
292  */
293 template <typename MsgT>
294 class SecurityPublisher : public Publisher<MsgT, SecurityType::kWithSecurity> {
295  public:
296  using UniquePtr = std::unique_ptr<SecurityPublisher<MsgT>>; ///< Owning unique-pointer alias.
297  using SharedPtr = std::shared_ptr<SecurityPublisher<MsgT>>; ///< Owning shared-pointer alias.
298 
299  /**
300  * @brief Heap-allocates a @c SecurityPublisher and wraps it in a @c std::unique_ptr.
301  *
302  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
303  * @param url_str Topic URL string.
304  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
305  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
306  * @return Owning @c UniquePtr to the new secure publisher.
307  */
308  // NOLINTNEXTLINE(modernize-use-constraints)
309  template <typename SecurityConfigT = Security::Config>
310  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
312 
313  /**
314  * @brief Heap-allocates a @c SecurityPublisher and wraps it in a @c std::shared_ptr.
315  *
316  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
317  * @param url_str Topic URL string.
318  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
319  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
320  * @return Owning @c SharedPtr to the new secure publisher.
321  */
322  // NOLINTNEXTLINE(modernize-use-constraints)
323  template <typename SecurityConfigT = Security::Config>
324  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
326 
327  /**
328  * @brief Constructs a @c SecurityPublisher from a typed configuration object.
329  *
330  * @details
331  * Constructs the base @c Publisher in @c kWithoutInit mode, installs
332  * @p sec_cfg via @c enable_security(), then runs @c init() unless deferred.
333  * If @c enable_security() fails to populate a usable security object the
334  * subsequent @c init() will fail.
335  *
336  * @tparam ConfT Configuration type derived from @c Conf.
337  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
338  * @param conf Populated configuration aggregate.
339  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
340  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
341  */
342  // NOLINTNEXTLINE(modernize-use-constraints)
343  template <typename ConfT, typename SecurityConfigT = Security::Config,
344  typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>> // NOLINT(modernize-use-constraints)
345  explicit SecurityPublisher(const ConfT& conf, SecurityConfigT&& sec_cfg = {}, InitType type = InitType::kWithInit);
346 
347  /**
348  * @brief Constructs a @c SecurityPublisher from a URL string.
349  *
350  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
351  * @param url_str Topic URL string.
352  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
353  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
354  */
355  // NOLINTNEXTLINE(modernize-use-constraints)
356  template <typename SecurityConfigT = Security::Config>
357  explicit SecurityPublisher(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
359 };
360 
361 } // namespace vlink
362 
Common CRTP base for every VLink communication primitive.
Transport-neutral base class for every event-model publisher implementation.