VLink 2.0.0
A high-performance communication middleware
Loading...
Searching...
No Matches
point_cloud.h
Go to the documentation of this file.
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 point_cloud.h
26 * @brief Zero-copy, schema-aware 3-D point cloud container for VLink transport.
27 *
28 * @details
29 * @c PointCloud stores an array of fixed-size point records together with a
30 * compact compile-time schema that describes the name, type, and byte size of
31 * every field per point. The schema is encoded into two @c uint64_t values
32 * (@c size_num and @c type_num) where each nibble (4 bits) encodes one field,
33 * and a comma-separated name string (up to 160 chars, 3-16 fields).
34 *
35 * @par Protocol encoding
36 * @code
37 * size_num: 0x0408... (nibble N = byte-size of field N, high nibble = first field)
38 * type_num: 0x0A0B... (nibble N = Type enum value of field N)
39 * names: "x,y,z,intensity"
40 * @endcode
41 *
42 * @par Supported field types
43 * | Enum | C++ type | Size |
44 * | ------------- | --------- | ---- |
45 * | kBoolType | bool | 1 |
46 * | kInt8Type | int8_t | 1 |
47 * | kUint8Type | uint8_t | 1 |
48 * | kInt16Type | int16_t | 2 |
49 * | kUint16Type | uint16_t | 2 |
50 * | kInt32Type | int32_t | 4 |
51 * | kUint32Type | uint32_t | 4 |
52 * | kInt64Type | int64_t | 8 |
53 * | kUint64Type | uint64_t | 8 |
54 * | kFloatType | float | 4 |
55 * | kDoubleType | double | 8 |
56 *
57 * @par Binary wire format
58 * @code
59 * [ magic_begin (4) | PointCloud struct (256) | point data (size * pack_size) | magic_end (4) ]
60 * @endcode
61 *
62 * @par Usage -- float XYZ + intensity
63 * @code
64 * vlink::zerocopy::PointCloud pc;
65 * pc.create_v3f<float>(1000, {"x", "y", "z", "intensity"});
66 *
67 * // Append points
68 * pc.push_value_v3f(1.0f, 2.0f, 3.0f, 0.8f);
69 * pc.push_value_v3f(4.0f, 5.0f, 6.0f, 0.5f);
70 *
71 * // Read back
72 * auto key_map = pc.get_key_map();
73 * float x = pc.get_value<float>(0, key_map, "x");
74 *
75 * // Serialise
76 * vlink::Bytes wire;
77 * pc >> wire;
78 *
79 * // Deserialise (zero-copy, borrows wire)
80 * vlink::zerocopy::PointCloud pc2;
81 * pc2 << wire;
82 * @endcode
83 *
84 * @note
85 * - 32-bit architectures emit a compile-time warning and are not supported.
86 * - After @c operator<<, the data pointer references memory inside the source
87 * @c Bytes. The @c Bytes must outlive the @c PointCloud.
88 * - @c create<T...>() requires 3-16 type parameters, all of which must be
89 * fundamental types.
90 * - @c set_value() requires @c resize() to have been called first so that the
91 * internal write-cursor is positioned correctly.
92 * - The struct is exactly 256 bytes on 64-bit platforms (verified by
93 * @c static_assert).
94 */
95
96#pragma once
97
98#include <cstdint>
99#include <iostream>
100#include <string>
101#include <string_view>
102#include <unordered_map>
103#include <vector>
104
105#include "../base/bytes.h"
106#include "./header.h"
107
108namespace vlink {
109
110namespace zerocopy {
111
112/**
113 * @struct PointCloud
114 * @brief Schema-aware zero-copy 3-D point cloud with typed per-point fields.
115 *
116 * @details
117 * Stores an array of homogeneous point records whose layout is described by
118 * a compact binary protocol embedded in the struct. The struct size is fixed
119 * at 256 bytes on 64-bit targets.
120 */
121struct VLINK_EXPORT_AND_ALIGNED(8) PointCloud final {
122 /**
123 * @brief Fundamental field type tags used in the binary protocol.
124 *
125 * @details
126 * Each nibble in @c Protocol::type_num encodes one of these values for
127 * the corresponding field.
128 */
129 enum Type : uint8_t {
130 kUnknownType = 0, ///< Unknown or unsupported type.
131 kBoolType = 1, ///< bool (1 byte).
132 kInt8Type = 2, ///< int8_t (1 byte).
133 kUint8Type = 3, ///< uint8_t (1 byte).
134 kInt16Type = 4, ///< int16_t (2 bytes).
135 kUint16Type = 5, ///< uint16_t (2 bytes).
136 kInt32Type = 6, ///< int32_t (4 bytes).
137 kUint32Type = 7, ///< uint32_t (4 bytes).
138 kInt64Type = 8, ///< int64_t (8 bytes).
139 kUint64Type = 9, ///< uint64_t (8 bytes).
140 kFloatType = 10, ///< float (4 bytes).
141 kDoubleType = 11, ///< double (8 bytes).
142 };
143
144 /**
145 * @struct Key
146 * @brief Describes one named field in a point record.
147 */
148 struct Key final {
149 std::string name; ///< Field name (e.g., "x", "intensity").
150 uint8_t type{kUnknownType}; ///< @c Type enum value for this field.
151 uint8_t size{0}; ///< Field byte size derived from the @c Type.
152 };
153
154 /**
155 * @brief Maps a field name to its byte offset within one packed point record.
156 *
157 * @details
158 * Obtain via @c get_key_map() and pass to the @c get_value() key-map
159 * overloads for efficient repeated access.
160 */
161 using KeyMap = std::unordered_map<std::string, uint16_t>;
162
163 /**
164 * @brief Ordered list of field descriptors for one point record.
165 */
166 using KeyList = std::vector<Key>;
167
168 /**
169 * @struct Vector3f
170 * @brief Compact 3-component single-precision vector (12 bytes, 4-byte aligned).
171 *
172 * @details
173 * Convenience type for XYZ coordinates when using the @c float-based
174 * @c create_v3f, @c push_value_v3f, and @c get_value_v3f helpers.
175 */
176 struct VLINK_EXPORT_AND_ALIGNED(4) Vector3f final {
177 float x{0}; ///< X component.
178 float y{0}; ///< Y component.
179 float z{0}; ///< Z component.
180
181 /**
182 * @brief Default constructor.
183 *
184 * @details
185 * Verifies via @c static_assert that @c sizeof(Vector3f) == 12 on
186 * 64-bit platforms.
187 */
188 Vector3f() noexcept;
189
190 /**
191 * @brief Value constructor.
192 *
193 * @param _x X component.
194 * @param _y Y component.
195 * @param _z Z component.
196 */
197 explicit Vector3f(float _x, float _y, float _z) noexcept;
198
199 /**
200 * @brief Formats the vector as @c (x, y, z) on an output stream.
201 *
202 * @param ostream Output stream.
203 * @param v3f Vector to format.
204 * @return Reference to @p ostream.
205 */
206 friend std::ostream& operator<<(std::ostream& ostream, const Vector3f& v3f) noexcept;
207 };
208
209 /**
210 * @struct Vector3d
211 * @brief Compact 3-component double-precision vector (24 bytes, 8-byte aligned).
212 *
213 * @details
214 * Convenience type for XYZ coordinates when using the @c double-based
215 * @c create_v3d, @c push_value_v3d, and @c get_value_v3d helpers.
216 */
217 struct VLINK_EXPORT_AND_ALIGNED(8) Vector3d final {
218 double x{0}; ///< X component.
219 double y{0}; ///< Y component.
220 double z{0}; ///< Z component.
221
222 /**
223 * @brief Default constructor.
224 *
225 * @details
226 * Verifies via @c static_assert that @c sizeof(Vector3d) == 24 on
227 * 64-bit platforms.
228 */
229 Vector3d() noexcept;
230
231 /**
232 * @brief Value constructor.
233 *
234 * @param _x X component.
235 * @param _y Y component.
236 * @param _z Z component.
237 */
238 explicit Vector3d(double _x, double _y, double _z) noexcept;
239
240 /**
241 * @brief Formats the vector as @c (x, y, z) on an output stream.
242 *
243 * @param ostream Output stream.
244 * @param v3d Vector to format.
245 * @return Reference to @p ostream.
246 */
247 friend std::ostream& operator<<(std::ostream& ostream, const Vector3d& v3d) noexcept;
248 };
249
250 /**
251 * @brief Default constructor.
252 *
253 * @details
254 * Verifies via @c static_assert that the struct is exactly 256 bytes on
255 * 64-bit platforms.
256 */
257 PointCloud() noexcept;
258
259 /**
260 * @brief Destructor -- frees the owned point data buffer if @c is_owner() is @c true.
261 */
262 ~PointCloud() noexcept;
263
264 /**
265 * @brief Copy constructor -- performs a deep copy of @p target.
266 *
267 * @param target Source to copy.
268 */
269 PointCloud(const PointCloud& target) noexcept;
270
271 /**
272 * @brief Move constructor -- transfers ownership from @p target.
273 *
274 * @param target Source to move from. Left empty after the call.
275 */
276 PointCloud(PointCloud&& target) noexcept;
277
278 /**
279 * @brief Copy-assignment operator -- deep-copies @p target.
280 *
281 * @param target Source to copy. Self-assignment is a no-op.
282 * @return Reference to @c *this.
283 */
284 PointCloud& operator=(const PointCloud& target) noexcept;
285
286 /**
287 * @brief Move-assignment operator -- transfers ownership from @p target.
288 *
289 * @param target Source to move. Self-assignment is a no-op.
290 * @return Reference to @c *this.
291 */
292 PointCloud& operator=(PointCloud&& target) noexcept;
293
294 /**
295 * @brief Deserialises a @c PointCloud from a @c Bytes wire buffer.
296 *
297 * @details
298 * Validates magic-number sentinels, copies the struct header (including
299 * the embedded @c Protocol), and sets the point data pointer to reference
300 * memory inside @p bytes (zero-copy; @c is_owner() == false).
301 * @p bytes must outlive this @c PointCloud.
302 *
303 * @param bytes Buffer produced by @c operator>>.
304 * @return @c true on success; @c false on invalid magic numbers or
305 * size mismatch.
306 */
307 bool operator<<(const Bytes& bytes) noexcept;
308
309 /**
310 * @brief Serialises this @c PointCloud into a @c Bytes wire buffer.
311 *
312 * @details
313 * Writes the magic-number envelope, all struct fields (including the
314 * protocol descriptor), and @c size() * @c pack_size() bytes of point data.
315 *
316 * @param bytes Output buffer (reallocated if the size does not match).
317 * @return Always @c true.
318 */
319 bool operator>>(Bytes& bytes) const noexcept;
320
321 /**
322 * @brief Checks whether @p bytes contains a valid @c PointCloud wire buffer.
323 *
324 * @param bytes Buffer to validate.
325 * @return @c true if magic sentinels are present and minimum size is met.
326 */
327 [[nodiscard]] static bool check_valid(const Bytes& bytes) noexcept;
328
329 /**
330 * @brief Returns @c true when the object has a valid point buffer.
331 *
332 * @return @c true if data pointer is non-null, @c size() > 0, and
333 * @c pack_size() > 0.
334 */
335 [[nodiscard]] bool is_valid() const noexcept;
336
337 /**
338 * @brief Borrows protocol and data pointer from @p target without copying.
339 *
340 * @details
341 * @c is_owner() becomes @c false. Any previously owned buffer is freed.
342 * The caller must ensure @p target outlives this object.
343 *
344 * @param target Source to borrow from.
345 * @return @c false if @p target == @c *this, otherwise @c true.
346 */
347 bool shallow_copy(const PointCloud& target) noexcept;
348
349 /**
350 * @brief Deep-copies protocol and point data from @p target.
351 *
352 * @details
353 * If @c *this already owns a buffer of the same capacity the data is copied
354 * in-place; otherwise a new buffer is allocated.
355 *
356 * @param target Source to copy.
357 * @return @c false if @p target == @c *this, otherwise @c true.
358 */
359 bool deep_copy(const PointCloud& target) noexcept;
360
361 /**
362 * @brief Transfers ownership of the point buffer from @p target to @c *this.
363 *
364 * @details
365 * After the call @p target is empty.
366 *
367 * @param target Source to move from.
368 * @return @c false if @p target == @c *this, otherwise @c true.
369 */
370 bool move_copy(PointCloud& target) noexcept;
371
372 /**
373 * @brief Returns the total serialised byte count for this point cloud.
374 *
375 * @details
376 * Equals: @c sizeof(magic_begin) + @c sizeof(PointCloud) + @c size() * @c pack_size() + @c sizeof(magic_end)
377 *
378 * @return Total bytes written by @c operator>>.
379 */
380 [[nodiscard]] size_t get_serialized_size() const noexcept;
381
382 /**
383 * @brief Builds a @c KeyMap from the embedded protocol descriptor.
384 *
385 * @details
386 * The returned map maps each field name to its byte offset within one
387 * packed point record. Optionally fills @p key_list with ordered @c Key
388 * entries that also carry type information.
389 *
390 * @param key_list Optional output; filled with ordered field descriptors.
391 * @return Unordered map from field name to byte offset.
392 */
393 [[nodiscard]] KeyMap get_key_map(KeyList* key_list = nullptr) const noexcept;
394
395 /**
396 * @brief Returns the number of points currently stored.
397 *
398 * @return Point count (@c 0 if empty).
399 */
400 [[nodiscard]] size_t size() const noexcept;
401
402 /**
403 * @brief Returns the byte size of one packed point record.
404 *
405 * @details
406 * Equals the sum of the byte sizes of all fields as encoded in the
407 * @c Protocol. Computed once during @c create() and cached.
408 *
409 * @return Bytes per point, or 0 if no schema has been set.
410 */
411 [[nodiscard]] size_t pack_size() const noexcept;
412
413 /**
414 * @brief Returns @c true if this object owns its point data buffer.
415 *
416 * @details
417 * An owned buffer is freed in the destructor. Borrowed buffers (from
418 * @c shallow_copy or @c operator<<) are not freed.
419 *
420 * @return @c true when memory ownership is held.
421 */
422 [[nodiscard]] bool is_owner() const noexcept;
423
424 /**
425 * @brief Returns the raw @c size_num protocol field.
426 *
427 * @details
428 * Each nibble encodes the byte size of one field (high nibble = first field).
429 *
430 * @return Protocol size-encoding integer.
431 */
432 [[nodiscard]] uint64_t get_protocol_size_num() const noexcept;
433
434 /**
435 * @brief Returns the raw @c type_num protocol field.
436 *
437 * @details
438 * Each nibble encodes the @c Type enum value of one field.
439 *
440 * @return Protocol type-encoding integer.
441 */
442 [[nodiscard]] uint64_t get_protocol_type_num() const noexcept;
443
444 /**
445 * @brief Returns a human-readable comma-separated field size string.
446 *
447 * @details
448 * Example output for a float XYZ + uint8 intensity schema: @c "4,4,4,1"
449 *
450 * @return Printable size string.
451 */
452 [[nodiscard]] std::string get_protocol_size_str() const noexcept;
453
454 /**
455 * @brief Returns the raw comma-separated field name string.
456 *
457 * @details
458 * Example: @c "x,y,z,intensity"
459 *
460 * @return Field names as stored in the protocol (up to 160 characters).
461 */
462 [[nodiscard]] std::string get_protocol_name_str() const noexcept;
463
464 /**
465 * @brief Returns a human-readable comma-separated field type string.
466 *
467 * @details
468 * Example: @c "float,float,float,uint8"
469 *
470 * @return Printable type string.
471 */
472 [[nodiscard]] std::string get_protocol_type_str() const noexcept;
473
474 /**
475 * @brief Returns a read-only pointer to the raw packed point buffer.
476 *
477 * @details
478 * The buffer contains @c size() * @c pack_size() bytes in tightly-packed
479 * row order (one row = one point).
480 *
481 * @return Pointer to the point data, or @c nullptr if empty.
482 */
483 [[nodiscard]] const uint8_t* get_internal_data() const noexcept;
484
485 /**
486 * @brief Returns the maximum number of points the current allocation can hold.
487 *
488 * @details
489 * Computed as @c capacity_ / @c pack_size_. For buffers obtained from
490 * @c operator<< (non-owned) this returns 0.
491 *
492 * @return Reserved (pre-allocated) point capacity.
493 */
494 [[nodiscard]] size_t get_reserved_size() const noexcept;
495
496 /**
497 * @brief Reads the first three floats of point @p loop_index as XYZ.
498 *
499 * @details
500 * Requires that the schema was created with @c create_v3f or that the first
501 * three fields are @c float. Reads directly using @c memcpy.
502 *
503 * @param x Output X value.
504 * @param y Output Y value.
505 * @param z Output Z value.
506 * @param loop_index Zero-based point index.
507 * @return @c false if @p loop_index is out of range.
508 */
509 bool get_value_v3f(float& x, float& y, float& z, size_t loop_index) const noexcept;
510
511 /**
512 * @brief Reads the first three floats of point @p loop_index into a @c Vector3f.
513 *
514 * @param v3f Output vector.
515 * @param loop_index Zero-based point index.
516 * @return @c false if @p loop_index is out of range.
517 */
518 bool get_value_v3f(Vector3f& v3f, size_t loop_index) const noexcept;
519
520 /**
521 * @brief Returns the first three floats of point @p loop_index as a @c Vector3f.
522 *
523 * @param loop_index Zero-based point index.
524 * @return @c Vector3f with X, Y, Z; zero-initialised on out-of-range.
525 */
526 [[nodiscard]] Vector3f get_value_v3f(size_t loop_index) const noexcept;
527
528 /**
529 * @brief Reads the first three doubles of point @p loop_index as XYZ.
530 *
531 * @param x Output X value.
532 * @param y Output Y value.
533 * @param z Output Z value.
534 * @param loop_index Zero-based point index.
535 * @return @c false if @p loop_index is out of range.
536 */
537 bool get_value_v3d(double& x, double& y, double& z, size_t loop_index) const noexcept;
538
539 /**
540 * @brief Reads the first three doubles of point @p loop_index into a @c Vector3d.
541 *
542 * @param v3d Output vector.
543 * @param loop_index Zero-based point index.
544 * @return @c false if @p loop_index is out of range.
545 */
546 bool get_value_v3d(Vector3d& v3d, size_t loop_index) const noexcept;
547
548 /**
549 * @brief Returns the first three doubles of point @p loop_index as a @c Vector3d.
550 *
551 * @param loop_index Zero-based point index.
552 * @return @c Vector3d with X, Y, Z; zero-initialised on out-of-range.
553 */
554 [[nodiscard]] Vector3d get_value_v3d(size_t loop_index) const noexcept;
555
556 /**
557 * @brief Reads a fundamental-type field from point @p loop_index at byte @p offset.
558 *
559 * @details
560 * @p T must be a fundamental type (enforced by @c static_assert). Use
561 * @c get_key_map() to resolve field names to byte offsets.
562 *
563 * @tparam T Field type; must be fundamental.
564 * @param t Output value.
565 * @param loop_index Zero-based point index.
566 * @param offset Byte offset within one packed record.
567 * @return @c false and zeroes @p t if the access would be out of bounds.
568 */
569 template <typename T>
570 bool get_value(T& t, size_t loop_index, uint16_t offset) const noexcept;
571
572 /**
573 * @brief Returns a fundamental-type field from point @p loop_index at byte @p offset.
574 *
575 * @tparam T Field type; must be fundamental.
576 * @param loop_index Zero-based point index.
577 * @param offset Byte offset within one packed record.
578 * @return Field value; zero-initialised if out of bounds.
579 */
580 template <typename T>
581 [[nodiscard]] T get_value(size_t loop_index, uint16_t offset) const noexcept;
582
583 /**
584 * @brief Reads a named field from point @p loop_index using a cached @c KeyMap.
585 *
586 * @details
587 * Looks up @p key in @p key_map to obtain the byte offset, then calls the
588 * offset-based overload. Returns @c false and zeroes @p t if the key is not
589 * found.
590 *
591 * @tparam T Field type; must be fundamental.
592 * @param t Output value.
593 * @param loop_index Zero-based point index.
594 * @param key_map Map obtained from @c get_key_map().
595 * @param key Field name to look up.
596 * @return @c false if the key is not found or the access is out of bounds.
597 */
598 template <typename T>
599 bool get_value(T& t, size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept;
600
601 /**
602 * @brief Returns a named field from point @p loop_index using a cached @c KeyMap.
603 *
604 * @tparam T Field type; must be fundamental.
605 * @param loop_index Zero-based point index.
606 * @param key_map Map obtained from @c get_key_map().
607 * @param key Field name.
608 * @return Field value; zero-initialised if not found or out of bounds.
609 */
610 template <typename T>
611 [[nodiscard]] T get_value(size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept;
612
613 /**
614 * @brief Returns a field value as @c double for display or numeric analysis.
615 *
616 * @details
617 * Dispatches to the appropriate @c get_value<T> specialisation based on
618 * @p type and casts the result to @c double. Useful when the caller knows
619 * the field type at runtime (e.g. from @c Key::type).
620 *
621 * @param loop_index Zero-based point index.
622 * @param offset Byte offset within one record.
623 * @param type @c Type enum value for this field.
624 * @return Field value as @c double, or 0 on unknown type.
625 */
626 [[nodiscard]] double get_value_for_double_float(size_t loop_index, uint16_t offset, uint8_t type) const noexcept;
627
628 /**
629 * @brief Returns a named field value as @c double using a @c KeyMap.
630 *
631 * @param loop_index Zero-based point index.
632 * @param key_map Map obtained from @c get_key_map().
633 * @param key Field name.
634 * @param type @c Type enum value for this field.
635 * @return Field value as @c double, or 0 on unknown type or key not found.
636 */
637 [[nodiscard]] double get_value_for_double_float(size_t loop_index, KeyMap& key_map, std::string_view key,
638 uint8_t type) const noexcept;
639
640 /**
641 * @brief Returns a field value formatted as a printable string.
642 *
643 * @details
644 * Calls @c std::to_string (or "true"/"false" for bool) based on @p type.
645 *
646 * @param loop_index Zero-based point index.
647 * @param offset Byte offset within one record.
648 * @param type @c Type enum value.
649 * @return String representation, or empty on unknown type.
650 */
651 [[nodiscard]] std::string get_value_for_print(size_t loop_index, uint16_t offset, uint8_t type) const noexcept;
652
653 /**
654 * @brief Returns a named field value formatted as a printable string.
655 *
656 * @param loop_index Zero-based point index.
657 * @param key_map Map obtained from @c get_key_map().
658 * @param key Field name.
659 * @param type @c Type enum value.
660 * @return String representation, or empty on unknown type or key not found.
661 */
662 [[nodiscard]] std::string get_value_for_print(size_t loop_index, KeyMap& key_map, std::string_view key,
663 uint8_t type) const noexcept;
664
665 /**
666 * @brief Creates a point cloud from pre-built protocol integers and a name string.
667 *
668 * @details
669 * Low-level creation path used when the caller has already computed
670 * @p size_num and @p type_num (e.g. from a received wire buffer).
671 * Use the template @c create<T...>() for type-safe construction.
672 *
673 * @param size Maximum number of points to pre-allocate.
674 * @param size_num Protocol size encoding (nibbles = byte sizes per field).
675 * @param type_num Protocol type encoding (nibbles = @c Type per field).
676 * @param key_str Comma-separated field names (max 160 bytes, 3-16 fields).
677 * @return @c false if the protocol parameters are invalid.
678 */
679 bool create(size_t size, uint64_t size_num, uint64_t type_num, std::string_view key_str) noexcept;
680
681 /**
682 * @brief Creates a point cloud with a type-safe variadic field schema.
683 *
684 * @details
685 * Derives @c size_num and @c type_num at compile time from the template
686 * parameters. @p keys must contain exactly @c sizeof...(T) names.
687 *
688 * @tparam T... Field types; must all be fundamental. 3-16 types required.
689 * @param _size Maximum number of points.
690 * @param keys Field names in the same order as @c T... .
691 * @return @c false if the number of keys does not match or is out of range.
692 */
693 template <typename... T>
694 bool create(size_t _size, const std::vector<std::string>& keys = {}) noexcept;
695
696 /**
697 * @brief Creates a point cloud with @c float XYZ as the first three fields,
698 * plus additional variadic fields.
699 *
700 * @details
701 * Prepends @c "x","y","z" to @p keys and @c float,float,float to the type list,
702 * then calls @c create<float,float,float,T...>().
703 *
704 * @tparam T... Types for additional fields beyond XYZ.
705 * @param _size Maximum number of points.
706 * @param keys Names for the additional fields (not including x, y, z).
707 * @return @c false if schema creation fails.
708 */
709 template <typename... T>
710 bool create_v3f(size_t _size, const std::vector<std::string>& keys = {}) noexcept;
711
712 /**
713 * @brief Creates a point cloud with @c double XYZ as the first three fields,
714 * plus additional variadic fields.
715 *
716 * @details
717 * Prepends @c "x","y","z" and @c double,double,double, then calls
718 * @c create<double,double,double,T...>().
719 *
720 * @tparam T... Types for additional fields beyond XYZ.
721 * @param _size Maximum number of points.
722 * @param keys Names for the additional fields (not including x, y, z).
723 * @return @c false if schema creation fails.
724 */
725 template <typename... T>
726 bool create_v3d(size_t _size, const std::vector<std::string>& keys = {}) noexcept;
727
728 /**
729 * @brief Bulk-fills the point buffer from pre-packed external memory.
730 *
731 * @details
732 * Copies @p _size * @c pack_size() bytes from @p src_data directly into the
733 * owned buffer without interpreting field types. The schema (protocol) must
734 * have been set up by a prior @c create() call. The buffer must be large
735 * enough to hold @p _size points.
736 *
737 * @param src_data Source buffer in packed row order. Must be non-null.
738 * @param _size Number of points to copy. Must be non-zero.
739 * @return @c false on invalid arguments or insufficient capacity.
740 */
741 bool fill_packed_data(const uint8_t* src_data, size_t _size) noexcept;
742
743 /**
744 * @brief Appends one point record at the end of the buffer.
745 *
746 * @details
747 * Each argument maps to one field in schema order. The total byte size of
748 * @c T... must exactly equal @c pack_size(). Returns @c false if the buffer
749 * is full or an owned buffer has not been set up.
750 *
751 * @tparam T... Field value types; must all be fundamental. 3-16 required.
752 * @param args Field values in schema order.
753 * @return @c false if the buffer is full or the pack size does not match.
754 */
755 template <typename... T>
756 bool push_value(T... args) noexcept;
757
758 /**
759 * @brief Appends one point with float XYZ coordinates plus additional fields.
760 *
761 * @tparam T... Types for additional fields beyond XYZ.
762 * @param x X coordinate.
763 * @param y Y coordinate.
764 * @param z Z coordinate.
765 * @param args Additional field values.
766 * @return @c false on overflow or schema mismatch.
767 */
768 template <typename... T>
769 bool push_value_v3f(float x, float y, float z, T... args) noexcept;
770
771 /**
772 * @brief Appends one point from a @c Vector3f plus additional fields.
773 *
774 * @tparam T... Types for additional fields beyond XYZ.
775 * @param v3f XYZ coordinates.
776 * @param args Additional field values.
777 * @return @c false on overflow or schema mismatch.
778 */
779 template <typename... T>
780 bool push_value_v3f(Vector3f v3f, T... args) noexcept;
781
782 /**
783 * @brief Appends one point with double XYZ coordinates plus additional fields.
784 *
785 * @tparam T... Types for additional fields beyond XYZ.
786 * @param x X coordinate.
787 * @param y Y coordinate.
788 * @param z Z coordinate.
789 * @param args Additional field values.
790 * @return @c false on overflow or schema mismatch.
791 */
792 template <typename... T>
793 bool push_value_v3d(double x, double y, double z, T... args) noexcept;
794
795 /**
796 * @brief Appends one point from a @c Vector3d plus additional fields.
797 *
798 * @tparam T... Types for additional fields beyond XYZ.
799 * @param v3d XYZ coordinates.
800 * @param args Additional field values.
801 * @return @c false on overflow or schema mismatch.
802 */
803 template <typename... T>
804 bool push_value_v3d(Vector3d v3d, T... args) noexcept;
805
806 /**
807 * @brief Sets the logical point count without reallocating.
808 *
809 * @details
810 * Updates the internal write cursor to @c size * @c pack_size_. Useful
811 * before calling @c set_value() to overwrite existing records. Does not
812 * zero-initialise newly exposed records.
813 *
814 * @param size New logical point count. Must not exceed allocated capacity.
815 * @return @c false if no owned buffer exists or @c pack_size_ is zero.
816 */
817 bool resize(size_t size) noexcept;
818
819 /**
820 * @brief Overwrites the point record at @p loop_index with new field values.
821 *
822 * @details
823 * Requires that @c resize() has been called first to set the logical size
824 * so the internal cursor is consistent. Returns @c false and logs a
825 * diagnostic if the cursor state is invalid.
826 *
827 * @tparam T... Field value types; must all be fundamental. 3-16 required.
828 * @param loop_index Zero-based point index to overwrite.
829 * @param args Field values in schema order.
830 * @return @c false on out-of-range, schema mismatch, or invalid state.
831 */
832 template <typename... T>
833 bool set_value(size_t loop_index, T... args) noexcept;
834
835 /**
836 * @brief Overwrites point @p loop_index with float XYZ plus additional fields.
837 *
838 * @tparam T... Types for additional fields.
839 * @param loop_index Zero-based point index.
840 * @param x, y, z XYZ float coordinates.
841 * @param args Additional field values.
842 * @return @c false on failure.
843 */
844 template <typename... T>
845 bool set_value_v3f(size_t loop_index, float x, float y, float z, T... args) noexcept;
846
847 /**
848 * @brief Overwrites point @p loop_index from a @c Vector3f plus additional fields.
849 *
850 * @tparam T... Types for additional fields.
851 * @param loop_index Zero-based point index.
852 * @param v3f XYZ float coordinates.
853 * @param args Additional field values.
854 * @return @c false on failure.
855 */
856 template <typename... T>
857 bool set_value_v3f(size_t loop_index, Vector3f v3f, T... args) noexcept;
858
859 /**
860 * @brief Overwrites point @p loop_index with double XYZ plus additional fields.
861 *
862 * @tparam T... Types for additional fields.
863 * @param loop_index Zero-based point index.
864 * @param x, y, z XYZ double coordinates.
865 * @param args Additional field values.
866 * @return @c false on failure.
867 */
868 template <typename... T>
869 bool set_value_v3d(size_t loop_index, double x, double y, double z, T... args) noexcept;
870
871 /**
872 * @brief Overwrites point @p loop_index from a @c Vector3d plus additional fields.
873 *
874 * @tparam T... Types for additional fields.
875 * @param loop_index Zero-based point index.
876 * @param v3d XYZ double coordinates.
877 * @param args Additional field values.
878 * @return @c false on failure.
879 */
880 template <typename... T>
881 bool set_value_v3d(size_t loop_index, Vector3d v3d, T... args) noexcept;
882
883 /**
884 * @brief Resets the logical point count (and optionally releases all resources).
885 *
886 * @details
887 * When @p force is @c false (default), only @c size_ and the write cursor
888 * are zeroed; the buffer and protocol remain intact, allowing the buffer to
889 * be refilled without reallocation.
890 *
891 * When @p force is @c true, the owned buffer is freed and all protocol and
892 * header fields are zeroed -- equivalent to returning the object to its
893 * default-constructed state.
894 *
895 * @param force If @c true, free the buffer and reset all state.
896 */
897 void clear(bool force = false) noexcept;
898
899 Header header; ///< Sequencing and timestamp metadata for this point cloud.
900
901 static constexpr bool kZerocopyTypes{true}; /// Internal
902
903 private:
904 struct VLINK_EXPORT_AND_ALIGNED(8) Protocol final {
905 uint64_t size_num{0};
906 char names[160]{0};
907 uint64_t type_num{0};
908
909 Protocol() noexcept = default;
910
911 template <typename... T>
912 static uint64_t get_size_num() noexcept;
913
914 template <typename... T>
915 static uint64_t get_type_num() noexcept;
916
917 template <typename T>
918 static constexpr uint8_t get_type() noexcept;
919
920 constexpr uint64_t get_pack_size() const noexcept;
921
922 static bool check_valid(uint64_t _size_num, std::string_view _names) noexcept;
923
924 static std::string get_names(const std::vector<std::string>& keys) noexcept;
925
926 KeyList get_key_list() const noexcept;
927
928 std::string get_size_for_print() const noexcept;
929
930 std::string get_type_for_print() const noexcept;
931 };
932
933 Protocol protocol_;
934 uint8_t* data_{nullptr};
935 size_t capacity_{0};
936 size_t size_{0};
937 uint32_t reserved_buf_{0};
938 uint16_t pack_size_{0};
939 bool is_owner_{false};
940 uint64_t index_{0};
941
942 static constexpr uint32_t kMagicNumberBegin{0x98B7F16A};
943 static constexpr uint32_t kMagicNumberEnd{0x98B7F16F};
944};
945
946////////////////////////////////////////////////////////////////
947/// Details
948////////////////////////////////////////////////////////////////
949
950template <typename T>
951inline bool PointCloud::get_value(T& t, size_t loop_index, uint16_t offset) const noexcept {
952 static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
953
954 size_t p = (loop_index * pack_size_) + offset;
955
956 if VUNLIKELY (p + sizeof(T) > size_ * pack_size_) {
957 std::memset(&t, 0, sizeof(T));
958 return false;
959 }
960
961 std::memcpy(&t, data_ + p, sizeof(T));
962
963 return true;
964}
965
966template <typename T>
967inline T PointCloud::get_value(size_t loop_index, uint16_t offset) const noexcept {
968 static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
969
970 T t;
971
972 get_value(t, loop_index, offset);
973
974 return t;
975}
976
977template <typename T>
978inline bool PointCloud::get_value(T& t, size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept {
979 static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
980
981 auto iter = key_map.find(key.data());
982
983 if VUNLIKELY (iter == key_map.end()) {
984 std::memset(&t, 0, sizeof(T));
985 return false;
986 }
987
988 return get_value<T>(t, loop_index, iter->second);
989}
990
991template <typename T>
992inline T PointCloud::get_value(size_t loop_index, KeyMap& key_map, std::string_view key) const noexcept {
993 static_assert(std::is_fundamental_v<T>, "T must be fundamental.");
994
995 T t;
996
997 get_value(t, loop_index, key_map, key);
998
999 return t;
1000}
1001
1002template <typename... T>
1003inline bool PointCloud::create(size_t size, const std::vector<std::string>& keys) noexcept {
1004 static_assert((std::is_fundamental_v<T> && ...), "All types must be fundamental.");
1005
1006 static_assert(sizeof...(T) >= 3 && sizeof...(T) <= 16, "The number of keys ranges is [3 ~ 16].");
1007
1008 if VUNLIKELY (sizeof...(T) != keys.size()) {
1009 return false;
1010 }
1011
1012 uint64_t size_num = Protocol::get_size_num<T...>();
1013
1014 if VUNLIKELY (size_num == 0) {
1015 return false;
1016 }
1017
1018 std::string key_str = Protocol::get_names(keys);
1019
1020 if VUNLIKELY (key_str.empty()) {
1021 return false;
1022 }
1023
1024 uint64_t type_num = Protocol::get_type_num<T...>();
1025
1026 if VUNLIKELY (type_num == 0) {
1027 return false;
1028 }
1029
1030 if (is_owner_ && data_ && capacity_ != 0) {
1031 Bytes::bytes_free(data_, capacity_);
1032 }
1033
1034 protocol_.size_num = size_num;
1035 std::memset(protocol_.names, 0, sizeof(protocol_.names));
1036 std::memcpy(protocol_.names, key_str.c_str(), key_str.size());
1037 protocol_.type_num = type_num;
1038
1039 size_ = 0;
1040 pack_size_ = protocol_.get_pack_size();
1041 capacity_ = size * pack_size_;
1042
1043 if VLIKELY (capacity_ != 0) {
1044 data_ = Bytes::bytes_malloc(capacity_);
1045 is_owner_ = true;
1046 }
1047
1048 index_ = 0;
1049
1050 return true;
1051}
1052
1053template <typename... T>
1054inline bool PointCloud::create_v3f(size_t _size, const std::vector<std::string>& keys) noexcept {
1055 std::vector<std::string> target_keys{"x", "y", "z"};
1056
1057 target_keys.insert(target_keys.end(), keys.begin(), keys.end());
1058
1059 return create<float, float, float, T...>(_size, target_keys);
1060}
1061
1062template <typename... T>
1063inline bool PointCloud::create_v3d(size_t _size, const std::vector<std::string>& keys) noexcept {
1064 std::vector<std::string> target_keys{"x", "y", "z"};
1065
1066 target_keys.insert(target_keys.end(), keys.begin(), keys.end());
1067
1068 return create<double, double, double, T...>(_size, target_keys);
1069}
1070
1071inline bool PointCloud::fill_packed_data(const uint8_t* src_data, size_t _size) noexcept {
1072 bool is_fill_success = false;
1073
1074 if VUNLIKELY (_size == 0 || !src_data) {
1075 is_fill_success = false;
1076 } else if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1077 is_fill_success = false;
1078 } else if VUNLIKELY (_size * pack_size_ > capacity_) {
1079 is_fill_success = false;
1080 } else {
1081 std::memcpy(data_, src_data, _size * pack_size_);
1082 size_ = _size;
1083 index_ = _size * pack_size_;
1084 is_fill_success = true;
1085 }
1086
1087 return is_fill_success;
1088}
1089
1090template <typename... T>
1091inline bool PointCloud::push_value(T... args) noexcept {
1092 static_assert((std::is_fundamental_v<T> && ...), "All types must be fundamental.");
1093
1094 static_assert(sizeof...(T) >= 3 && sizeof...(T) <= 16, "The number of keys ranges is [3 ~ 16].");
1095
1096 if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1097 return false;
1098 }
1099
1100 if VUNLIKELY (size_ * pack_size_ >= capacity_) {
1101 return false;
1102 }
1103
1104 constexpr size_t kTargetPackSize = (sizeof(T) + ...);
1105
1106 if VUNLIKELY (kTargetPackSize != pack_size_) {
1107 return false;
1108 }
1109
1110 auto* target_ptr = data_ + index_;
1111
1112 (
1113 [&]() {
1114 std::memcpy(target_ptr, &args, sizeof(args));
1115 target_ptr += sizeof(args);
1116 }(),
1117 ...);
1118
1119 index_ += pack_size_;
1120 ++size_;
1121
1122 return true;
1123}
1124
1125template <typename... T>
1126inline bool PointCloud::push_value_v3f(float x, float y, float z, T... args) noexcept {
1127 return push_value(x, y, z, args...);
1128}
1129
1130template <typename... T>
1131inline bool PointCloud::push_value_v3f(Vector3f v3f, T... args) noexcept {
1132 return push_value(v3f.x, v3f.y, v3f.z, args...);
1133}
1134
1135template <typename... T>
1136inline bool PointCloud::push_value_v3d(double x, double y, double z, T... args) noexcept {
1137 return push_value(x, y, z, args...);
1138}
1139
1140template <typename... T>
1141inline bool PointCloud::push_value_v3d(Vector3d v3d, T... args) noexcept {
1142 return push_value(v3d.x, v3d.y, v3d.z, args...);
1143}
1144
1145inline bool PointCloud::resize(size_t size) noexcept {
1146 if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1147 return false;
1148 }
1149
1150 if VUNLIKELY (size * pack_size_ > capacity_) {
1151 return false;
1152 }
1153
1154 size_ = size;
1155 index_ = size_ * pack_size_;
1156
1157 return true;
1158}
1159
1160template <typename... T>
1161bool PointCloud::set_value(size_t loop_index, T... args) noexcept {
1162 static_assert((std::is_fundamental_v<T> && ...), "All types must be fundamental.");
1163
1164 static_assert(sizeof...(T) >= 3 && sizeof...(T) <= 16, "The number of keys ranges is [3 ~ 16].");
1165
1166 if VUNLIKELY (!is_owner_ || !data_ || pack_size_ == 0 || capacity_ == 0) {
1167 return false;
1168 }
1169
1170 if VUNLIKELY (loop_index >= size_ || size_ * pack_size_ > capacity_) {
1171 return false;
1172 }
1173
1174 if VUNLIKELY (index_ != size_ * pack_size_) {
1175 std::cerr << "[PointCloud::set_value] Invalid buffer state: "
1176 << "The current buffer size does not match the capacity. "
1177 << "Please call resize(size) before using set_value()." << std::endl;
1178 return false;
1179 }
1180
1181 constexpr size_t kTargetPackSize = (sizeof(T) + ...);
1182
1183 if VUNLIKELY (kTargetPackSize != pack_size_) {
1184 return false;
1185 }
1186
1187 auto* target_ptr = data_ + (loop_index * pack_size_);
1188
1189 (
1190 [&]() {
1191 std::memcpy(target_ptr, &args, sizeof(args));
1192 target_ptr += sizeof(args);
1193 }(),
1194 ...);
1195
1196 return true;
1197}
1198
1199template <typename... T>
1200inline bool PointCloud::set_value_v3f(size_t loop_index, float x, float y, float z, T... args) noexcept {
1201 return set_value(loop_index, x, y, z, args...);
1202}
1203
1204template <typename... T>
1205inline bool PointCloud::set_value_v3f(size_t loop_index, Vector3f v3f, T... args) noexcept {
1206 return set_value(loop_index, v3f.x, v3f.y, v3f.z, args...);
1207}
1208
1209template <typename... T>
1210inline bool PointCloud::set_value_v3d(size_t loop_index, double x, double y, double z, T... args) noexcept {
1211 return set_value(loop_index, x, y, z, args...);
1212}
1213
1214template <typename... T>
1215inline bool PointCloud::set_value_v3d(size_t loop_index, Vector3d v3d, T... args) noexcept {
1216 return set_value(loop_index, v3d.x, v3d.y, v3d.z, args...);
1217}
1218
1219template <typename... T>
1220inline uint64_t PointCloud::Protocol::get_size_num() noexcept {
1221 uint64_t target_num = 0;
1222
1223 uint64_t key_shift = sizeof...(T) * 4;
1224
1225 (
1226 [&](auto type) {
1227 key_shift -= 4;
1228 target_num |= (static_cast<uint64_t>(sizeof(type)) & 0xF) << key_shift;
1229 }(T{}),
1230 ...);
1231
1232 return target_num;
1233}
1234
1235template <typename... T>
1236inline uint64_t PointCloud::Protocol::get_type_num() noexcept {
1237 uint64_t target_num = 0;
1238
1239 uint64_t key_shift = sizeof...(T) * 4;
1240
1241 (
1242 [&](auto type) {
1243 key_shift -= 4;
1244 target_num |= (static_cast<uint64_t>(get_type<decltype(type)>()) & 0xF) << key_shift;
1245 }(T{}),
1246 ...);
1247
1248 return target_num;
1249}
1250
1251template <typename T>
1252inline constexpr uint8_t PointCloud::Protocol::get_type() noexcept {
1253 if constexpr (std::is_same_v<T, bool>) {
1254 return kBoolType;
1255 } else if constexpr (std::is_same_v<T, int8_t>) {
1256 return kInt8Type;
1257 } else if constexpr (std::is_same_v<T, uint8_t>) {
1258 return kUint8Type;
1259 } else if constexpr (std::is_same_v<T, int16_t>) {
1260 return kInt16Type;
1261 } else if constexpr (std::is_same_v<T, uint16_t>) {
1262 return kUint16Type;
1263 } else if constexpr (std::is_same_v<T, int32_t>) {
1264 return kInt32Type;
1265 } else if constexpr (std::is_same_v<T, uint32_t>) {
1266 return kUint32Type;
1267 } else if constexpr (std::is_same_v<T, int64_t>) {
1268 return kInt64Type;
1269 } else if constexpr (std::is_same_v<T, uint64_t>) {
1270 return kUint64Type;
1271 } else if constexpr (std::is_same_v<T, float>) {
1272 return kFloatType;
1273 } else if constexpr (std::is_same_v<T, double>) {
1274 return kDoubleType;
1275 } else {
1276 return kUnknownType;
1277 }
1278}
1279
1280inline constexpr uint64_t PointCloud::Protocol::get_pack_size() const noexcept {
1281 int sum = 0;
1282
1283 auto target_num = size_num;
1284
1285 while (target_num > 0) {
1286 sum += target_num & 0xF;
1287 target_num >>= 4;
1288 }
1289
1290 return sum;
1291}
1292
1293} // namespace zerocopy
1294
1295} // namespace vlink
Versatile byte buffer with small-buffer optimisation, ownership semantics and compression.
Common timestamp and sequencing metadata header for VLink zero-copy data types.
#define VUNLIKELY(...)
Shorthand alias for VLINK_UNLIKELY. Hints that the expression is unlikely true.
Definition macros.h:302
#define VLINK_EXPORT_AND_ALIGNED(align_num)
Marks a class or variable as both exported and aligned to the given byte boundary.
Definition macros.h:103
#define VLIKELY(...)
Shorthand alias for VLINK_LIKELY. Hints that the expression is likely true.
Definition macros.h:297
STL namespace.
Definition point_cloud.h:121
Schema-aware zero-copy 3-D point cloud with typed per-point fields.