VLink  2.0.0
A high-performance communication middleware
object_array.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 object_array.h
26  * @brief Zero-copy variable-length array of fixed-size 3-D detection / tracking records.
27  *
28  * @details
29  * @c ObjectArray is the canonical message produced by 3-D perception, sensor
30  * fusion, and multi-object tracking pipelines. It packs a homogeneous array
31  * of @c Object records, each capturing the pose, dimensions, kinematics,
32  * classification, identity, and covariance of one obstacle. A 40-byte
33  * @c Header prefix carries sequencing and dual-timestamp metadata.
34  *
35  * @par Container vs. record layout
36  * The container struct is 112 bytes; each @c Object record is 144 bytes.
37  * Both sizes are locked by @c static_assert. @c Object uses 4-byte alignment
38  * because its widest field is 4 bytes; with the wire envelope (magic 4 +
39  * version 4 + struct 112) the payload starts at offset 120, an 8-byte boundary,
40  * so the requirement is comfortably met and a typed @c objects() pointer stays
41  * safe on strict-alignment architectures (ARM, AArch64).
42  *
43  * @par Object schema
44  * | Field | Type | Meaning |
45  * | ------------------------- | ------------------- | --------------------------------------------- |
46  * | @c label | @c char[32] | Class name (e.g. "car", "pedestrian") |
47  * | @c position[3] | @c float | Centre in metres (world frame) |
48  * | @c yaw | @c float | Heading in radians (REP-103) |
49  * | @c size[3] | @c float | Length, width, height in metres |
50  * | @c yaw_rate | @c float | Angular velocity in rad/s |
51  * | @c velocity[3] | @c float | Linear velocity in m/s |
52  * | @c score | @c float | Detection confidence in [0, 1] |
53  * | @c acceleration[3] | @c float | Linear acceleration in m/s^2 |
54  * | @c existence_probability | @c float | Existence probability in [0, 1] |
55  * | @c position_covariance[6] | @c float | Upper triangle of 3x3 covariance |
56  * | @c class_id | @c uint32_t | Numeric class identifier |
57  * | @c track_id | @c uint32_t | Tracking id (0 = unassociated detection) |
58  * | @c age | @c uint32_t | Tracking age in frames |
59  * | @c num_observations | @c uint32_t | Cumulative observation count |
60  * | @c motion_state | @c MotionState | Stationary / moving / stopped / parked |
61  * | @c source_type | @c SourceType | LiDAR / camera / radar / fusion / ultrasonic |
62  * | @c subtype_id | @c uint16_t | Fine-grained subtype |
63  * | @c reserved_buf | @c uint32_t | Reserved (wire-locked) |
64  *
65  * @par Wire format
66  * Both the container and the @c Object record are POD; @c memcpy is the
67  * canonical serialiser. The @c sizeof values are locked by @c static_assert
68  * and form a permanent contract: @c vlink::zerocopy::* containers offer NO
69  * forward and NO backward binary compatibility, and every field including
70  * reserved bytes is part of the wire contract.
71  * @code
72  * static_assert(sizeof(ObjectArray) == 112, "Sizeof must be 112 bytes.");
73  * static_assert(sizeof(ObjectArray::Object) == 144, "Sizeof must be 144 bytes.");
74  * @endcode
75  *
76  * @par Memory layout
77  * @code
78  * Offset Size Field
79  * ------ ---- --------------------------
80  * 0 40 Header header
81  * 40 8 uint8_t* data_
82  * 48 8 size_t capacity_
83  * 56 8 uint64_t update_time_ns_
84  * 64 16 char source_id_[16]
85  * 80 4 uint32_t channel_
86  * 84 4 uint32_t freq_
87  * 88 4 uint32_t count_
88  * 92 4 uint32_t pack_size_
89  * 96 1 bool is_owner_
90  * 97 1 uint8_t reserved_buf_
91  * 98 2 uint16_t reserved_buf2_
92  * 100 4 uint32_t reserved_buf3_
93  * 104 4 uint32_t reserved_buf4_
94  * 108 4 uint32_t reserved_buf5_
95  * ------ ---- --------------------------
96  * Total 112 bytes (alignas 8)
97  *
98  * Wire envelope:
99  * [ magic_begin (4) | version (4) | ObjectArray (112) | Object[0..count) (count*144) | magic_end (4) ]
100  * @endcode
101  *
102  * @par Reserved bytes
103  * @c reserved_buf_, @c reserved_buf2_, @c reserved_buf3_, @c reserved_buf4_, @c reserved_buf5_
104  * are exposed through @c get_reserved* helpers and survive @c clear() and
105  * the copy / move helpers so producers can stamp minor flags. None of these
106  * slots may be repurposed by application code: future library revisions may
107  * bind them to real fields, silently breaking peers that abused the slot.
108  *
109  * @par Example
110  * @code
111  * vlink::zerocopy::ObjectArray arr;
112  * arr.create(256);
113  * arr.set_source_id("fusion_v2");
114  *
115  * vlink::zerocopy::ObjectArray::Object obj;
116  * std::strncpy(obj.label, "car", sizeof(obj.label) - 1);
117  * obj.position[0] = 12.0F;
118  * obj.size[0] = 4.5F;
119  * obj.velocity[0] = 8.5F;
120  * obj.class_id = 1;
121  * obj.track_id = 42;
122  * arr.push_value(obj);
123  *
124  * vlink::Bytes wire;
125  * arr >> wire;
126  * @endcode
127  */
128 
129 #pragma once
130 
131 #include <cstdint>
132 #include <string_view>
133 
134 #include "../base/bytes.h"
135 #include "./header.h"
136 
137 namespace vlink {
138 
139 namespace zerocopy {
140 
141 /**
142  * @struct ObjectArray
143  * @brief 112-byte POD container holding a packed array of 144-byte @c Object records.
144  *
145  * @details
146  * The container size is locked at 112 bytes on 64-bit targets via
147  * @c static_assert. Records share a single owned allocation so a freshly
148  * deserialised array can return a typed @c objects() pointer that addresses
149  * the wire buffer in place.
150  */
151 struct VLINK_EXPORT_AND_ALIGNED(8) ObjectArray final {
152  /**
153  * @brief Motion / kinematic state classification for a tracked object.
154  */
155  enum MotionState : uint8_t {
156  kMotionUnknown = 0, ///< Unknown / uninitialised state.
157  kMotionStationary = 1, ///< Stationary obstacle (never expected to move).
158  kMotionMoving = 2, ///< Actively moving.
159  kMotionStopped = 3, ///< Temporarily stopped (e.g. at a red light).
160  kMotionParked = 4, ///< Long-term parked vehicle.
161  };
162 
163  /**
164  * @brief Origin sensor / pipeline that produced the detection.
165  */
166  enum SourceType : uint8_t {
167  kSourceUnknown = 0, ///< Unknown / uninitialised source.
168  kSourceLidar = 1, ///< LiDAR-only detection.
169  kSourceCamera = 2, ///< Camera-only detection.
170  kSourceRadar = 3, ///< Radar-only detection.
171  kSourceFusion = 4, ///< Multi-sensor fusion result.
172  kSourceUltrasonic = 5, ///< Ultrasonic-based detection.
173  };
174 
175  /**
176  * @struct Object
177  * @brief 144-byte POD record describing one detection / track.
178  *
179  * @details
180  * Public POD; assign fields directly and append with
181  * @c ObjectArray::push_value. The record uses 4-byte alignment because its
182  * widest field is 4 bytes; the containing payload starts at an 8-byte-aligned
183  * offset within the wire envelope, so typed access stays safe on
184  * strict-alignment CPUs.
185  */
186  struct VLINK_EXPORT_AND_ALIGNED(4) Object final {
187  /**
188  * @brief Default-constructs a zeroed record and asserts the 144-byte contract.
189  */
190  Object() noexcept;
191 
192  char label[32]{0}; ///< NUL-terminated class label (e.g. @c "car").
193  float position[3]{0}; ///< Centre position in metres (world frame).
194  float yaw{0}; ///< Heading in radians (REP-103).
195  float size[3]{0}; ///< Length, width, height bounding-box extents in metres.
196  float yaw_rate{0}; ///< Heading rate in radians per second.
197  float velocity[3]{0}; ///< Linear velocity in metres per second.
198  float score{0}; ///< Detection confidence in [0, 1].
199  float acceleration[3]{0}; ///< Linear acceleration in metres per second squared.
200  float existence_probability{0}; ///< Existence probability in [0, 1].
201  float position_covariance[6]{0}; ///< Upper-triangle of 3x3 position covariance (xx,xy,xz,yy,yz,zz).
202  uint32_t class_id{0}; ///< Numeric class identifier.
203  uint32_t track_id{0}; ///< Tracking identifier; 0 marks an unassociated detection.
204  uint32_t age{0}; ///< Tracking age in frames.
205  uint32_t num_observations{0}; ///< Cumulative observation count for this track.
206  MotionState motion_state{kMotionUnknown}; ///< Motion / kinematic classification.
207  SourceType source_type{kSourceUnknown}; ///< Producing sensor or pipeline.
208  uint16_t subtype_id{0}; ///< Fine-grained subtype (e.g. sedan vs SUV).
209  uint32_t reserved_buf{0}; ///< Reserved (wire-locked); do not repurpose.
210  };
211 
212  /**
213  * @brief Default-constructs an empty array and asserts the 112-byte container contract.
214  */
215  ObjectArray() noexcept;
216 
217  /**
218  * @brief Frees the owned record buffer when @c is_owner() is @c true.
219  */
220  ~ObjectArray() noexcept;
221 
222  /**
223  * @brief Deep-copies @p target into a freshly allocated array.
224  *
225  * @param target Source array to clone.
226  */
227  ObjectArray(const ObjectArray& target) noexcept;
228 
229  /**
230  * @brief Steals @p target's allocation and metadata; @p target ends empty.
231  *
232  * @param target Source array moved from.
233  */
234  ObjectArray(ObjectArray&& target) noexcept;
235 
236  /**
237  * @brief Deep-copy-assigns @p target; self-assignment is a no-op.
238  *
239  * @param target Source array to clone.
240  * @return Reference to @c *this.
241  */
242  ObjectArray& operator=(const ObjectArray& target) noexcept;
243 
244  /**
245  * @brief Move-assigns @p target; self-assignment is a no-op.
246  *
247  * @param target Source array moved from.
248  * @return Reference to @c *this.
249  */
250  ObjectArray& operator=(ObjectArray&& target) noexcept;
251 
252  /**
253  * @brief Deserialises an array from @p bytes using zero-copy borrowing semantics.
254  *
255  * @param bytes Wire buffer previously produced by @c operator>>.
256  * @return @c true on success; @c false on magic mismatch or size mismatch.
257  */
258  bool operator<<(const Bytes& bytes) noexcept;
259 
260  /**
261  * @brief Serialises the struct snapshot plus record buffer into @p bytes.
262  *
263  * @param bytes Output buffer; resized automatically when too small.
264  * @return Always @c true.
265  */
266  bool operator>>(Bytes& bytes) const noexcept;
267 
268  /**
269  * @brief Validates that @p bytes carries a well-formed @c ObjectArray envelope.
270  *
271  * @param bytes Wire buffer to inspect.
272  * @return @c true when both magic sentinels match and the minimum size holds.
273  */
274  [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
275 
276  /**
277  * @brief Total bytes that @c operator>> would write for this array.
278  *
279  * @return @c sizeof(magic_begin) + @c sizeof(version) + @c sizeof(ObjectArray) + @c count * @c pack_size + @c
280  * sizeof(magic_end).
281  */
282  [[nodiscard]] size_t get_serialized_size() const noexcept;
283 
284  /**
285  * @brief Whether the record buffer pointer is non-null and the array holds at least one record.
286  *
287  * @return @c true when the array holds usable records.
288  */
289  [[nodiscard]] bool is_valid() const noexcept;
290 
291  /**
292  * @brief Borrows @p target's record buffer without copying.
293  *
294  * @param target Source array whose buffer must outlive @c *this.
295  * @return @c false on self-borrow, otherwise @c true.
296  */
297  bool shallow_copy(const ObjectArray& target) noexcept;
298 
299  /**
300  * @brief Allocates (or reuses) an owned buffer and copies @p target's records.
301  *
302  * @param target Source array to clone.
303  * @return @c false on self-copy, otherwise @c true.
304  */
305  bool deep_copy(const ObjectArray& target) noexcept;
306 
307  /**
308  * @brief Transfers ownership from @p target; @p target ends empty.
309  *
310  * @param target Source array moved from.
311  * @return @c false on self-move, otherwise @c true.
312  */
313  bool move_copy(ObjectArray& target) noexcept;
314 
315  /**
316  * @brief Pre-allocates capacity for @p count records and resets the logical count.
317  *
318  * @param count Maximum number of records; must be non-zero.
319  * @return @c false when @p count is zero, otherwise @c true.
320  */
321  bool create(size_t count) noexcept;
322 
323  /**
324  * @brief Releases the owned buffer (if any) and resets metadata except reserved fields.
325  */
326  void clear() noexcept;
327 
328  /**
329  * @brief Appends a record at the end of the buffer.
330  *
331  * @param object Source record.
332  * @return @c false on capacity overflow or invalid state.
333  */
334  bool push_value(const Object& object) noexcept;
335 
336  /**
337  * @brief Overwrites the record at @p index.
338  *
339  * @param index Zero-based slot index; must be less than @c count().
340  * @param object New record contents.
341  * @return @c false on out-of-range or invalid state.
342  */
343  bool set_value(uint32_t index, const Object& object) noexcept;
344 
345  /**
346  * @brief Reads the record at @p index into @p object (zeroed on out-of-range).
347  *
348  * @param index Zero-based slot index.
349  * @param object Destination.
350  * @return @c false on out-of-range.
351  */
352  bool get_value(uint32_t index, Object& object) const noexcept;
353 
354  /**
355  * @brief Returns the record at @p index by value (zero-initialised on out-of-range).
356  *
357  * @param index Zero-based slot index.
358  * @return Copy of the record, or a zero-initialised @c Object when out of range.
359  */
360  [[nodiscard]] Object get_value(uint32_t index) const noexcept;
361 
362  /**
363  * @brief Resets the logical record count without reallocating.
364  *
365  * @param count New logical count; must not exceed allocated capacity.
366  * @return @c false on capacity overflow.
367  */
368  bool resize(uint32_t count) noexcept;
369 
370  /**
371  * @brief Producer-side timestamp recording when the array was assembled.
372  *
373  * @return Stored nanosecond timestamp.
374  */
375  [[nodiscard]] uint64_t update_time_ns() const noexcept;
376 
377  /**
378  * @brief Producer / module identifier (e.g. @c "fusion_v2").
379  *
380  * @return Non-owning view into the embedded buffer.
381  */
382  [[nodiscard]] std::string_view source_id() const noexcept;
383 
384  /**
385  * @brief Sensor / producer channel identifier.
386  *
387  * @return Stored channel id.
388  */
389  [[nodiscard]] uint32_t channel() const noexcept;
390 
391  /**
392  * @brief Nominal publish frequency in Hz.
393  *
394  * @return Stored value.
395  */
396  [[nodiscard]] uint32_t freq() const noexcept;
397 
398  /**
399  * @brief Logical record count currently stored.
400  *
401  * @return Number of populated records.
402  */
403  [[nodiscard]] uint32_t count() const noexcept;
404 
405  /**
406  * @brief Byte size of one @c Object record (always @c sizeof(Object)).
407  *
408  * @return Record stride.
409  */
410  [[nodiscard]] uint32_t pack_size() const noexcept;
411 
412  /**
413  * @brief Read-only pointer to the @c Object at @p index.
414  *
415  * @param index Zero-based slot index (default 0 returns the array base).
416  * @return Typed pointer, or @c nullptr on out-of-range / invalid state.
417  */
418  [[nodiscard]] const Object* objects(uint32_t index = 0) const noexcept;
419 
420  /**
421  * @brief Read-only pointer to the raw contiguous record buffer.
422  *
423  * @return Byte pointer to the first record.
424  */
425  [[nodiscard]] const uint8_t* data() const noexcept;
426 
427  /**
428  * @brief Total byte capacity of the allocated record buffer.
429  *
430  * @return Capacity in bytes, or 0 when no buffer is allocated.
431  */
432  [[nodiscard]] size_t capacity() const noexcept;
433 
434  /**
435  * @brief Whether this array owns its record buffer.
436  *
437  * @return @c true when the destructor would free the buffer.
438  */
439  [[nodiscard]] bool is_owner() const noexcept;
440 
441  /**
442  * @brief Stores the producer-side timestamp.
443  *
444  * @param update_time_ns Timestamp in nanoseconds.
445  */
446  void set_update_time_ns(uint64_t update_time_ns) noexcept;
447 
448  /**
449  * @brief Stores the producer / module identifier (truncated to @c sizeof(source_id) - 1 bytes).
450  *
451  * @param source_id Identifier string.
452  */
453  void set_source_id(std::string_view source_id) noexcept;
454 
455  /**
456  * @brief Stores the sensor / producer channel identifier.
457  *
458  * @param channel Channel id.
459  */
460  void set_channel(uint32_t channel) noexcept;
461 
462  /**
463  * @brief Stores the nominal publish frequency.
464  *
465  * @param freq Frequency in Hz.
466  */
467  void set_freq(uint32_t freq) noexcept;
468 
469  /**
470  * @brief Mutable accessor for the first wire-locked reserved field (@c uint8_t).
471  *
472  * @return Reference to @c reserved_buf_.
473  */
474  uint8_t& get_reserved() noexcept { return reserved_buf_; }
475 
476  /**
477  * @brief Mutable accessor for the second wire-locked reserved field (@c uint16_t).
478  *
479  * @return Reference to @c reserved_buf2_.
480  */
481  uint16_t& get_reserved2() noexcept { return reserved_buf2_; }
482 
483  /**
484  * @brief Mutable accessor for the third wire-locked reserved field (@c uint32_t).
485  *
486  * @return Reference to @c reserved_buf3_.
487  */
488  uint32_t& get_reserved3() noexcept { return reserved_buf3_; }
489 
490  /**
491  * @brief Mutable accessor for the fourth wire-locked reserved field (@c uint32_t).
492  *
493  * @return Reference to @c reserved_buf4_.
494  */
495  uint32_t& get_reserved4() noexcept { return reserved_buf4_; }
496 
497  /**
498  * @brief Mutable accessor for the fifth wire-locked reserved field (@c uint32_t).
499  *
500  * @return Reference to @c reserved_buf5_.
501  */
502  uint32_t& get_reserved5() noexcept { return reserved_buf5_; }
503 
504  Header header; ///< Sequencing and timestamp metadata prefix.
505 
506  static constexpr bool kZerocopyTypes{true}; ///< Marker probed by the VLink type-trait machinery.
507 
508  private:
509  uint8_t* data_{nullptr};
510  size_t capacity_{0};
511  uint64_t update_time_ns_{0};
512  char source_id_[16]{0};
513  uint32_t channel_{0};
514  uint32_t freq_{0};
515  uint32_t count_{0};
516  uint32_t pack_size_{0};
517  bool is_owner_{false};
518  uint8_t reserved_buf_{0};
519  uint16_t reserved_buf2_{0};
520  uint32_t reserved_buf3_{0};
521  uint32_t reserved_buf4_{0};
522  uint32_t reserved_buf5_{0};
523 
524  static constexpr uint32_t kMagicNumberBegin{0x98B7F18A};
525  static constexpr uint32_t kMagicNumberEnd{0x98B7F18F};
526 };
527 
528 } // namespace zerocopy
529 
530 } // namespace vlink
Fixed-size timestamp and sequencing prefix embedded by every VLink zero-copy container.
40-byte timestamp / sequencing metadata prefix shared by all zero-copy containers.
112-byte POD container holding a packed array of 144-byte Object records.
Definition: object_array.h:151