VLink 2.0.0
A high-performance communication middleware
载入中...
搜索中...
未找到
message_convert_plugin.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 message_convert_plugin.h
26 * @brief Plugin interface for VLink/webviz message conversion across visualization backends.
27 *
28 * @details
29 * @c MessageConvertPlugin allows users to implement custom VLink/webviz message conversion
30 * logic in a shared library plugin. The plugin receives raw VLink message bytes
31 * (Protobuf, FlatBuffers, CDR, POD, or any custom serialisation) and produces a
32 * backend-specific payload along with schema/type metadata.
33 *
34 * This interface has **zero third-party dependencies** -- it only uses VLink base
35 * types (@c Bytes) and standard C++ types, making it easy to implement in external
36 * projects without linking against Protobuf, FlatBuffers, Rerun SDK, or JSON libraries.
37 *
38 * A single plugin can support multiple visualization backends by checking the
39 * @c ConvertTarget parameter in each method. The plugin coexists with JSON-based
40 * VLink-to-webviz mapping files. When both are present, the plugin is tried
41 * first; if it returns @c false for a given type, the JSON mapping pipeline takes over.
42 *
43 * @par Supported targets
44 * | Target | Payload format | type_name meaning |
45 * |------------- |-----------------------------------|--------------------------------|
46 * | @c kFoxglove | FlatBuffer / Protobuf binary | Foxglove schema name |
47 * | @c kRerun | JSON string describing components | Rerun archetype name |
48 *
49 * @par Rerun JSON payload format
50 * When targeting Rerun, the plugin should produce a UTF-8 JSON string as the payload.
51 * The JSON object describes the Rerun archetype components. Each archetype has its own
52 * expected fields:
53 *
54 * @code{.json}
55 * // Points3D example:
56 * {
57 * "positions": [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]],
58 * "colors": [[255, 0, 0, 255], [0, 255, 0, 255]],
59 * "radii": [0.1, 0.2]
60 * }
61 *
62 * // EncodedImage example (binary data is base64-encoded):
63 * {
64 * "media_type": "image/jpeg",
65 * "data_base64": "<base64-encoded image bytes>"
66 * }
67 *
68 * // GeoPoints example:
69 * {
70 * "lat_deg": [37.7749, 37.7750],
71 * "lon_deg": [-122.4194, -122.4195]
72 * }
73 *
74 * // TextLog example:
75 * {
76 * "text": "Hello world",
77 * "level": "INFO"
78 * }
79 *
80 * // Scalars example:
81 * {
82 * "value": 3.14
83 * }
84 *
85 * // Transform3D example:
86 * {
87 * "translation": [1.0, 2.0, 3.0],
88 * "rotation_quat": [0.0, 0.0, 0.0, 1.0]
89 * }
90 *
91 * // Boxes3D example:
92 * {
93 * "half_sizes": [[1.0, 2.0, 3.0]],
94 * "centers": [[0.0, 0.0, 0.0]],
95 * "quaternions": [[0.0, 0.0, 0.0, 1.0]],
96 * "colors": [[255, 0, 0, 255]],
97 * "labels": ["box1"]
98 * }
99 *
100 * // Pinhole example:
101 * {
102 * "image_from_camera": [[fx, 0, cx], [0, fy, cy], [0, 0, 1]],
103 * "resolution": [1920, 1080]
104 * }
105 * @endcode
106 *
107 * Binary archetypes should carry their raw bytes in a @c data_base64 string. Currently the
108 * built-in Rerun JSON bridge supports this for @c EncodedImage, @c Image, @c DepthImage,
109 * @c SegmentationImage, @c EncodedDepthImage, @c Asset3D, @c AssetVideo, and @c Tensor.
110 * @c Image, @c DepthImage, and @c SegmentationImage also require @c width/@c height (or
111 * @c resolution). @c Tensor additionally requires @c shape and may provide @c dim_names.
112 * Direct VLink-to-Rerun mappings still cover a broader set of archetypes than the plugin
113 * JSON bridge.
114 *
115 * @par Plugin lifecycle
116 * 1. @c init() is called once after loading, with an optional config string.
117 * 2. @c can_convert() is called for each discovered VLink serialisation type
118 * (with the target backend) to determine if the plugin handles it.
119 * 3. @c get_schema_info() is called once per type to register the channel/archetype.
120 * 4. @c convert() is called for every incoming message on matched types.
121 * 5. The plugin is destroyed when the server shuts down.
122 *
123 * @par Example implementation (supports both Foxglove and Rerun)
124 * @code
125 * #include <vlink/extension/message_convert_plugin.h>
126 *
127 * class MyConvertPlugin : public vlink::MessageConvertPlugin {
128 * VLINK_PLUGIN_REGISTER(MessageConvertPlugin)
129 *
130 * public:
131 * bool init(const std::string& config) override {
132 * // Parse config, load schemas, etc.
133 * return true;
134 * }
135 *
136 * bool can_convert(const std::string& vlink_ser, ConvertTarget target) override {
137 * if (vlink_ser != "my_pkg.MyMessage") return false;
138 * // Support both backends
139 * return target == ConvertTarget::kFoxglove || target == ConvertTarget::kRerun;
140 * }
141 *
142 * bool get_schema_info(const std::string& vlink_ser, ConvertTarget target,
143 * std::string& type_name, std::string& encoding,
144 * std::string& schema_encoding,
145 * std::string& schema_data) override {
146 * if (target == ConvertTarget::kFoxglove) {
147 * type_name = "foxglove.LocationFix";
148 * encoding = "flatbuffers";
149 * schema_encoding = "flatbuffers";
150 * // schema_data = binary FBS schema bytes
151 * } else if (target == ConvertTarget::kRerun) {
152 * type_name = "GeoPoints";
153 * encoding = "json";
154 * // schema_encoding and schema_data are unused for Rerun
155 * }
156 * return true;
157 * }
158 *
159 * bool convert(const std::string& vlink_ser, const vlink::Bytes& raw,
160 * ConvertTarget target, vlink::Bytes& payload) override {
161 * if (target == ConvertTarget::kFoxglove) {
162 * // Build Foxglove FlatBuffer payload
163 * } else if (target == ConvertTarget::kRerun) {
164 * // Build JSON: {"lat_deg":[37.77], "lon_deg":[-122.41]}
165 * std::string json = R"({"lat_deg":[37.77],"lon_deg":[-122.41]})";
166 * payload = vlink::Bytes::deep_copy(json.data(), json.size());
167 * }
168 * return true;
169 * }
170 * };
171 * VLINK_PLUGIN_DECLARE(MyConvertPlugin, 4, 0)
172 * @endcode
173 */
174
175#pragma once
176
177#include <string>
178
179#include "../base/bytes.h"
180#include "../base/plugin.h"
181#include "../impl/types.h"
182
183namespace vlink {
184
185/**
186 * @enum ConvertTarget
187 * @brief Identifies the visualization backend that the plugin is converting for.
188 *
189 * @details
190 * Passed to @c can_convert(), @c get_schema_info(), and @c convert() so that
191 * a single plugin implementation can produce the appropriate output format
192 * for each backend.
193 */
194enum class ConvertTarget : uint8_t {
195 kFoxglove = 0, ///< Foxglove Studio (WebSocket + FlatBuffers/Protobuf)
196 kRerun = 1, ///< Rerun Viewer (gRPC + Arrow IPC, plugin outputs JSON)
197};
198
199/**
200 * @struct WebChannel
201 * @brief Describes a frontend-advertised publish channel.
202 *
203 * @details
204 * Used by inbound/web command conversion hooks so plugins can route Foxglove
205 * webviz-published payloads to the correct VLink topic and serialisation type.
206 */
207struct WebChannel final {
208 std::string topic; ///< Frontend channel topic as advertised by the client.
209 std::string encoding; ///< Frontend payload encoding (e.g. json/protobuf/flatbuffers).
210 std::string schema_name; ///< Frontend schema/type name.
211 std::string schema_encoding; ///< Encoding of @c schema (if provided by the client).
212 std::string schema; ///< Raw schema string/binary payload (transport-specific).
213};
214
215/**
216 * @struct VlinkPublish
217 * @brief VLink publish destination resolved for an inbound frontend message.
218 */
219struct VlinkPublish final {
220 std::string url; ///< Destination VLink URL (e.g. @c "dds://vehicle/cmd").
221 std::string serialization; ///< Destination VLink serialisation type.
222 SchemaType schema_type{SchemaType::kUnknown}; ///< Coarse backend schema family for the published payload.
223};
224
225/**
226 * @class MessageConvertPlugin
227 * @brief Abstract interface for VLink/webviz message conversion plugins supporting
228 * multiple visualization backends.
229 *
230 * @details
231 * Loaded as a dynamic plugin via @c Plugin::load<MessageConvertPlugin>().
232 *
233 * The plugin must be thread-safe: @c convert() may be called concurrently from
234 * multiple ProxyAPI data callback threads.
235 *
236 * A plugin may support only one target or both. Return @c false from
237 * @c can_convert() for unsupported targets.
238 */
241
242 protected:
244
245 virtual ~MessageConvertPlugin() = default;
246
247 public:
248 /**
249 * @brief Initialises the plugin with an optional configuration string.
250 *
251 * @details
252 * Called once after the plugin is loaded. The @p config parameter may contain
253 * a file path, JSON string, or any format the plugin understands.
254 * Returning @c false causes the plugin to be unloaded.
255 *
256 * @param config Configuration string (may be empty).
257 * @return @c true on success, @c false if initialisation failed.
258 */
259 virtual bool init(const std::string& config) = 0;
260
261 /**
262 * @brief Tests whether this plugin can handle the given VLink serialisation type
263 * for the specified target backend.
264 *
265 * @details
266 * Called during channel/topic discovery for each new VLink type. If this returns
267 * @c true, @c get_schema_info() and @c convert() will be used for that type.
268 * The result may be cached by the caller.
269 *
270 * @param vlink_ser VLink serialisation type name (e.g. @c "proto.VehiclePose").
271 * @param target The visualization backend requesting the conversion.
272 * @return @c true if this plugin handles the type for the given target.
273 */
274 [[nodiscard]] virtual bool can_convert(const std::string& vlink_ser, ConvertTarget target) = 0;
275
276 /**
277 * @brief Returns schema/type metadata for the given VLink type and target backend.
278 *
279 * @details
280 * Called once per type during channel/topic registration.
281 *
282 * For @c ConvertTarget::kFoxglove, the plugin must fill in the Foxglove schema name,
283 * encoding type, schema encoding, and the binary schema data (typically a serialised
284 * FlatBuffers binary schema @c .bfbs file content).
285 *
286 * For @c ConvertTarget::kRerun, the plugin should fill in:
287 * - @p type_name: Rerun archetype name (e.g. @c "Points3D", @c "EncodedImage")
288 * - @p encoding: @c "json" (the payload format)
289 * - @p schema_encoding and @p schema_data may be left empty.
290 *
291 * @param[in] vlink_ser VLink serialisation type name.
292 * @param[in] target The visualization backend requesting the metadata.
293 * @param[out] type_name Schema/archetype name.
294 * @param[out] encoding Wire encoding (e.g. @c "flatbuffers", @c "json").
295 * @param[out] schema_encoding Schema encoding (Foxglove only, e.g. @c "flatbuffers").
296 * @param[out] schema_data Binary schema data bytes (Foxglove only).
297 * @return @c true on success.
298 */
299 [[nodiscard]] virtual bool get_schema_info(const std::string& vlink_ser, ConvertTarget target, std::string& type_name,
300 std::string& encoding, std::string& schema_encoding,
301 std::string& schema_data) = 0;
302
303 /**
304 * @brief Converts a raw VLink message to a backend-specific payload.
305 *
306 * @details
307 * Called for every incoming message whose type was accepted by @c can_convert().
308 *
309 * For @c ConvertTarget::kFoxglove, the plugin should build the target Foxglove
310 * FlatBuffer (or Protobuf) and write the result to @p payload using
311 * @c Bytes::deep_copy() or @c Bytes::create().
312 *
313 * For @c ConvertTarget::kRerun, the plugin should produce a UTF-8 JSON string
314 * describing the Rerun archetype components. The JSON format is documented in the
315 * file-level documentation. Write the JSON bytes to @p payload.
316 *
317 * This method must be thread-safe.
318 *
319 * @param[in] vlink_ser VLink serialisation type name.
320 * @param[in] raw Raw serialised message bytes from VLink.
321 * @param[in] target The visualization backend requesting the conversion.
322 * @param[out] payload Output buffer for the converted payload.
323 * @return @c true on success.
324 */
325 [[nodiscard]] virtual bool convert(const std::string& vlink_ser, const Bytes& raw, ConvertTarget target,
326 Bytes& payload) = 0;
327
328 /**
329 * @brief Extracts a message-level timestamp from the raw message, in nanoseconds.
330 *
331 * @details
332 * Called after @c convert() to obtain a per-message timestamp derived from the
333 * message content (e.g. a sensor timestamp field). This allows the visualization
334 * frontend to use the actual data timestamp rather than the proxy transport timestamp.
335 *
336 * The default implementation returns @c -1 (not available), meaning the server
337 * falls back to the proxy-provided timestamp. Override this method to extract
338 * timestamps from your message format.
339 *
340 * @param[in] vlink_ser VLink serialisation type name.
341 * @param[in] raw Raw serialised message bytes from VLink.
342 * @param[in] target The visualization backend.
343 * @return Timestamp in nanoseconds since epoch, or @c -1 if not available.
344 */
345 [[nodiscard]] virtual int64_t extract_timestamp(const std::string& vlink_ser, const Bytes& raw,
346 ConvertTarget target) {
347 (void)vlink_ser;
348 (void)raw;
349 (void)target;
350 return -1;
351 }
352
353 /**
354 * @brief Tests whether this plugin handles a frontend-published channel.
355 *
356 * @details
357 * This hook is used for inbound command/control flows such as Foxglove
358 * clientPublish. The default implementation returns @c false.
359 */
360 [[nodiscard]] virtual bool can_convert_frontend(const WebChannel& channel, ConvertTarget target) {
361 (void)channel;
362 (void)target;
363 return false;
364 }
365
366 /**
367 * @brief Resolves the destination VLink publish URL and serialisation type for
368 * an inbound frontend channel.
369 *
370 * @details
371 * Called when a frontend channel is advertised. Returning @c true enables the
372 * host to provision the required VLink publisher(s) ahead of time.
373 *
374 * The default implementation returns @c false.
375 */
376 [[nodiscard]] virtual bool get_publish_info(const WebChannel& channel, ConvertTarget target,
377 VlinkPublish& publish_info) {
378 (void)channel;
379 (void)target;
380 (void)publish_info;
381 return false;
382 }
383
384 /**
385 * @brief Converts a frontend-published payload into a raw VLink message payload.
386 *
387 * @details
388 * Called for each inbound frontend message after @c get_publish_info()
389 * resolved the destination topic. The plugin should write the destination raw
390 * VLink payload to @p payload.
391 *
392 * The default implementation returns @c false.
393 */
394 [[nodiscard]] virtual bool convert_frontend(const WebChannel& channel, const Bytes& raw, ConvertTarget target,
395 Bytes& payload) {
396 (void)channel;
397 (void)raw;
398 (void)target;
399 (void)payload;
400 return false;
401 }
402
403 private:
405};
406
407} // namespace vlink
Versatile byte buffer with small-buffer optimisation, ownership semantics and compression.
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
定义 macros.h:184
Type-safe dynamic plugin loader with version checking and lifecycle management.
#define VLINK_PLUGIN_REGISTER(InterfaceType)
Macro to register a plugin, automatically deriving its ID from the interface type name.
定义 plugin.h:343
Core type definitions shared across all VLink node implementations.