VLink  2.1.0
A high-performance communication middleware
subscriber.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 subscriber.h
26  * @brief Read-side primitive of the VLink event communication model.
27  *
28  * @details
29  * @c Subscriber<MsgT, SecT> attaches a callback to a VLink topic. Each
30  * frame delivered by the transport back-end is deserialised into a @c MsgT
31  * instance and forwarded to the user callback registered via @c listen().
32  * Unlike @c Getter, the subscriber retains no value history -- it simply
33  * forwards every event in delivery order.
34  *
35  * The class is a thin, header-only template wrapper around @c SubscriberImpl.
36  * Codec dispatch is fully resolved at compile time using the type detected
37  * by @c Serializer::get_type_of<MsgT>().
38  *
39  * @par Event-model Delivery Path
40  * @verbatim
41  * Transport Back-end Subscriber<MsgT>
42  * ------------------ -----------------
43  * | inbound frame |
44  * |--------------------------------------> |
45  * | | Serializer::deserialize
46  * | | (per-delivery local value)
47  * | |
48  * | | optional MessageLoop hop
49  * | |
50  * | v
51  * | user callback(const MsgT&)
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 to callback. |
58  * | Protobuf value | @c MyProto | @c kProtoType | ParseFromArray path. |
59  * | Protobuf pointer | @c MyProto* | @c kProtoPtrType | Needs @c bind_proto_arena. |
60  * | FlatBuffers obj | @c MyTableT (NativeTable) | @c kFlatTableType | Object API. |
61  * | FlatBuffers ptr | @c MyTable* | @c kFlatPtrType | Zero-copy view of buffer. |
62  * | DDS CDR | FastDDS IDL or ROS2 message type | @c kCdrType | Encapsulated CDR bytes. |
63  * | POD struct | trivial standard-layout type | @c kStandardType | @c sizeof(T) byte copy. |
64  * | String | @c std::string | @c kStringType | Payload-sized byte string. |
65  * | Custom | type with @c operator>>/<< | @c kCustomType | User-supplied codec. |
66  *
67  * Raw Protobuf pointer receivers allocate a distinct message in the bound
68  * Arena for each delivery; that storage remains until the Arena is reset or
69  * destroyed.
70  *
71  * @par Basic Listen Example
72  * @code
73  * vlink::Subscriber<MyMsg> sub("dds://vehicle/speed");
74  * sub.listen([](const MyMsg& msg) { handle(msg); });
75  * @endcode
76  *
77  * @par Callback Dispatch via MessageLoop
78  * @code
79  * vlink::MessageLoop loop;
80  * vlink::Subscriber<MyMsg> sub("dds://vehicle/speed", vlink::InitType::kWithoutInit);
81  * sub.attach(&loop);
82  * sub.init();
83  * sub.listen([](const MyMsg& m) { handle(m); });
84  * loop.run();
85  * @endcode
86  *
87  * @par Zero-copy Intra Transport
88  * When @c MsgT is a shared pointer whose @c element_type derives from
89  * @c IntraDataType (generated via @c VLINK_INTRA_DATA_DECLARE) and the URL
90  * scheme is @c intra://, the shared pointer is forwarded zero-copy to the
91  * callback without serialisation:
92  * @code
93  * VLINK_INTRA_DATA_DECLARE(MyProtoMsg, MyIntra)
94  * vlink::Subscriber<MyIntra> sub("intra://my_topic");
95  * sub.listen([](const MyIntra& data) { use(data); });
96  * @endcode
97  *
98  * @par Latency and Sample-loss Tracking
99  * @code
100  * vlink::Subscriber<MyMsg> sub("dds://my_topic");
101  * sub.set_latency_and_lost_enabled(true);
102  * auto latency_ns = sub.get_latency();
103  * auto stats = sub.get_lost();
104  * @endcode
105  *
106  * @warning For value message types, the deserialised callback argument is a
107  * per-delivery local object whose reference is valid only for the
108  * callback duration. Pointer-view types may instead refer to an
109  * external buffer or arena and follow that storage's lifetime.
110  * Copy retained values into explicitly owned storage. Copying
111  * @c Bytes creates owned storage, and copying an @c IntraData shared
112  * pointer extends that object's lifetime.
113  *
114  * @note Calling @c listen() more than once is a fatal error. The subscriber
115  * must be initialised (either by @c InitType::kWithInit or by explicit
116  * @c init()) before @c listen() is called.
117  *
118  * @see publisher.h, node.h, serializer.h, base/message_loop.h
119  */
120 
121 #pragma once
122 
123 #include <memory>
124 #include <string>
125 #include <type_traits>
126 
127 #include "./base/functional.h"
128 #include "./impl/subscriber_impl.h"
129 #include "./node.h"
130 
131 namespace vlink {
132 
133 /**
134  * @class Subscriber
135  * @brief Type-safe topic listener for the VLink event communication model.
136  *
137  * @details
138  * Inherits the full @c Node API and adds receive-specific operations:
139  * @c listen() to register the user callback and latency / sample-loss
140  * tracking. The transport
141  * implementation (@c SubscriberImpl) is selected by the URL scheme or by
142  * the typed configuration object supplied at construction time.
143  *
144  * @tparam MsgT C++ message type. Must satisfy @c Serializer::is_supported().
145  * @tparam SecT Security mode; defaults to @c SecurityType::kWithoutSecurity.
146  */
147 template <typename MsgT, SecurityType SecT = SecurityType::kWithoutSecurity>
148 class Subscriber : public Node<SubscriberImpl, SecT> {
149  public:
150  using UniquePtr = std::unique_ptr<Subscriber<MsgT, SecT>>; ///< Owning unique-pointer alias.
151  using SharedPtr = std::shared_ptr<Subscriber<MsgT, SecT>>; ///< Owning shared-pointer alias.
152  using MsgCallback = Function<void(const MsgT&)>; ///< User callback signature for received messages.
153 
154  static constexpr ImplType kImplType = kSubscriber; ///< Node role tag (@c kSubscriber).
155  static constexpr Serializer::Type kMsgType = Serializer::get_type_of<MsgT>(); ///< Codec resolved from @c MsgT.
156 
157  static_assert(Serializer::is_supported(kMsgType), "<MsgT> is not a supported Serializer type.");
158 
159  /**
160  * @brief Heap-allocates a @c Subscriber and wraps it in a @c std::unique_ptr.
161  *
162  * @param url_str Topic URL string.
163  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
164  * @return Owning @c UniquePtr to the new subscriber.
165  */
166  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, InitType type = InitType::kWithInit);
167 
168  /**
169  * @brief Heap-allocates a @c Subscriber and wraps it in a @c std::shared_ptr.
170  *
171  * @param url_str Topic URL string.
172  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
173  * @return Owning @c SharedPtr to the new subscriber.
174  */
175  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, InitType type = InitType::kWithInit);
176 
177  /**
178  * @brief Constructs a subscriber from a typed transport configuration object.
179  *
180  * @details
181  * Accepts any @c Conf-derived configuration. A compile-time check enforces
182  * that the configuration permits the subscriber role.
183  *
184  * @tparam ConfT Concrete configuration type derived from @c Conf.
185  * @param conf Populated configuration aggregate.
186  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
187  */
188  // NOLINTNEXTLINE(modernize-use-constraints)
189  template <typename ConfT, typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
190  explicit Subscriber(const ConfT& conf, InitType type = InitType::kWithInit);
191 
192  /**
193  * @brief Constructs a subscriber from a URL string.
194  *
195  * @param url_str Topic URL such as @c "shm://vehicle/speed".
196  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
197  */
198  explicit Subscriber(const std::string& url_str, InitType type = InitType::kWithInit);
199 
200  /**
201  * @brief Installs the receive callback that runs for every inbound message.
202  *
203  * @details
204  * The callback is invoked on the transport delivery thread by default; if
205  * the subscriber is @c attach()ed to a @c MessageLoop the callback is
206  * posted onto that loop instead. For @c intra:// transports carrying an
207  * @c IntraDataType shared pointer the value is forwarded zero-copy and
208  * no deserialisation occurs.
209  *
210  * @warning The argument reference is valid only for the duration of the
211  * callback. Value messages use per-delivery local storage, while
212  * pointer-view messages and @c Bytes may refer to external storage.
213  * Copy retained values into explicitly owned storage; copying
214  * @c Bytes creates owned storage, and copying an @c IntraData shared
215  * pointer extends that object's lifetime.
216  *
217  * @note Calling @c listen() more than once is fatal. The subscriber must
218  * be initialised before the first call to @c listen().
219  *
220  * @param callback @c void(const MsgT&) invoked for each received message.
221  * @return @c true if the callback was installed successfully.
222  */
223  bool listen(MsgCallback&& callback);
224 
225  /**
226  * @brief Toggles per-message latency and sample-loss measurement.
227  *
228  * @param enable @c true to begin tracking; @c false to stop.
229  */
230  void set_latency_and_lost_enabled(bool enable);
231 
232  /**
233  * @brief Reports whether latency and sample-loss tracking is currently active.
234  *
235  * @return @c true if @c set_latency_and_lost_enabled(true) was invoked.
236  */
237  [[nodiscard]] bool is_latency_and_lost_enabled() const;
238 
239  /**
240  * @brief Returns the most recent end-to-end latency measurement.
241  *
242  * @details
243  * Computed as receive-timestamp minus source-timestamp on the last
244  * delivered message. Returns @c 0 when tracking is not enabled.
245  *
246  * @return Latency in nanoseconds; @c 0 if disabled.
247  */
248  [[nodiscard]] int64_t get_latency() const;
249 
250  /**
251  * @brief Returns cumulative sample-delivery statistics.
252  *
253  * @return @c SampleLostInfo with total expected and total lost counts.
254  */
255  [[nodiscard]] SampleLostInfo get_lost() const;
256 
257  /**
258  * @brief Promotes this subscriber to behave as a @c Getter (field-reader) at the transport layer.
259  *
260  * @details
261  * Switches @c impl_type from @c kSubscriber to @c kGetter so that
262  * latest-value delivery semantics are activated. Reinitialises the
263  * transport extension if called post-@c init(). Used internally by
264  * @c Getter.
265  */
266  void mark_as_getter();
267 
268  private:
269  bool listen_bytes(NodeImpl::MsgCallback&& callback);
270 
271  bool listen_intra(MsgCallback&& callback);
272 };
273 
274 /**
275  * @class SecuritySubscriber
276  * @brief Convenience alias of @c Subscriber with per-message decryption enabled.
277  *
278  * @details
279  * Equivalent to @c Subscriber<MsgT, SecurityType::kWithSecurity>. Every
280  * inbound payload is decrypted with the configured @c Security::Config
281  * before the codec dispatcher is invoked.
282  *
283  * @note Security is not supported on @c intra:// or on @c dds:// CDR
284  * payloads.
285  *
286  * @tparam MsgT C++ message type to subscribe.
287  */
288 template <typename MsgT>
289 class SecuritySubscriber : public Subscriber<MsgT, SecurityType::kWithSecurity> {
290  public:
291  using UniquePtr = std::unique_ptr<SecuritySubscriber<MsgT>>; ///< Owning unique-pointer alias.
292  using SharedPtr = std::shared_ptr<SecuritySubscriber<MsgT>>; ///< Owning shared-pointer alias.
293 
294  /**
295  * @brief Heap-allocates a @c SecuritySubscriber and wraps it in a @c std::unique_ptr.
296  *
297  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
298  * @param url_str Topic URL string.
299  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
300  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
301  * @return Owning @c UniquePtr to the new secure subscriber.
302  */
303  // NOLINTNEXTLINE(modernize-use-constraints)
304  template <typename SecurityConfigT = Security::Config>
305  [[nodiscard]] static UniquePtr create_unique(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
307 
308  /**
309  * @brief Heap-allocates a @c SecuritySubscriber and wraps it in a @c std::shared_ptr.
310  *
311  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
312  * @param url_str Topic URL string.
313  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
314  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
315  * @return Owning @c SharedPtr to the new secure subscriber.
316  */
317  // NOLINTNEXTLINE(modernize-use-constraints)
318  template <typename SecurityConfigT = Security::Config>
319  [[nodiscard]] static SharedPtr create_shared(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
321 
322  /**
323  * @brief Constructs a @c SecuritySubscriber from a typed configuration object.
324  *
325  * @tparam ConfT Configuration type derived from @c Conf.
326  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
327  * @param conf Populated configuration aggregate.
328  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
329  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
330  */
331  // NOLINTNEXTLINE(modernize-use-constraints)
332  template <typename ConfT, typename SecurityConfigT = Security::Config,
333  typename = std::enable_if_t<std::is_base_of_v<Conf, ConfT>>>
334  explicit SecuritySubscriber(const ConfT& conf, SecurityConfigT&& sec_cfg = {}, InitType type = InitType::kWithInit);
335 
336  /**
337  * @brief Constructs a @c SecuritySubscriber from a URL string and installs the security configuration.
338  *
339  * @details
340  * Builds the base @c Subscriber in @c kWithoutInit mode, installs @p sec_cfg
341  * via @c enable_security(), then calls @c init() unless deferred. When
342  * @c enable_security() fails to produce a usable @c NodeImpl::security the
343  * subsequent @c init() will fail.
344  *
345  * @tparam SecurityConfigT Forwardable @c Security::Config compatible type.
346  * @param url_str Topic URL string.
347  * @param sec_cfg Security configuration; empty uses the default symmetric slot.
348  * @param type Whether to call @c init() inline; default is @c InitType::kWithInit.
349  */
350  // NOLINTNEXTLINE(modernize-use-constraints)
351  template <typename SecurityConfigT = Security::Config>
352  explicit SecuritySubscriber(const std::string& url_str, SecurityConfigT&& sec_cfg = {},
354 };
355 
356 } // namespace vlink
357 
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Common CRTP base for every VLink communication primitive.
Transport-neutral base class for every event-model subscriber implementation.