blob: 0aff48e16090e1874916556d6e0e47e50b390176 [file] [log] [blame]
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001#ifndef AOS_FLATBUFFERS_STATIC_VECTOR_H_
2#define AOS_FLATBUFFERS_STATIC_VECTOR_H_
3#include <span>
4
5#include "flatbuffers/base.h"
6#include "glog/logging.h"
7
8#include "aos/containers/inlined_vector.h"
9#include "aos/containers/sized_array.h"
10#include "aos/flatbuffers/base.h"
11
12namespace aos::fbs {
13
14namespace internal {
15// Helper class for managing how we specialize the Vector object for different
16// contained types.
17// Users of the Vector class should never need to care about this.
18// Template arguments:
19// T: The type that the vector stores.
20// kInline: Whether the type in question is stored inline or not.
21// Enable: Used for SFINAE around struct values; can be ignored.
22// The struct provides the following types:
23// Type: The type of the data that will be stored inline in the vector.
24// ObjectType: The type of the actual data (only used for non-inline objects).
25// FlatbufferType: The type used by flatbuffers::Vector to store this type.
26// ConstFlatbufferType: The type used by a const flatbuffers::Vector to store
27// this type.
28// kDataAlign: Alignment required by the stored type.
29// kDataSize: Nominal size required by each non-inline data member. This is
30// what will be initially allocated; once created, individual members may
31// grow to accommodate dynamically lengthed vectors.
32template <typename T, bool kInline, class Enable = void>
33struct InlineWrapper;
34} // namespace internal
35
36// This Vector class provides a mutable, resizeable, flatbuffer vector.
37//
38// Upon creation, the Vector will start with enough space allocated for
39// kStaticLength elements, and must be provided with a memory buffer that
40// is large enough to serialize all the kStaticLength members (kStaticLength may
41// be zero).
42//
43// Once created, the Vector may be grown using calls to reserve().
44// This will result in the Vector attempting to allocate memory via its
45// parent object; such calls may fail if there is no space available in the
46// allocator.
47//
48// Note that if you are using the Vector class in a realtime context (and thus
49// must avoid dynamic memory allocations) you must only be using a Vector of
50// inline data (i.e., scalars, enums, or structs). Flatbuffer tables and strings
51// require overhead to manage and so require some form of dynamic memory
52// allocation. If we discover a strong use-case for such things, then we may
53// provide some interface that allows managing said metadata on the stack or
54// in another realtime-safe manner.
55//
56// Template arguments:
57// T: Type contained by the vector; either a scalar/struct/enum type or a
58// static flatbuffer type of some sort (a String or an implementation of
59// aos::fbs::Table).
60// kStaticLength: Number of elements to statically allocate memory for.
61// May be zero.
62// kInline: Whether the type T will be stored inline in the vector.
63// kForceAlign: Alignment to force for the start of the vector (e.g., for
64// byte arrays it may be desirable to have the entire array aligned).
65// kNullTerminate: Whether to reserve an extra byte past the end of
66// the inline data for null termination. Not included in kStaticLength,
67// so if e.g. you want to store the string "abc" then kStaticLength can
68// be 3 and kNullTerminate can be true and the vector data will take
69// up 4 bytes of memory.
70//
71// Vector buffer memory layout:
72// * Requirements:
73// * Minimum alignment of 4 bytes (for element count).
74// * The start of the vector data must be aligned to either
75// alignof(InlineType) or a user-specified number.
76// * The element count for the vector must immediately precede the vector
77// data (and so may itself not be aligned to alignof(InlineType)).
78// * For non-inlined types, the individual types must be aligned to
79// their own alignment.
80// * In order to accommodate this, the vector buffer as a whole must
81// generally be aligned to the greatest of the above alignments. There
82// are two reasonable ways one could do this:
83// * Require that the 4th byte of the buffer provided by aligned to
84// the maximum alignment of its contents.
85// * Require that the buffer itself by aligned, and provide padding
86// ourselves. The Vector would then have to expose its own offset
87// because it would not start at the start of the buffer.
88// The former requires that the wrapping code understand the internals
89// of how vectors work; the latter generates extra padding and adds
90// extra logic around handling non-zero offsets.
91// To maintain general simplicity, we will use the second condition and eat
92// the cost of the potential extra few bytes of padding.
93// * The layout of the buffer will thus be:
94// [padding; element_count; inline_data; padding; offset_data]
95// The first padding will be of size max(0, kAlign - 4).
96// The element_count is of size 4.
97// The inline_data is of size sizeof(InlineType) * kStaticLength.
98// The second padding is of size
99// (kAlign - ((sizeof(InlineType) * kStaticLength) % kAlign)).
100// The remaining data is only present if kInline is false.
101// The offset data is of size T::kSize * kStaticLength. T::kSize % T::kAlign
102// must be zero.
103// Note that no padding is required on the end because T::kAlign will always
104// end up being equal to the alignment (this can only be violated if
105// kForceAlign is used, but we do not allow that).
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800106// The Vector class leaves any padding uninitialized. Until and unless we
107// determine that it is a performance issue, it is the responsibility of the
108// parent of this object to zero-initialize the memory.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700109template <typename T, size_t kStaticLength, bool kInline,
110 size_t kForceAlign = 0, bool kNullTerminate = false>
111class Vector : public ResizeableObject {
James Kuszmaul22448052023-12-14 15:55:14 -0800112 template <typename VectorType, typename ValueType>
113 class generic_iterator {
114 public:
115 using iterator_category = std::random_access_iterator_tag;
116 using value_type = ValueType;
117 using difference_type = std::ptrdiff_t;
118 using pointer = value_type *;
119 using reference = value_type &;
120
121 explicit generic_iterator(VectorType *vector, size_t index)
122 : vector_(vector), index_(index) {}
123 generic_iterator(const generic_iterator &) = default;
124 generic_iterator() : vector_(nullptr), index_(0) {}
125 generic_iterator &operator=(const generic_iterator &) = default;
126
127 generic_iterator &operator++() {
128 ++index_;
129 return *this;
130 }
131 generic_iterator operator++(int) {
132 generic_iterator retval = *this;
133 ++(*this);
134 return retval;
135 }
136 generic_iterator &operator--() {
137 --index_;
138 return *this;
139 }
140 generic_iterator operator--(int) {
141 generic_iterator retval = *this;
142 --(*this);
143 return retval;
144 }
145 bool operator==(const generic_iterator &other) const {
146 CHECK_EQ(other.vector_, vector_);
147 return index_ == other.index_;
148 }
149 std::strong_ordering operator<=>(const generic_iterator &other) const {
150 CHECK_EQ(other.vector_, vector_);
151 return index_ <=> other.index_;
152 }
153 reference operator*() const { return vector_->at(index_); }
154 difference_type operator-(const generic_iterator &other) const {
155 CHECK_EQ(other.vector_, vector_);
156 return index_ - other.index_;
157 }
158 generic_iterator operator-(difference_type decrement) const {
159 return generic_iterator(vector_, index_ - decrement);
160 }
161 friend generic_iterator operator-(difference_type decrement,
162 const generic_iterator &rhs) {
163 return rhs - decrement;
164 }
165 generic_iterator operator+(difference_type increment) const {
166 return generic_iterator(vector_, index_ + increment);
167 }
168 friend generic_iterator operator+(difference_type increment,
169 const generic_iterator &rhs) {
170 return rhs + increment;
171 }
172 generic_iterator &operator+=(difference_type increment) {
173 index_ += increment;
174 return *this;
175 }
176 generic_iterator &operator-=(difference_type increment) {
177 index_ -= increment;
178 return *this;
179 }
180 reference operator[](difference_type index) const {
181 return *(*this + index);
182 }
183
184 private:
185 VectorType *vector_;
186 size_t index_;
187 };
188
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700189 public:
James Kuszmaul22448052023-12-14 15:55:14 -0800190 using iterator = generic_iterator<Vector, T>;
191 using const_iterator = generic_iterator<const Vector, const T>;
192
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700193 static_assert(kInline || !kNullTerminate,
194 "It does not make sense to null-terminate vectors of objects.");
195 // Type stored inline in the serialized vector (offsets for tables/strings; T
196 // otherwise).
197 using InlineType = typename internal::InlineWrapper<T, kInline>::Type;
198 // OUt-of-line type for out-of-line T.
199 using ObjectType = typename internal::InlineWrapper<T, kInline>::ObjectType;
200 // Type used as the template parameter to flatbuffers::Vector<>.
201 using FlatbufferType =
202 typename internal::InlineWrapper<T, kInline>::FlatbufferType;
203 using ConstFlatbufferType =
204 typename internal::InlineWrapper<T, kInline>::ConstFlatbufferType;
205 // flatbuffers::Vector type that corresponds to this Vector.
206 typedef flatbuffers::Vector<FlatbufferType> Flatbuffer;
207 typedef const flatbuffers::Vector<ConstFlatbufferType> ConstFlatbuffer;
208 // Alignment of the inline data.
209 static constexpr size_t kInlineAlign =
210 std::max(kForceAlign, alignof(InlineType));
211 // Type used for serializing the length of the vector.
212 typedef uint32_t LengthType;
213 // Overall alignment of this type, and required alignment of the buffer that
214 // must be provided to the Vector.
215 static constexpr size_t kAlign =
216 std::max({alignof(LengthType), kInlineAlign,
217 internal::InlineWrapper<T, kInline>::kDataAlign});
218 // Padding inserted prior to the length element of the vector (to manage
219 // alignment of the data properly; see class comment)
220 static constexpr size_t kPadding1 =
221 std::max<size_t>(0, kAlign - sizeof(LengthType));
222 // Size of the vector length field.
223 static constexpr size_t kLengthSize = sizeof(LengthType);
224 // Size of all the inline vector data, including null termination (prior to
225 // any dynamic increases in size).
226 static constexpr size_t kInlineSize =
227 sizeof(InlineType) * (kStaticLength + (kNullTerminate ? 1 : 0));
228 // Per-element size of any out-of-line data.
229 static constexpr size_t kDataElementSize =
230 internal::InlineWrapper<T, kInline>::kDataSize;
231 // Padding between the inline data and any out-of-line data, to manage
232 // mismatches in alignment between the two.
233 static constexpr size_t kPadding2 = kAlign - (kInlineSize % kAlign);
234 // Total statically allocated space for any out-of-line data ("offset data")
235 // (prior to any dynamic increases in size).
236 static constexpr size_t kOffsetOffsetDataSize =
237 kInline ? 0 : (kStaticLength * kDataElementSize);
238 // Total nominal size of the Vector.
239 static constexpr size_t kSize =
240 kPadding1 + kLengthSize + kInlineSize + kPadding2 + kOffsetOffsetDataSize;
241 // Offset from the start of the provided buffer to where the actual start of
242 // the vector is.
243 static constexpr size_t kOffset = kPadding1;
244 // Constructors; the provided buffer must be aligned to kAlign and be kSize in
245 // length. parent must be non-null.
246 Vector(std::span<uint8_t> buffer, ResizeableObject *parent)
247 : ResizeableObject(buffer, parent) {
248 CHECK_EQ(0u, reinterpret_cast<size_t>(buffer.data()) % kAlign);
249 CHECK_EQ(kSize, buffer.size());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700250 SetLength(0u);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700251 if (!kInline) {
252 // Initialize the offsets for any sub-tables. These are used to track
253 // where each table will get serialized in memory as memory gets
254 // resized/moved around.
255 for (size_t index = 0; index < kStaticLength; ++index) {
256 object_absolute_offsets_.emplace_back(kPadding1 + kLengthSize +
257 kInlineSize + kPadding2 +
258 index * kDataElementSize);
259 }
260 }
261 }
262 Vector(const Vector &) = delete;
263 Vector &operator=(const Vector &) = delete;
264 virtual ~Vector() {}
265 // Current allocated length of this vector.
266 // Does not include null termination.
267 size_t capacity() const { return allocated_length_; }
268 // Current length of the vector.
269 // Does not include null termination.
270 size_t size() const { return length_; }
271
272 // Appends an element to the Vector. Used when kInline is false. Returns
273 // nullptr if the append failed due to insufficient capacity. If you need to
274 // increase the capacity() of the vector, call reserve().
275 [[nodiscard]] T *emplace_back();
276 // Appends an element to the Vector. Used when kInline is true. Returns false
277 // if there is insufficient capacity for a new element.
278 [[nodiscard]] bool emplace_back(T element) {
279 static_assert(kInline);
280 return AddInlineElement(element);
281 }
282
283 // Adjusts the allocated size of the vector (does not affect the actual
284 // current length as returned by size()). Returns true on success, and false
285 // if the allocation failed for some reason.
286 // Note that reductions in size will not currently result in the allocated
287 // size actually changing.
288 [[nodiscard]] bool reserve(size_t new_length) {
289 if (new_length > allocated_length_) {
290 const size_t new_elements = new_length - allocated_length_;
291 // First, we must add space for our new inline elements.
292 if (!InsertBytes(
293 inline_data() + allocated_length_ + (kNullTerminate ? 1 : 0),
294 new_elements * sizeof(InlineType), SetZero::kYes)) {
295 return false;
296 }
297 if (!kInline) {
298 // For non-inline objects, create the space required for all the new
299 // object data.
300 const size_t insertion_point = buffer_.size();
301 if (!InsertBytes(buffer_.data() + insertion_point,
302 new_elements * kDataElementSize, SetZero::kYes)) {
303 return false;
304 }
305 for (size_t index = 0; index < new_elements; ++index) {
306 // Note that the already-allocated data may be arbitrarily-sized, so
307 // we cannot use the same static calculation that we do in the
308 // constructor.
309 object_absolute_offsets_.emplace_back(insertion_point +
310 index * kDataElementSize);
311 }
312 objects_.reserve(new_length);
313 }
314 allocated_length_ = new_length;
315 }
316 return true;
317 }
318
319 // Accessors for using the Vector as a flatbuffers::Vector.
320 // Note that these pointers will be unstable if any memory allocations occur
321 // that cause memory to get shifted around.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700322 ConstFlatbuffer *AsFlatbufferVector() const {
323 return reinterpret_cast<const Flatbuffer *>(vector_buffer().data());
324 }
325
326 // Copies the contents of the provided vector into this; returns false on
327 // failure (e.g., if the provided vector is too long for the amount of space
328 // we can allocate through reserve()).
James Kuszmaul710883b2023-12-14 14:34:48 -0800329 // This is a deep copy, and will call FromFlatbuffer on any constituent
330 // objects.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700331 [[nodiscard]] bool FromFlatbuffer(ConstFlatbuffer *vector);
332
333 // Returns the element at the provided index. index must be less than size().
334 const T &at(size_t index) const {
335 CHECK_LT(index, length_);
336 return unsafe_at(index);
337 }
338
339 // Same as at(), except that bounds checks are only performed in non-optimized
340 // builds.
341 // TODO(james): The GetInlineElement() call itself does some bounds-checking;
342 // consider down-grading that.
343 const T &unsafe_at(size_t index) const {
344 DCHECK_LT(index, length_);
345 if (kInline) {
346 // This reinterpret_cast is extremely wrong if T != InlineType (this is
347 // fine because we only do this if kInline is true).
348 // TODO(james): Get the templating improved so that we can get away with
349 // specializing at() instead of using if statements. Resolving this will
350 // also allow deduplicating the Resize() calls.
351 // This specialization is difficult because you cannot partially
352 // specialize a templated class method (online things seem to suggest e.g.
353 // using a struct as the template parameter rather than having separate
354 // parameters).
355 return reinterpret_cast<const T &>(GetInlineElement(index));
356 } else {
357 return objects_[index].t;
358 }
359 }
360
361 // Returns a mutable pointer to the element at the provided index. index must
362 // be less than size().
363 T &at(size_t index) {
364 CHECK_LT(index, length_);
365 return unsafe_at(index);
366 }
367
368 // Same as at(), except that bounds checks are only performed in non-optimized
369 // builds.
370 // TODO(james): The GetInlineElement() call itself does some bounds-checking;
371 // consider down-grading that.
372 T &unsafe_at(size_t index) {
373 DCHECK_LT(index, length_);
374 if (kInline) {
375 // This reinterpret_cast is extremely wrong if T != InlineType (this is
376 // fine because we only do this if kInline is true).
377 // TODO(james): Get the templating improved so that we can get away with
378 // specializing at() instead of using if statements. Resolving this will
379 // also allow deduplicating the Resize() calls.
380 // This specialization is difficult because you cannot partially
381 // specialize a templated class method (online things seem to suggest e.g.
382 // using a struct as the template parameter rather than having separate
383 // parameters).
384 return reinterpret_cast<T &>(GetInlineElement(index));
385 } else {
386 return objects_[index].t;
387 }
388 }
389
390 const T &operator[](size_t index) const { return at(index); }
391 T &operator[](size_t index) { return at(index); }
392
393 // Resizes the vector to the requested size.
394 // size must be less than or equal to the current capacity() of the vector.
395 // Does not allocate additional memory (call reserve() to allocate additional
396 // memory).
397 // Zero-initializes all inline element; initializes all subtable/string
398 // elements to extant but empty objects.
399 void resize(size_t size);
400
401 // Resizes an inline vector to the requested size.
402 // When changing the size of the vector, the removed/inserted elements will be
403 // set to zero if requested. Otherwise, they will be left uninitialized.
404 void resize_inline(size_t size, SetZero set_zero) {
405 CHECK_LE(size, allocated_length_);
406 static_assert(
407 kInline,
408 "Vector::resize_inline() only works for inline vector types (scalars, "
409 "enums, structs).");
410 if (size == length_) {
411 return;
412 }
413 if (set_zero == SetZero::kYes) {
414 memset(
415 reinterpret_cast<void *>(inline_data() + std::min(size, length_)), 0,
416 std::abs(static_cast<ssize_t>(length_) - static_cast<ssize_t>(size)) *
417 sizeof(InlineType));
418 }
419 length_ = size;
420 SetLength(length_);
421 }
422 // Resizes a vector of offsets to the requested size.
423 // If the size is increased, the new elements will be initialized
424 // to empty but extant objects for non-inlined types (so, zero-length
425 // vectors/strings; objects that exist but have no fields populated).
426 // Note that this is always equivalent to resize().
427 void resize_not_inline(size_t size) {
428 CHECK_LE(size, allocated_length_);
429 static_assert(!kInline,
430 "Vector::resize_not_inline() only works for offset vector "
431 "types (objects, strings).");
432 if (size == length_) {
433 return;
434 } else if (length_ > size) {
435 // TODO: Remove any excess allocated memory.
436 length_ = size;
437 SetLength(length_);
438 return;
439 } else {
440 while (length_ < size) {
441 CHECK_NOTNULL(emplace_back());
442 }
443 }
444 }
445
446 // Accessors directly to the inline data of a vector.
447 const T *data() const {
448 static_assert(kInline,
449 "If you have a use-case for directly accessing the "
450 "flatbuffer data pointer for vectors of "
451 "objects/strings, please start a discussion.");
452 return inline_data();
453 }
454
455 T *data() {
456 static_assert(kInline,
457 "If you have a use-case for directly accessing the "
458 "flatbuffer data pointer for vectors of "
459 "objects/strings, please start a discussion.");
460 return inline_data();
461 }
462
James Kuszmaul22448052023-12-14 15:55:14 -0800463 // Iterators to allow easy use with standard C++ features.
464 iterator begin() { return iterator(this, 0); }
465 iterator end() { return iterator(this, size()); }
466 const_iterator begin() const { return const_iterator(this, 0); }
467 const_iterator end() const { return const_iterator(this, size()); }
468
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700469 std::string SerializationDebugString() const {
470 std::stringstream str;
471 str << "Raw Size: " << kSize << " alignment: " << kAlign
472 << " allocated length: " << allocated_length_ << " inline alignment "
473 << kInlineAlign << " kPadding1 " << kPadding1 << "\n";
474 str << "Observed length " << GetLength() << " (expected " << length_
475 << ")\n";
476 str << "Inline Size " << kInlineSize << " Inline bytes/value:\n";
477 // TODO(james): Get pretty-printing for structs so we can provide better
478 // debug.
479 internal::DebugBytes(
480 internal::GetSubSpan(vector_buffer(), kLengthSize,
481 sizeof(InlineType) * allocated_length_),
482 str);
483 str << "kPadding2 " << kPadding2 << " offset data size "
484 << kOffsetOffsetDataSize << "\n";
485 return str.str();
486 }
487
488 protected:
489 friend struct internal::TableMover<
490 Vector<T, kStaticLength, kInline, kForceAlign, kNullTerminate>>;
491 // protected so that the String class can access the move constructor.
492 Vector(Vector &&) = default;
493
494 private:
495 // See kAlign and kOffset.
496 size_t Alignment() const final { return kAlign; }
497 size_t AbsoluteOffsetOffset() const override { return kOffset; }
498 // Returns a buffer that starts at the start of the vector itself (past any
499 // padding).
500 std::span<uint8_t> vector_buffer() {
501 return internal::GetSubSpan(buffer(), kPadding1);
502 }
503 std::span<const uint8_t> vector_buffer() const {
504 return internal::GetSubSpan(buffer(), kPadding1);
505 }
506
507 bool AddInlineElement(InlineType e) {
508 if (length_ == allocated_length_) {
509 return false;
510 }
511 SetInlineElement(length_, e);
512 ++length_;
513 SetLength(length_);
514 return true;
515 }
516
517 void SetInlineElement(size_t index, InlineType value) {
518 CHECK_LT(index, allocated_length_);
519 inline_data()[index] = value;
520 }
521
522 InlineType &GetInlineElement(size_t index) {
523 CHECK_LT(index, allocated_length_);
524 return inline_data()[index];
525 }
526
527 const InlineType &GetInlineElement(size_t index) const {
528 CHECK_LT(index, allocated_length_);
529 return inline_data()[index];
530 }
531
532 // Returns a pointer to the start of the inline data itself.
533 InlineType *inline_data() {
534 return reinterpret_cast<InlineType *>(vector_buffer().data() + kLengthSize);
535 }
536 const InlineType *inline_data() const {
537 return reinterpret_cast<const InlineType *>(vector_buffer().data() +
538 kLengthSize);
539 }
540
541 // Updates the length of the vector to match the provided length. Does not set
542 // the length_ member.
543 void SetLength(LengthType length) {
544 *reinterpret_cast<LengthType *>(vector_buffer().data()) = length;
545 if (kNullTerminate) {
546 memset(reinterpret_cast<void *>(inline_data() + length), 0,
547 sizeof(InlineType));
548 }
549 }
550 LengthType GetLength() const {
551 return *reinterpret_cast<const LengthType *>(vector_buffer().data());
552 }
553
554 // Overrides to allow ResizeableObject to manage memory adjustments.
555 size_t NumberOfSubObjects() const final {
556 return kInline ? 0 : allocated_length_;
557 }
558 using ResizeableObject::SubObject;
559 SubObject GetSubObject(size_t index) final {
560 return SubObject{
561 reinterpret_cast<uoffset_t *>(&GetInlineElement(index)),
562 // In order to let this compile regardless of whether type T is an
563 // object type or not, we just use a reinterpret_cast.
564 (index < length_)
565 ? reinterpret_cast<ResizeableObject *>(&objects_[index].t)
566 : nullptr,
567 &object_absolute_offsets_[index]};
568 }
569 // Implementation that handles copying from a flatbuffers::Vector of an inline
570 // data type.
571 [[nodiscard]] bool FromInlineFlatbuffer(ConstFlatbuffer *vector) {
572 if (!reserve(CHECK_NOTNULL(vector)->size())) {
573 return false;
574 }
575
576 // We will be overwriting the whole vector very shortly; there is no need to
577 // clear the buffer to zero.
578 resize_inline(vector->size(), SetZero::kNo);
579
580 memcpy(inline_data(), vector->Data(), size() * sizeof(InlineType));
581 return true;
582 }
583
584 // Implementation that handles copying from a flatbuffers::Vector of a
585 // not-inline data type.
586 [[nodiscard]] bool FromNotInlineFlatbuffer(const Flatbuffer *vector) {
587 if (!reserve(vector->size())) {
588 return false;
589 }
590 // "Clear" the vector.
591 resize_not_inline(0);
592
593 for (const typename T::Flatbuffer *entry : *vector) {
594 if (!CHECK_NOTNULL(emplace_back())->FromFlatbuffer(entry)) {
595 return false;
596 }
597 }
598 return true;
599 }
600
601 // In order to allow for easy partial template specialization, we use a
602 // non-member class to call FromInline/FromNotInlineFlatbuffer and
603 // resize_inline/resize_not_inline. There are not actually any great ways to
604 // do this with just our own class member functions, so instead we make these
605 // methods members of a friend of the Vector class; we then partially
606 // specialize the entire InlineWrapper class and use it to isolate anything
607 // that needs to have a common user interface while still having separate
608 // actual logic.
609 template <typename T_, bool kInline_, class Enable_>
610 friend struct internal::InlineWrapper;
611
612 // Note: The objects here really want to be owned by this object (as opposed
613 // to e.g. returning a stack-allocated object from the emplace_back() methods
614 // that the user then owns). There are two main challenges with have the user
615 // own the object on question:
616 // 1. We can't have >1 reference floating around, or else one object's state
617 // can become out of date. This forces us to do ref-counting and could
618 // make certain types of code obnoxious to write.
619 // 2. Once the user-created object goes out of scope, we lose all of its
620 // internal state. In _theory_ it should be possible to reconstruct most
621 // of the relevant state by examining the contents of the buffer, but
622 // doing so would be cumbersome.
623 aos::InlinedVector<internal::TableMover<ObjectType>,
624 kInline ? 0 : kStaticLength>
625 objects_;
626 aos::InlinedVector<size_t, kInline ? 0 : kStaticLength>
627 object_absolute_offsets_;
628 // Current actual length of the vector.
629 size_t length_ = 0;
630 // Current length that we have allocated space available for.
631 size_t allocated_length_ = kStaticLength;
632};
633
634template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
635 bool kNullTerminate>
636T *Vector<T, kStaticLength, kInline, kForceAlign,
637 kNullTerminate>::emplace_back() {
638 static_assert(!kInline);
639 if (length_ >= allocated_length_) {
640 return nullptr;
641 }
642 const size_t object_start = object_absolute_offsets_[length_];
643 std::span<uint8_t> object_buffer =
644 internal::GetSubSpan(buffer(), object_start, T::kSize);
645 objects_.emplace_back(object_buffer, this);
646 const uoffset_t offset =
647 object_start - (reinterpret_cast<size_t>(&GetInlineElement(length_)) -
648 reinterpret_cast<size_t>(buffer().data()));
649 CHECK(AddInlineElement(offset));
650 return &objects_[objects_.size() - 1].t;
651}
652
653// The String class is a special version of the Vector that is always
654// null-terminated, always contains 1-byte character elements, and which has a
655// few extra methods for convenient string access.
656template <size_t kStaticLength>
657class String : public Vector<char, kStaticLength, true, 0, true> {
658 public:
659 typedef Vector<char, kStaticLength, true, 0, true> VectorType;
660 typedef flatbuffers::String Flatbuffer;
661 String(std::span<uint8_t> buffer, ResizeableObject *parent)
662 : VectorType(buffer, parent) {}
663 virtual ~String() {}
664 void SetString(std::string_view string) {
665 CHECK_LT(string.size(), VectorType::capacity());
666 VectorType::resize_inline(string.size(), SetZero::kNo);
667 memcpy(VectorType::data(), string.data(), string.size());
668 }
669 std::string_view string_view() const {
670 return std::string_view(VectorType::data(), VectorType::size());
671 }
672 std::string str() const {
673 return std::string(VectorType::data(), VectorType::size());
674 }
675 const char *c_str() const { return VectorType::data(); }
676
677 private:
678 friend struct internal::TableMover<String<kStaticLength>>;
679 String(String &&) = default;
680};
681
682namespace internal {
683// Specialization for all non-inline vector types. All of these types will just
684// use offsets for their inline data and have appropriate member types/constants
685// for the remaining fields.
686template <typename T>
687struct InlineWrapper<T, false, void> {
688 typedef uoffset_t Type;
689 typedef T ObjectType;
690 typedef flatbuffers::Offset<typename T::Flatbuffer> FlatbufferType;
691 typedef flatbuffers::Offset<typename T::Flatbuffer> ConstFlatbufferType;
692 static_assert((T::kSize % T::kAlign) == 0);
693 static constexpr size_t kDataAlign = T::kAlign;
694 static constexpr size_t kDataSize = T::kSize;
695 template <typename StaticVector>
696 static bool FromFlatbuffer(
697 StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
698 return to->FromNotInlineFlatbuffer(from);
699 }
700 template <typename StaticVector>
701 static void ResizeVector(StaticVector *target, size_t size) {
702 target->resize_not_inline(size);
703 }
704};
705// Specialization for "normal" scalar inline data (ints, floats, doubles,
706// enums).
707template <typename T>
708struct InlineWrapper<T, true,
709 typename std::enable_if_t<!std::is_class<T>::value>> {
710 typedef T Type;
711 typedef T ObjectType;
712 typedef T FlatbufferType;
713 typedef T ConstFlatbufferType;
714 static constexpr size_t kDataAlign = alignof(T);
715 static constexpr size_t kDataSize = sizeof(T);
716 template <typename StaticVector>
717 static bool FromFlatbuffer(
718 StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
719 return to->FromInlineFlatbuffer(from);
720 }
721 template <typename StaticVector>
722 static void ResizeVector(StaticVector *target, size_t size) {
723 target->resize_inline(size, SetZero::kYes);
724 }
725};
726// Specialization for booleans, given that flatbuffers uses uint8_t's for bools.
727template <>
728struct InlineWrapper<bool, true, void> {
729 typedef uint8_t Type;
730 typedef uint8_t ObjectType;
731 typedef uint8_t FlatbufferType;
732 typedef uint8_t ConstFlatbufferType;
733 static constexpr size_t kDataAlign = 1u;
734 static constexpr size_t kDataSize = 1u;
735 template <typename StaticVector>
736 static bool FromFlatbuffer(
737 StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
738 return to->FromInlineFlatbuffer(from);
739 }
740 template <typename StaticVector>
741 static void ResizeVector(StaticVector *target, size_t size) {
742 target->resize_inline(size, SetZero::kYes);
743 }
744};
745// Specialization for flatbuffer structs.
746// The flatbuffers codegen uses struct pointers rather than references or the
747// such, so it needs to be treated special.
748template <typename T>
749struct InlineWrapper<T, true,
750 typename std::enable_if_t<std::is_class<T>::value>> {
751 typedef T Type;
752 typedef T ObjectType;
753 typedef T *FlatbufferType;
754 typedef const T *ConstFlatbufferType;
755 static constexpr size_t kDataAlign = alignof(T);
756 static constexpr size_t kDataSize = sizeof(T);
757 template <typename StaticVector>
758 static bool FromFlatbuffer(
759 StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
760 return to->FromInlineFlatbuffer(from);
761 }
762 template <typename StaticVector>
763 static void ResizeVector(StaticVector *target, size_t size) {
764 target->resize_inline(size, SetZero::kYes);
765 }
766};
767} // namespace internal
768 //
769template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
770 bool kNullTerminate>
771bool Vector<T, kStaticLength, kInline, kForceAlign,
772 kNullTerminate>::FromFlatbuffer(ConstFlatbuffer *vector) {
773 return internal::InlineWrapper<T, kInline>::FromFlatbuffer(this, vector);
774}
775
776template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
777 bool kNullTerminate>
778void Vector<T, kStaticLength, kInline, kForceAlign, kNullTerminate>::resize(
779 size_t size) {
780 internal::InlineWrapper<T, kInline>::ResizeVector(this, size);
781}
782
783} // namespace aos::fbs
784#endif // AOS_FLATBUFFERS_STATIC_VECTOR_H_