VLink  2.0.0
A high-performance communication middleware
convert_plugin_interface.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 convert_plugin_interface.h
26  * @brief Plugin contract for converting VLink payloads to visualisation backend formats.
27  *
28  * @details
29  * @c ConvertPluginInterface lets users supply custom encoders that translate raw VLink
30  * messages -- in any serialisation -- into the payload format expected by a particular
31  * webviz frontend. The plugin is loaded as a shared library via the VLink @c Plugin
32  * framework and has no third-party dependencies of its own: it consumes @c Bytes and
33  * emits @c Bytes, so consumers may implement it without linking Protobuf, FlatBuffers,
34  * the Rerun SDK or any JSON library.
35  *
36  * Conversion pipeline:
37  *
38  * @verbatim
39  * can_convert(ser, target)?
40  * VLink Bytes -----> +-----------------------+
41  * | ConvertPluginInterface | --get_schema(ser, target, info)--> channel registration
42  * +-----------------------+
43  * |
44  * v convert(ser, raw, target, payload)
45  * backend Bytes ---> Foxglove / Rerun frontend
46  * @endverbatim
47  *
48  * Supported source/target combinations and the meaning of each output field:
49  *
50  * | @c Target | Wire payload | @c SchemaInfo::type_name meaning |
51  * | ------------------ | ----------------------------------- | ------------------------------------- |
52  * | @c kFoxglove | FlatBuffer / Protobuf binary bytes | Foxglove schema name |
53  * | @c kRerun | UTF-8 JSON describing components | Rerun archetype name |
54  *
55  * @par Rerun JSON payload format
56  * Plugins targeting Rerun emit a UTF-8 JSON object whose fields match the Rerun
57  * archetype. Binary archetypes carry their bytes through a @c data_base64 field.
58  *
59  * @code{.json}
60  * // Points3D
61  * { "positions": [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]],
62  * "colors": [[255, 0, 0, 255], [0, 255, 0, 255]],
63  * "radii": [0.1, 0.2] }
64  *
65  * // EncodedImage (binary payload base64-encoded)
66  * { "media_type": "image/jpeg",
67  * "data_base64": "<base64 image bytes>" }
68  *
69  * // GeoPoints
70  * { "lat_deg": [37.7749, 37.7750],
71  * "lon_deg": [-122.4194, -122.4195] }
72  *
73  * // TextLog
74  * { "text": "Hello world", "level": "INFO" }
75  *
76  * // Scalars
77  * { "value": 3.14 }
78  *
79  * // Transform3D
80  * { "translation": [1.0, 2.0, 3.0],
81  * "rotation_quat": [0.0, 0.0, 0.0, 1.0] }
82  *
83  * // Boxes3D
84  * { "half_sizes": [[1.0, 2.0, 3.0]],
85  * "centers": [[0.0, 0.0, 0.0]],
86  * "quaternions": [[0.0, 0.0, 0.0, 1.0]],
87  * "colors": [[255, 0, 0, 255]],
88  * "labels": ["box1"] }
89  *
90  * // Pinhole
91  * { "image_from_camera": [[fx, 0, cx], [0, fy, cy], [0, 0, 1]],
92  * "resolution": [1920, 1080] }
93  * @endcode
94  *
95  * The built-in Rerun JSON bridge currently handles @c data_base64 for @c EncodedImage,
96  * @c Image, @c DepthImage, @c SegmentationImage, @c EncodedDepthImage, @c Asset3D,
97  * @c AssetVideo and @c Tensor. @c Image, @c DepthImage and @c SegmentationImage also
98  * require @c width / @c height (or @c resolution). @c Tensor additionally requires
99  * @c shape and may provide @c dim_names. Direct VLink-to-Rerun mappings still cover
100  * a broader set of archetypes than the JSON bridge.
101  *
102  * Plugin lifecycle:
103  * 1. @c init() runs once after dynamic load, with an opaque configuration string.
104  * 2. @c can_convert() is queried per discovered VLink serialisation type, per target.
105  * 3. @c get_schema() is called once per accepted type to register the channel.
106  * 4. @c convert() runs for every incoming payload on accepted types.
107  * 5. Optional reverse hooks (@c can_publish(), @c get_publish(),
108  * @c convert_publish()) handle inbound frontend command/control flows.
109  * 6. The destructor runs when the host unloads the plugin.
110  *
111  * @par Example
112  * @code
113  * #include <vlink/extension/convert_plugin_interface.h>
114  *
115  * class MyConvertPlugin : public vlink::ConvertPluginInterface {
116  * VLINK_PLUGIN_REGISTER(ConvertPluginInterface)
117  *
118  * public:
119  * bool init(const std::string& config) override {
120  * (void)config;
121  * return true;
122  * }
123  *
124  * bool can_convert(const std::string& ser_type, Target target) override {
125  * return ser_type == "my_pkg.MyMessage";
126  * }
127  *
128  * bool get_schema(const std::string& ser_type, Target target,
129  * SchemaInfo& schema_info) override {
130  * if (target == Target::kFoxglove) {
131  * schema_info.type_name = "foxglove.LocationFix";
132  * schema_info.encoding = "flatbuffers";
133  * schema_info.schema_encoding = "flatbuffers";
134  * // schema_info.schema_data = compiled BFBS bytes
135  * } else {
136  * schema_info.type_name = "GeoPoints";
137  * schema_info.encoding = "json";
138  * }
139  * return true;
140  * }
141  *
142  * bool convert(const std::string& ser_type, const vlink::Bytes& raw,
143  * Target target, vlink::Bytes& payload) override {
144  * if (target == Target::kRerun) {
145  * std::string json = R"({"lat_deg":[37.77],"lon_deg":[-122.41]})";
146  * payload = vlink::Bytes::deep_copy(json.data(), json.size());
147  * }
148  * return true;
149  * }
150  * };
151  * VLINK_PLUGIN_DECLARE(MyConvertPlugin, 4, 0)
152  * @endcode
153  */
154 
155 #pragma once
156 
157 #include <cstdint>
158 #include <string>
159 
160 #include "../base/bytes.h"
161 #include "../base/plugin.h"
162 #include "../impl/types.h"
163 
164 namespace vlink {
165 
166 /**
167  * @class ConvertPluginInterface
168  * @brief Abstract plugin base translating between VLink payloads and visualisation backends.
169  *
170  * @details
171  * Loaded via @c Plugin::load<ConvertPluginInterface>(). The plugin must be thread-safe:
172  * @c convert() and the inbound @c convert_publish() hooks may run concurrently from
173  * multiple ProxyAPI worker threads. A plugin that only supports one backend should
174  * return @c false from @c can_convert() / @c can_publish() for the others.
175  */
178 
179  protected:
181 
182  virtual ~ConvertPluginInterface() = default;
183 
184  public:
185  /**
186  * @enum Target
187  * @brief Visualisation backend identifier carried by every conversion hook.
188  *
189  * @details
190  * Allows a single plugin to support multiple backends from one binary -- the plugin
191  * branches on this value to produce the appropriate payload format.
192  */
193  enum class Target : uint8_t {
194  kFoxglove = 0, ///< Foxglove Studio (WebSocket transport, FlatBuffers/Protobuf payloads).
195  kRerun = 1, ///< Rerun Viewer (gRPC + Arrow IPC; plugin payload is UTF-8 JSON).
196  };
197 
198  /**
199  * @struct SchemaInfo
200  * @brief Backend channel schema metadata returned by @c get_schema().
201  */
202  struct SchemaInfo final {
203  std::string type_name; ///< Backend schema or archetype name.
204  std::string encoding; ///< Wire encoding label (e.g. @c "flatbuffers", @c "json").
205  std::string schema_encoding; ///< Encoding of @c schema_data when provided.
206  std::string schema_data; ///< Binary schema bytes or schema text, depending on @c target.
207  };
208 
209  /**
210  * @struct FrontendChannel
211  * @brief Frontend-advertised channel description used by inbound conversion hooks.
212  *
213  * @details
214  * Allows plugins to route Foxglove @c clientPublish-style messages onto the right VLink
215  * topic by inspecting the channel's topic, encoding and schema metadata.
216  */
217  struct FrontendChannel final {
218  std::string topic; ///< Channel topic advertised by the frontend client.
219  std::string encoding; ///< Frontend payload encoding (json/protobuf/flatbuffers/...).
220  std::string schema_name; ///< Frontend-side schema or type name.
221  std::string schema_encoding; ///< Encoding of @c schema when provided.
222  std::string schema; ///< Raw schema string or binary payload (transport-specific).
223  };
224 
225  /**
226  * @struct PublishInfo
227  * @brief VLink publish destination resolved from an inbound frontend channel.
228  */
229  struct PublishInfo final {
230  std::string url; ///< Destination VLink URL (e.g. @c "dds://vehicle/cmd").
231  std::string ser_type; ///< Destination VLink serialisation type.
232  SchemaType schema_type{SchemaType::kUnknown}; ///< Coarse schema family for the published payload.
233  };
234 
235  /**
236  * @brief Initialises the plugin with an opaque configuration string.
237  *
238  * @details
239  * Called once after the plugin is loaded; the @p config string may be a file path,
240  * JSON document or anything the plugin defines. Returning @c false causes the host
241  * to unload the plugin.
242  *
243  * @param config Configuration payload; may be empty.
244  * @return @c true on success.
245  */
246  virtual bool init(const std::string& config) = 0;
247 
248  /**
249  * @brief Reports whether this plugin handles a (serialisation, target) pair.
250  *
251  * @details
252  * Polled during channel discovery for each new VLink type. A @c true answer commits
253  * the plugin to subsequent @c get_schema() and @c convert() calls for that pair.
254  *
255  * @param ser_type VLink serialisation type name (e.g. @c "proto.VehiclePose").
256  * @param target Visualisation backend asking about the conversion.
257  * @return @c true when the plugin can produce a payload for @p target.
258  */
259  [[nodiscard]] virtual bool can_convert(const std::string& ser_type, Target target) = 0;
260 
261  /**
262  * @brief Provides schema metadata for an accepted (serialisation, target) pair.
263  *
264  * @details
265  * Called once per accepted pair when registering the frontend channel. Outputs
266  * differ per target:
267  * - @c kFoxglove: fill @p schema_info with type, encoding and schema bytes
268  * (typically the bytes of a compiled BFBS file).
269  * - @c kRerun: fill @p schema_info.type_name with the archetype name and
270  * @p schema_info.encoding with @c "json"; schema payload fields are unused.
271  *
272  * @param[in] ser_type VLink serialisation type name.
273  * @param[in] target Visualisation backend.
274  * @param[out] schema_info Backend channel schema metadata.
275  * @return @c true on success.
276  */
277  [[nodiscard]] virtual bool get_schema(const std::string& ser_type, Target target, SchemaInfo& schema_info) = 0;
278 
279  /**
280  * @brief Converts a single raw VLink payload to the backend-specific representation.
281  *
282  * @details
283  * Invoked once per incoming message on accepted types. Must be thread-safe.
284  *
285  * @param[in] ser_type VLink serialisation type name.
286  * @param[in] raw Raw serialised VLink payload.
287  * @param[in] target Visualisation backend.
288  * @param[out] payload Output buffer that receives the backend payload.
289  * @return @c true on success.
290  */
291  [[nodiscard]] virtual bool convert(const std::string& ser_type, const Bytes& raw, Target target, Bytes& payload) = 0;
292 
293  /**
294  * @brief Optionally extracts a per-message timestamp from the raw payload.
295  *
296  * @details
297  * Called after @c convert() so the frontend can prefer a sensor or content timestamp
298  * over the proxy transport timestamp. The default implementation returns @c -1,
299  * causing the host to fall back to the transport-level timestamp.
300  *
301  * @param[in] ser_type VLink serialisation type name.
302  * @param[in] raw Raw serialised VLink payload.
303  * @param[in] target Visualisation backend.
304  * @return Timestamp in nanoseconds since epoch, or @c -1 when unavailable.
305  */
306  [[nodiscard]] virtual int64_t get_timestamp(const std::string& ser_type, const Bytes& raw, Target target) {
307  (void)ser_type;
308  (void)raw;
309  (void)target;
310  return -1;
311  }
312 
313  /**
314  * @brief Inbound counterpart of @c can_convert() for frontend-published channels.
315  *
316  * @details
317  * Default implementation returns @c false; override to opt in to clientPublish-style
318  * command flows.
319  */
320  [[nodiscard]] virtual bool can_publish(const FrontendChannel& channel, Target target) {
321  (void)channel;
322  (void)target;
323  return false;
324  }
325 
326  /**
327  * @brief Resolves the VLink publish destination for an inbound frontend channel.
328  *
329  * @details
330  * Returning @c true allows the host to provision the required VLink publishers ahead
331  * of time. Default implementation returns @c false.
332  */
333  [[nodiscard]] virtual bool get_publish(const FrontendChannel& channel, Target target, PublishInfo& publish_info) {
334  (void)channel;
335  (void)target;
336  (void)publish_info;
337  return false;
338  }
339 
340  /**
341  * @brief Converts a frontend-published payload into a raw VLink payload.
342  *
343  * @details
344  * Invoked once per inbound message after @c get_publish() routed the channel.
345  * Default implementation returns @c false.
346  */
347  [[nodiscard]] virtual bool convert_publish(const FrontendChannel& channel, const Bytes& raw, Target target,
348  Bytes& payload) {
349  (void)channel;
350  (void)raw;
351  (void)target;
352  (void)payload;
353  return false;
354  }
355 
356  private:
358 };
359 
360 } // namespace vlink
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
#define VLINK_PLUGIN_REGISTER(InterfaceType)
Declares a plugin's identity from the demangled name of its abstract interface.
Definition: plugin.h:345