blob: c8358643f2b3846f2a408d82f10f08a808fa7ec4 [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
Austin Schuh99f7c6a2024-06-25 22:07:44 -07005#include "absl/log/check.h"
6#include "absl/log/log.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -07007#include "flatbuffers/base.h"
James Kuszmaule65fb402024-01-13 14:10:51 -08008#include "flatbuffers/vector.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -07009
10#include "aos/containers/inlined_vector.h"
11#include "aos/containers/sized_array.h"
12#include "aos/flatbuffers/base.h"
13
14namespace aos::fbs {
15
16namespace internal {
17// Helper class for managing how we specialize the Vector object for different
18// contained types.
19// Users of the Vector class should never need to care about this.
20// Template arguments:
21// T: The type that the vector stores.
22// kInline: Whether the type in question is stored inline or not.
23// Enable: Used for SFINAE around struct values; can be ignored.
24// The struct provides the following types:
25// Type: The type of the data that will be stored inline in the vector.
26// ObjectType: The type of the actual data (only used for non-inline objects).
27// FlatbufferType: The type used by flatbuffers::Vector to store this type.
28// ConstFlatbufferType: The type used by a const flatbuffers::Vector to store
29// this type.
Austin Schuhf8440852024-05-31 10:46:50 -070030// kDataElementAlign: Alignment required by the stored type.
31// kDataElementSize: Nominal size required by each non-inline data member.
32// This is what will be initially allocated; once created, individual
33// members may grow to accommodate dynamically lengthed vectors.
34// kDataElementAlignOffset: Alignment offset required by the stored type.
James Kuszmaulf5eb4682023-09-22 17:16:59 -070035template <typename T, bool kInline, class Enable = void>
36struct InlineWrapper;
37} // namespace internal
38
39// This Vector class provides a mutable, resizeable, flatbuffer vector.
40//
41// Upon creation, the Vector will start with enough space allocated for
42// kStaticLength elements, and must be provided with a memory buffer that
43// is large enough to serialize all the kStaticLength members (kStaticLength may
44// be zero).
45//
46// Once created, the Vector may be grown using calls to reserve().
47// This will result in the Vector attempting to allocate memory via its
48// parent object; such calls may fail if there is no space available in the
49// allocator.
50//
51// Note that if you are using the Vector class in a realtime context (and thus
52// must avoid dynamic memory allocations) you must only be using a Vector of
53// inline data (i.e., scalars, enums, or structs). Flatbuffer tables and strings
54// require overhead to manage and so require some form of dynamic memory
55// allocation. If we discover a strong use-case for such things, then we may
56// provide some interface that allows managing said metadata on the stack or
57// in another realtime-safe manner.
58//
59// Template arguments:
60// T: Type contained by the vector; either a scalar/struct/enum type or a
61// static flatbuffer type of some sort (a String or an implementation of
62// aos::fbs::Table).
63// kStaticLength: Number of elements to statically allocate memory for.
64// May be zero.
65// kInline: Whether the type T will be stored inline in the vector.
66// kForceAlign: Alignment to force for the start of the vector (e.g., for
67// byte arrays it may be desirable to have the entire array aligned).
68// kNullTerminate: Whether to reserve an extra byte past the end of
69// the inline data for null termination. Not included in kStaticLength,
70// so if e.g. you want to store the string "abc" then kStaticLength can
71// be 3 and kNullTerminate can be true and the vector data will take
72// up 4 bytes of memory.
73//
74// Vector buffer memory layout:
75// * Requirements:
76// * Minimum alignment of 4 bytes (for element count).
77// * The start of the vector data must be aligned to either
78// alignof(InlineType) or a user-specified number.
79// * The element count for the vector must immediately precede the vector
80// data (and so may itself not be aligned to alignof(InlineType)).
81// * For non-inlined types, the individual types must be aligned to
82// their own alignment.
83// * In order to accommodate this, the vector buffer as a whole must
84// generally be aligned to the greatest of the above alignments. There
85// are two reasonable ways one could do this:
86// * Require that the 4th byte of the buffer provided by aligned to
87// the maximum alignment of its contents.
88// * Require that the buffer itself by aligned, and provide padding
89// ourselves. The Vector would then have to expose its own offset
90// because it would not start at the start of the buffer.
91// The former requires that the wrapping code understand the internals
92// of how vectors work; the latter generates extra padding and adds
93// extra logic around handling non-zero offsets.
94// To maintain general simplicity, we will use the second condition and eat
95// the cost of the potential extra few bytes of padding.
96// * The layout of the buffer will thus be:
Austin Schuhf8440852024-05-31 10:46:50 -070097// [element_count; inline_data; padding; offset_data]
James Kuszmaulf5eb4682023-09-22 17:16:59 -070098// The element_count is of size 4.
99// The inline_data is of size sizeof(InlineType) * kStaticLength.
Austin Schuhf8440852024-05-31 10:46:50 -0700100// The padding is sized such that the sum of the size of inline_data and the
101// padding adds up to the alignment if we have offset_data.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700102// The remaining data is only present if kInline is false.
Austin Schuhf8440852024-05-31 10:46:50 -0700103// The offset data is of size T::kSize * kStaticLength. T::kSize is rounded
104// up to a multiple of T::kAlign.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700105// Note that no padding is required on the end because T::kAlign will always
106// end up being equal to the alignment (this can only be violated if
107// kForceAlign is used, but we do not allow that).
James Kuszmaul1c9693f2023-12-08 09:45:26 -0800108// The Vector class leaves any padding uninitialized. Until and unless we
109// determine that it is a performance issue, it is the responsibility of the
110// parent of this object to zero-initialize the memory.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700111template <typename T, size_t kStaticLength, bool kInline,
112 size_t kForceAlign = 0, bool kNullTerminate = false>
113class Vector : public ResizeableObject {
James Kuszmaul22448052023-12-14 15:55:14 -0800114 template <typename VectorType, typename ValueType>
115 class generic_iterator {
116 public:
117 using iterator_category = std::random_access_iterator_tag;
118 using value_type = ValueType;
119 using difference_type = std::ptrdiff_t;
120 using pointer = value_type *;
121 using reference = value_type &;
122
123 explicit generic_iterator(VectorType *vector, size_t index)
124 : vector_(vector), index_(index) {}
125 generic_iterator(const generic_iterator &) = default;
126 generic_iterator() : vector_(nullptr), index_(0) {}
127 generic_iterator &operator=(const generic_iterator &) = default;
128
129 generic_iterator &operator++() {
130 ++index_;
131 return *this;
132 }
133 generic_iterator operator++(int) {
134 generic_iterator retval = *this;
135 ++(*this);
136 return retval;
137 }
138 generic_iterator &operator--() {
139 --index_;
140 return *this;
141 }
142 generic_iterator operator--(int) {
143 generic_iterator retval = *this;
144 --(*this);
145 return retval;
146 }
147 bool operator==(const generic_iterator &other) const {
148 CHECK_EQ(other.vector_, vector_);
149 return index_ == other.index_;
150 }
151 std::strong_ordering operator<=>(const generic_iterator &other) const {
152 CHECK_EQ(other.vector_, vector_);
153 return index_ <=> other.index_;
154 }
155 reference operator*() const { return vector_->at(index_); }
156 difference_type operator-(const generic_iterator &other) const {
157 CHECK_EQ(other.vector_, vector_);
158 return index_ - other.index_;
159 }
160 generic_iterator operator-(difference_type decrement) const {
161 return generic_iterator(vector_, index_ - decrement);
162 }
163 friend generic_iterator operator-(difference_type decrement,
164 const generic_iterator &rhs) {
165 return rhs - decrement;
166 }
167 generic_iterator operator+(difference_type increment) const {
168 return generic_iterator(vector_, index_ + increment);
169 }
170 friend generic_iterator operator+(difference_type increment,
171 const generic_iterator &rhs) {
172 return rhs + increment;
173 }
174 generic_iterator &operator+=(difference_type increment) {
175 index_ += increment;
176 return *this;
177 }
178 generic_iterator &operator-=(difference_type increment) {
179 index_ -= increment;
180 return *this;
181 }
182 reference operator[](difference_type index) const {
183 return *(*this + index);
184 }
185
186 private:
187 VectorType *vector_;
188 size_t index_;
189 };
190
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700191 public:
James Kuszmaul22448052023-12-14 15:55:14 -0800192 using iterator = generic_iterator<Vector, T>;
193 using const_iterator = generic_iterator<const Vector, const T>;
194
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700195 static_assert(kInline || !kNullTerminate,
196 "It does not make sense to null-terminate vectors of objects.");
197 // Type stored inline in the serialized vector (offsets for tables/strings; T
198 // otherwise).
199 using InlineType = typename internal::InlineWrapper<T, kInline>::Type;
Austin Schuhf8440852024-05-31 10:46:50 -0700200 // Out-of-line type for out-of-line T.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700201 using ObjectType = typename internal::InlineWrapper<T, kInline>::ObjectType;
202 // Type used as the template parameter to flatbuffers::Vector<>.
203 using FlatbufferType =
204 typename internal::InlineWrapper<T, kInline>::FlatbufferType;
205 using ConstFlatbufferType =
206 typename internal::InlineWrapper<T, kInline>::ConstFlatbufferType;
James Kuszmaul6be41022023-12-20 11:55:28 -0800207 // FlatbufferObjectType corresponds to the type used by the flatbuffer
208 // "object" API (i.e. the FlatbufferT types).
209 // This type will be something unintelligble for inline types.
210 using FlatbufferObjectType =
211 typename internal::InlineWrapper<T, kInline>::FlatbufferObjectType;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700212 // flatbuffers::Vector type that corresponds to this Vector.
213 typedef flatbuffers::Vector<FlatbufferType> Flatbuffer;
214 typedef const flatbuffers::Vector<ConstFlatbufferType> ConstFlatbuffer;
215 // Alignment of the inline data.
216 static constexpr size_t kInlineAlign =
217 std::max(kForceAlign, alignof(InlineType));
218 // Type used for serializing the length of the vector.
219 typedef uint32_t LengthType;
Austin Schuhf8440852024-05-31 10:46:50 -0700220 static constexpr size_t kDataElementAlign =
221 internal::InlineWrapper<T, kInline>::kDataElementAlign;
222 static constexpr size_t kDataElementAlignOffset =
223 internal::InlineWrapper<T, kInline>::kDataElementAlignOffset;
224 // Per-element size of any out-of-line data.
225 static constexpr size_t kDataElementSize =
226 internal::InlineWrapper<T, kInline>::kDataElementSize;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700227 // Overall alignment of this type, and required alignment of the buffer that
228 // must be provided to the Vector.
229 static constexpr size_t kAlign =
Austin Schuhf8440852024-05-31 10:46:50 -0700230 std::max({alignof(LengthType), kInlineAlign, kDataElementAlign});
231 // Offset into the buffer of where things must be aligned to the specified
232 // alignment.
233 static constexpr size_t kAlignOffset = sizeof(LengthType);
234
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700235 // Size of the vector length field.
236 static constexpr size_t kLengthSize = sizeof(LengthType);
237 // Size of all the inline vector data, including null termination (prior to
238 // any dynamic increases in size).
239 static constexpr size_t kInlineSize =
240 sizeof(InlineType) * (kStaticLength + (kNullTerminate ? 1 : 0));
Austin Schuhf8440852024-05-31 10:46:50 -0700241
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700242 // Padding between the inline data and any out-of-line data, to manage
243 // mismatches in alignment between the two.
Austin Schuhf8440852024-05-31 10:46:50 -0700244 //
245 // For inline vectors, we don't want to add any extra padding. The allocator
246 // will add extra padding if needed and communicate it to our constructor.
247 //
248 // For non-inline vectors, we need to pad out the offsets so that their end
249 // ends up kDataElementAlignOffset before the aligned start of the elements.
250 //
251 // This pads kInlineSize out to
252 static constexpr size_t kPadding1 =
253 kInline
254 ? 0
255 : ((kAlign - ((kInlineSize + kAlign /* Add kAlign to guarentee we
256 don't mod a negative number */
257 - kDataElementAlignOffset) %
258 kAlign)) %
259 kAlign);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700260 // Total statically allocated space for any out-of-line data ("offset data")
261 // (prior to any dynamic increases in size).
262 static constexpr size_t kOffsetOffsetDataSize =
263 kInline ? 0 : (kStaticLength * kDataElementSize);
264 // Total nominal size of the Vector.
265 static constexpr size_t kSize =
Austin Schuhf8440852024-05-31 10:46:50 -0700266 kLengthSize + kInlineSize + kPadding1 + kOffsetOffsetDataSize;
267 // If this is 0, then the parent object will not plan to statically
268 // reserve any memory for this object and will only reserve memory when the
269 // user requests creation of this object. This makes it so that zero-length
270 // vectors (which would require dynamic allocation *anyways* to actually be
271 // helpful) do not use up memory when unpopulated.
272 static constexpr size_t kPreallocatedSize = (kStaticLength > 0) ? kSize : 0;
273
274 // Returns the buffer size (in bytes) needed to hold the largest number of
275 // elements that can fit fully in the provided length (in bytes). This lets
276 // us compute how much of the padding we can fill with elements.
277 static constexpr size_t RoundedLength(size_t length) {
278 constexpr size_t overall_element_size =
279 sizeof(InlineType) + (kInline ? 0 : kDataElementSize);
280 return ((length - kLengthSize) / overall_element_size) *
281 overall_element_size +
282 kLengthSize;
283 }
284
285 // Constructors; the provided buffer must be aligned to kAlign and be kSize
286 // in length. parent must be non-null.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700287 Vector(std::span<uint8_t> buffer, ResizeableObject *parent)
288 : ResizeableObject(buffer, parent) {
Austin Schuhf8440852024-05-31 10:46:50 -0700289 CHECK_EQ(0u,
290 reinterpret_cast<size_t>(buffer.data() + kAlignOffset) % kAlign);
291 CHECK_LE(kSize, buffer.size());
292 if constexpr (kInline) {
293 // If everything is inline, it costs us nothing to consume the padding and
294 // use it for holding elements. For something like a short string in 8
295 // byte aligned space, this saves a second 8 byte allocation for the data.
296 allocated_length_ = (buffer.size() - kLengthSize) / sizeof(InlineType) -
297 (kNullTerminate ? 1 : 0);
298 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700299 SetLength(0u);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700300 if (!kInline) {
301 // Initialize the offsets for any sub-tables. These are used to track
302 // where each table will get serialized in memory as memory gets
303 // resized/moved around.
Austin Schuhf8440852024-05-31 10:46:50 -0700304 //
305 // We don't want to expand allocated_length_ here because that would then
306 // imply we have more memory for elements too, which we don't.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700307 for (size_t index = 0; index < kStaticLength; ++index) {
Austin Schuhf8440852024-05-31 10:46:50 -0700308 object_absolute_offsets_.emplace_back(
309 kLengthSize + kInlineSize + kPadding1 + index * kDataElementSize);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700310 }
311 }
312 }
313 Vector(const Vector &) = delete;
314 Vector &operator=(const Vector &) = delete;
315 virtual ~Vector() {}
316 // Current allocated length of this vector.
317 // Does not include null termination.
318 size_t capacity() const { return allocated_length_; }
319 // Current length of the vector.
320 // Does not include null termination.
321 size_t size() const { return length_; }
322
323 // Appends an element to the Vector. Used when kInline is false. Returns
324 // nullptr if the append failed due to insufficient capacity. If you need to
325 // increase the capacity() of the vector, call reserve().
326 [[nodiscard]] T *emplace_back();
327 // Appends an element to the Vector. Used when kInline is true. Returns false
328 // if there is insufficient capacity for a new element.
329 [[nodiscard]] bool emplace_back(T element) {
330 static_assert(kInline);
331 return AddInlineElement(element);
332 }
333
334 // Adjusts the allocated size of the vector (does not affect the actual
335 // current length as returned by size()). Returns true on success, and false
336 // if the allocation failed for some reason.
337 // Note that reductions in size will not currently result in the allocated
338 // size actually changing.
James Kuszmaul22efcb82024-03-22 16:20:56 -0700339 // For vectors of non-inline types (e.g., vectors of strings or vectors of
340 // tables), reserve() will allocate memory in an internal vector that we use
341 // for storing some metadata.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700342 [[nodiscard]] bool reserve(size_t new_length) {
343 if (new_length > allocated_length_) {
344 const size_t new_elements = new_length - allocated_length_;
345 // First, we must add space for our new inline elements.
Austin Schuhf8440852024-05-31 10:46:50 -0700346 std::optional<std::span<uint8_t>> inserted_bytes;
347
348 if (allocated_length_ == 0) {
349 // If we have padding and the padding is enough to hold the buffer, use
350 // it. This only consumes the padding in the case where we have a
351 // non-inline object, but are allocating small enough data that the
352 // padding is big enough.
353 //
354 // TODO(austin): Use the padding when we are adding large numbers of
355 // elements too.
356 if (new_elements * sizeof(InlineType) <= kPadding1) {
357 inserted_bytes = internal::GetSubSpan(vector_buffer(), kLengthSize,
358 kPadding1 / sizeof(InlineType));
359 }
360 }
361
362 if (!inserted_bytes.has_value()) {
363 inserted_bytes = InsertBytes(
364 inline_data() + allocated_length_ + (kNullTerminate ? 1 : 0),
365 new_elements * sizeof(InlineType), SetZero::kYes);
366 }
367 if (!inserted_bytes.has_value()) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700368 return false;
369 }
370 if (!kInline) {
371 // For non-inline objects, create the space required for all the new
372 // object data.
373 const size_t insertion_point = buffer_.size();
374 if (!InsertBytes(buffer_.data() + insertion_point,
375 new_elements * kDataElementSize, SetZero::kYes)) {
376 return false;
377 }
378 for (size_t index = 0; index < new_elements; ++index) {
379 // Note that the already-allocated data may be arbitrarily-sized, so
380 // we cannot use the same static calculation that we do in the
381 // constructor.
382 object_absolute_offsets_.emplace_back(insertion_point +
383 index * kDataElementSize);
384 }
385 objects_.reserve(new_length);
Austin Schuhf8440852024-05-31 10:46:50 -0700386 } else {
387 // If we allocated memory, and the elements are inline (so we don't have
388 // to deal with allocating elements too), consume any extra space
389 // allocated as extra elements.
390 if (new_elements * sizeof(InlineType) < inserted_bytes->size()) {
391 new_length +=
392 inserted_bytes->size() / sizeof(InlineType) - new_elements;
393 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700394 }
395 allocated_length_ = new_length;
396 }
397 return true;
398 }
399
400 // Accessors for using the Vector as a flatbuffers::Vector.
401 // Note that these pointers will be unstable if any memory allocations occur
402 // that cause memory to get shifted around.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700403 ConstFlatbuffer *AsFlatbufferVector() const {
404 return reinterpret_cast<const Flatbuffer *>(vector_buffer().data());
405 }
406
407 // Copies the contents of the provided vector into this; returns false on
408 // failure (e.g., if the provided vector is too long for the amount of space
409 // we can allocate through reserve()).
James Kuszmaul710883b2023-12-14 14:34:48 -0800410 // This is a deep copy, and will call FromFlatbuffer on any constituent
411 // objects.
James Kuszmaul692780f2023-12-20 14:01:56 -0800412 [[nodiscard]] bool FromFlatbuffer(ConstFlatbuffer *vector) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700413 CHECK(vector != nullptr);
414 return FromFlatbuffer(*vector);
James Kuszmaul692780f2023-12-20 14:01:56 -0800415 }
416 [[nodiscard]] bool FromFlatbuffer(ConstFlatbuffer &vector);
James Kuszmaul6be41022023-12-20 11:55:28 -0800417 // The remaining FromFlatbuffer() overloads are for when using the flatbuffer
418 // "object" API, which uses std::vector's for representing vectors.
419 [[nodiscard]] bool FromFlatbuffer(const std::vector<InlineType> &vector) {
420 static_assert(kInline);
421 return FromData(vector.data(), vector.size());
422 }
423 // Overload for vectors of bools, since the standard library may not use a
424 // full byte per vector element.
425 [[nodiscard]] bool FromFlatbuffer(const std::vector<bool> &vector) {
426 static_assert(kInline);
427 // We won't be able to do a clean memcpy because std::vector<bool> may be
428 // implemented using bit-packing.
429 return FromIterator(vector.cbegin(), vector.cend());
430 }
431 // Overload for non-inline types. Note that to avoid having this overload get
432 // resolved with inline types, we make FlatbufferObjectType != InlineType.
433 [[nodiscard]] bool FromFlatbuffer(
434 const std::vector<FlatbufferObjectType> &vector) {
435 static_assert(!kInline);
436 return FromNotInlineIterable(vector);
437 }
438
439 // Copies values from the provided data pointer into the vector, resizing the
440 // vector as needed to match. Returns false on failure (e.g., if the
441 // underlying allocator has insufficient space to perform the copy). Only
442 // works for inline data types.
443 [[nodiscard]] bool FromData(const InlineType *input_data, size_t input_size) {
444 static_assert(kInline);
445 if (!reserve(input_size)) {
446 return false;
447 }
448
449 // We will be overwriting the whole vector very shortly; there is no need to
450 // clear the buffer to zero.
451 resize_inline(input_size, SetZero::kNo);
452
Philipp Schraderd1c74a82024-04-30 13:46:31 -0700453 if (input_size > 0) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700454 CHECK(input_data != nullptr);
455 memcpy(inline_data(), input_data, size() * sizeof(InlineType));
Philipp Schraderd1c74a82024-04-30 13:46:31 -0700456 }
James Kuszmaul6be41022023-12-20 11:55:28 -0800457 return true;
458 }
459
460 // Copies values from the provided iterators into the vector, resizing the
461 // vector as needed to match. Returns false on failure (e.g., if the
462 // underlying allocator has insufficient space to perform the copy). Only
463 // works for inline data types.
464 // Does not attempt any optimizations if the iterators meet the
465 // std::contiguous_iterator concept; instead, it simply copies each element
466 // out one-by-one.
467 template <typename Iterator>
468 [[nodiscard]] bool FromIterator(Iterator begin, Iterator end) {
469 static_assert(kInline);
470 resize(0);
471 for (Iterator it = begin; it != end; ++it) {
472 if (!reserve(size() + 1)) {
473 return false;
474 }
475 // Should never fail, due to the reserve() above.
476 CHECK(emplace_back(*it));
477 }
478 return true;
479 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700480
481 // Returns the element at the provided index. index must be less than size().
482 const T &at(size_t index) const {
483 CHECK_LT(index, length_);
484 return unsafe_at(index);
485 }
486
487 // Same as at(), except that bounds checks are only performed in non-optimized
488 // builds.
489 // TODO(james): The GetInlineElement() call itself does some bounds-checking;
490 // consider down-grading that.
491 const T &unsafe_at(size_t index) const {
492 DCHECK_LT(index, length_);
493 if (kInline) {
494 // This reinterpret_cast is extremely wrong if T != InlineType (this is
495 // fine because we only do this if kInline is true).
496 // TODO(james): Get the templating improved so that we can get away with
497 // specializing at() instead of using if statements. Resolving this will
498 // also allow deduplicating the Resize() calls.
499 // This specialization is difficult because you cannot partially
500 // specialize a templated class method (online things seem to suggest e.g.
501 // using a struct as the template parameter rather than having separate
502 // parameters).
503 return reinterpret_cast<const T &>(GetInlineElement(index));
504 } else {
505 return objects_[index].t;
506 }
507 }
508
509 // Returns a mutable pointer to the element at the provided index. index must
510 // be less than size().
511 T &at(size_t index) {
512 CHECK_LT(index, length_);
513 return unsafe_at(index);
514 }
515
516 // Same as at(), except that bounds checks are only performed in non-optimized
517 // builds.
518 // TODO(james): The GetInlineElement() call itself does some bounds-checking;
519 // consider down-grading that.
520 T &unsafe_at(size_t index) {
521 DCHECK_LT(index, length_);
522 if (kInline) {
523 // This reinterpret_cast is extremely wrong if T != InlineType (this is
524 // fine because we only do this if kInline is true).
525 // TODO(james): Get the templating improved so that we can get away with
526 // specializing at() instead of using if statements. Resolving this will
527 // also allow deduplicating the Resize() calls.
528 // This specialization is difficult because you cannot partially
529 // specialize a templated class method (online things seem to suggest e.g.
530 // using a struct as the template parameter rather than having separate
531 // parameters).
532 return reinterpret_cast<T &>(GetInlineElement(index));
533 } else {
534 return objects_[index].t;
535 }
536 }
537
538 const T &operator[](size_t index) const { return at(index); }
539 T &operator[](size_t index) { return at(index); }
540
541 // Resizes the vector to the requested size.
542 // size must be less than or equal to the current capacity() of the vector.
543 // Does not allocate additional memory (call reserve() to allocate additional
544 // memory).
545 // Zero-initializes all inline element; initializes all subtable/string
546 // elements to extant but empty objects.
547 void resize(size_t size);
548
549 // Resizes an inline vector to the requested size.
550 // When changing the size of the vector, the removed/inserted elements will be
551 // set to zero if requested. Otherwise, they will be left uninitialized.
552 void resize_inline(size_t size, SetZero set_zero) {
553 CHECK_LE(size, allocated_length_);
554 static_assert(
555 kInline,
556 "Vector::resize_inline() only works for inline vector types (scalars, "
557 "enums, structs).");
558 if (size == length_) {
559 return;
560 }
561 if (set_zero == SetZero::kYes) {
562 memset(
563 reinterpret_cast<void *>(inline_data() + std::min(size, length_)), 0,
564 std::abs(static_cast<ssize_t>(length_) - static_cast<ssize_t>(size)) *
565 sizeof(InlineType));
566 }
567 length_ = size;
568 SetLength(length_);
569 }
570 // Resizes a vector of offsets to the requested size.
571 // If the size is increased, the new elements will be initialized
572 // to empty but extant objects for non-inlined types (so, zero-length
573 // vectors/strings; objects that exist but have no fields populated).
574 // Note that this is always equivalent to resize().
575 void resize_not_inline(size_t size) {
576 CHECK_LE(size, allocated_length_);
577 static_assert(!kInline,
578 "Vector::resize_not_inline() only works for offset vector "
579 "types (objects, strings).");
580 if (size == length_) {
581 return;
582 } else if (length_ > size) {
583 // TODO: Remove any excess allocated memory.
584 length_ = size;
585 SetLength(length_);
586 return;
587 } else {
588 while (length_ < size) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700589 CHECK(emplace_back() != nullptr);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700590 }
591 }
592 }
593
594 // Accessors directly to the inline data of a vector.
595 const T *data() const {
596 static_assert(kInline,
597 "If you have a use-case for directly accessing the "
598 "flatbuffer data pointer for vectors of "
599 "objects/strings, please start a discussion.");
600 return inline_data();
601 }
602
603 T *data() {
604 static_assert(kInline,
605 "If you have a use-case for directly accessing the "
606 "flatbuffer data pointer for vectors of "
607 "objects/strings, please start a discussion.");
608 return inline_data();
609 }
610
James Kuszmaul22448052023-12-14 15:55:14 -0800611 // Iterators to allow easy use with standard C++ features.
612 iterator begin() { return iterator(this, 0); }
613 iterator end() { return iterator(this, size()); }
614 const_iterator begin() const { return const_iterator(this, 0); }
615 const_iterator end() const { return const_iterator(this, size()); }
616
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700617 std::string SerializationDebugString() const {
618 std::stringstream str;
619 str << "Raw Size: " << kSize << " alignment: " << kAlign
620 << " allocated length: " << allocated_length_ << " inline alignment "
Austin Schuhf8440852024-05-31 10:46:50 -0700621 << kInlineAlign << " \n";
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700622 str << "Observed length " << GetLength() << " (expected " << length_
623 << ")\n";
624 str << "Inline Size " << kInlineSize << " Inline bytes/value:\n";
625 // TODO(james): Get pretty-printing for structs so we can provide better
626 // debug.
627 internal::DebugBytes(
628 internal::GetSubSpan(vector_buffer(), kLengthSize,
629 sizeof(InlineType) * allocated_length_),
630 str);
Austin Schuhf8440852024-05-31 10:46:50 -0700631 str << "kPadding1 " << kPadding1 << " offset data size "
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700632 << kOffsetOffsetDataSize << "\n";
633 return str.str();
634 }
635
636 protected:
637 friend struct internal::TableMover<
638 Vector<T, kStaticLength, kInline, kForceAlign, kNullTerminate>>;
639 // protected so that the String class can access the move constructor.
640 Vector(Vector &&) = default;
641
642 private:
Austin Schuhf8440852024-05-31 10:46:50 -0700643 // See kAlign.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700644 size_t Alignment() const final { return kAlign; }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700645 // Returns a buffer that starts at the start of the vector itself (past any
646 // padding).
Austin Schuhf8440852024-05-31 10:46:50 -0700647 std::span<uint8_t> vector_buffer() { return buffer(); }
648 std::span<const uint8_t> vector_buffer() const { return buffer(); }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700649
650 bool AddInlineElement(InlineType e) {
651 if (length_ == allocated_length_) {
652 return false;
653 }
654 SetInlineElement(length_, e);
655 ++length_;
656 SetLength(length_);
657 return true;
658 }
659
660 void SetInlineElement(size_t index, InlineType value) {
661 CHECK_LT(index, allocated_length_);
662 inline_data()[index] = value;
663 }
664
665 InlineType &GetInlineElement(size_t index) {
666 CHECK_LT(index, allocated_length_);
667 return inline_data()[index];
668 }
669
670 const InlineType &GetInlineElement(size_t index) const {
671 CHECK_LT(index, allocated_length_);
672 return inline_data()[index];
673 }
674
675 // Returns a pointer to the start of the inline data itself.
676 InlineType *inline_data() {
677 return reinterpret_cast<InlineType *>(vector_buffer().data() + kLengthSize);
678 }
679 const InlineType *inline_data() const {
680 return reinterpret_cast<const InlineType *>(vector_buffer().data() +
681 kLengthSize);
682 }
683
684 // Updates the length of the vector to match the provided length. Does not set
685 // the length_ member.
686 void SetLength(LengthType length) {
687 *reinterpret_cast<LengthType *>(vector_buffer().data()) = length;
688 if (kNullTerminate) {
689 memset(reinterpret_cast<void *>(inline_data() + length), 0,
690 sizeof(InlineType));
691 }
692 }
693 LengthType GetLength() const {
694 return *reinterpret_cast<const LengthType *>(vector_buffer().data());
695 }
696
697 // Overrides to allow ResizeableObject to manage memory adjustments.
698 size_t NumberOfSubObjects() const final {
699 return kInline ? 0 : allocated_length_;
700 }
701 using ResizeableObject::SubObject;
702 SubObject GetSubObject(size_t index) final {
703 return SubObject{
704 reinterpret_cast<uoffset_t *>(&GetInlineElement(index)),
705 // In order to let this compile regardless of whether type T is an
706 // object type or not, we just use a reinterpret_cast.
707 (index < length_)
708 ? reinterpret_cast<ResizeableObject *>(&objects_[index].t)
709 : nullptr,
710 &object_absolute_offsets_[index]};
711 }
712 // Implementation that handles copying from a flatbuffers::Vector of an inline
713 // data type.
James Kuszmaul692780f2023-12-20 14:01:56 -0800714 [[nodiscard]] bool FromInlineFlatbuffer(ConstFlatbuffer &vector) {
715 return FromData(reinterpret_cast<const InlineType *>(vector.Data()),
716 vector.size());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700717 }
718
719 // Implementation that handles copying from a flatbuffers::Vector of a
720 // not-inline data type.
James Kuszmaul6be41022023-12-20 11:55:28 -0800721 template <typename Iterable>
722 [[nodiscard]] bool FromNotInlineIterable(const Iterable &vector) {
723 if (!reserve(vector.size())) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700724 return false;
725 }
726 // "Clear" the vector.
727 resize_not_inline(0);
728
James Kuszmaul6be41022023-12-20 11:55:28 -0800729 for (const auto &entry : vector) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700730 T *emplaced_entry = emplace_back();
731 CHECK(emplaced_entry != nullptr);
732 if (!emplaced_entry->FromFlatbuffer(entry)) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700733 return false;
734 }
735 }
736 return true;
737 }
738
James Kuszmaul692780f2023-12-20 14:01:56 -0800739 [[nodiscard]] bool FromNotInlineFlatbuffer(const Flatbuffer &vector) {
740 return FromNotInlineIterable(vector);
James Kuszmaul6be41022023-12-20 11:55:28 -0800741 }
742
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700743 // In order to allow for easy partial template specialization, we use a
744 // non-member class to call FromInline/FromNotInlineFlatbuffer and
745 // resize_inline/resize_not_inline. There are not actually any great ways to
746 // do this with just our own class member functions, so instead we make these
747 // methods members of a friend of the Vector class; we then partially
748 // specialize the entire InlineWrapper class and use it to isolate anything
749 // that needs to have a common user interface while still having separate
750 // actual logic.
751 template <typename T_, bool kInline_, class Enable_>
752 friend struct internal::InlineWrapper;
753
754 // Note: The objects here really want to be owned by this object (as opposed
755 // to e.g. returning a stack-allocated object from the emplace_back() methods
756 // that the user then owns). There are two main challenges with have the user
757 // own the object on question:
758 // 1. We can't have >1 reference floating around, or else one object's state
759 // can become out of date. This forces us to do ref-counting and could
760 // make certain types of code obnoxious to write.
761 // 2. Once the user-created object goes out of scope, we lose all of its
762 // internal state. In _theory_ it should be possible to reconstruct most
763 // of the relevant state by examining the contents of the buffer, but
764 // doing so would be cumbersome.
765 aos::InlinedVector<internal::TableMover<ObjectType>,
766 kInline ? 0 : kStaticLength>
767 objects_;
768 aos::InlinedVector<size_t, kInline ? 0 : kStaticLength>
769 object_absolute_offsets_;
770 // Current actual length of the vector.
771 size_t length_ = 0;
772 // Current length that we have allocated space available for.
773 size_t allocated_length_ = kStaticLength;
774};
775
776template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
777 bool kNullTerminate>
778T *Vector<T, kStaticLength, kInline, kForceAlign,
779 kNullTerminate>::emplace_back() {
780 static_assert(!kInline);
781 if (length_ >= allocated_length_) {
782 return nullptr;
783 }
784 const size_t object_start = object_absolute_offsets_[length_];
785 std::span<uint8_t> object_buffer =
786 internal::GetSubSpan(buffer(), object_start, T::kSize);
787 objects_.emplace_back(object_buffer, this);
788 const uoffset_t offset =
789 object_start - (reinterpret_cast<size_t>(&GetInlineElement(length_)) -
790 reinterpret_cast<size_t>(buffer().data()));
791 CHECK(AddInlineElement(offset));
792 return &objects_[objects_.size() - 1].t;
793}
794
795// The String class is a special version of the Vector that is always
796// null-terminated, always contains 1-byte character elements, and which has a
797// few extra methods for convenient string access.
798template <size_t kStaticLength>
799class String : public Vector<char, kStaticLength, true, 0, true> {
800 public:
801 typedef Vector<char, kStaticLength, true, 0, true> VectorType;
802 typedef flatbuffers::String Flatbuffer;
James Kuszmaul6be41022023-12-20 11:55:28 -0800803 typedef std::string FlatbufferObjectType;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700804 String(std::span<uint8_t> buffer, ResizeableObject *parent)
805 : VectorType(buffer, parent) {}
806 virtual ~String() {}
807 void SetString(std::string_view string) {
Sanjay Narayanan71de06a2024-05-06 15:24:48 -0700808 CHECK_LE(string.size(), VectorType::capacity());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700809 VectorType::resize_inline(string.size(), SetZero::kNo);
810 memcpy(VectorType::data(), string.data(), string.size());
811 }
James Kuszmaul6be41022023-12-20 11:55:28 -0800812 using VectorType::FromFlatbuffer;
813 [[nodiscard]] bool FromFlatbuffer(const std::string &string) {
814 return VectorType::FromData(string.data(), string.size());
815 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700816 std::string_view string_view() const {
817 return std::string_view(VectorType::data(), VectorType::size());
818 }
819 std::string str() const {
820 return std::string(VectorType::data(), VectorType::size());
821 }
822 const char *c_str() const { return VectorType::data(); }
823
824 private:
825 friend struct internal::TableMover<String<kStaticLength>>;
826 String(String &&) = default;
827};
828
829namespace internal {
830// Specialization for all non-inline vector types. All of these types will just
831// use offsets for their inline data and have appropriate member types/constants
832// for the remaining fields.
833template <typename T>
834struct InlineWrapper<T, false, void> {
835 typedef uoffset_t Type;
836 typedef T ObjectType;
837 typedef flatbuffers::Offset<typename T::Flatbuffer> FlatbufferType;
838 typedef flatbuffers::Offset<typename T::Flatbuffer> ConstFlatbufferType;
James Kuszmaul6be41022023-12-20 11:55:28 -0800839 typedef T::FlatbufferObjectType FlatbufferObjectType;
Austin Schuhf8440852024-05-31 10:46:50 -0700840 static constexpr size_t kDataElementAlign = T::kAlign;
841 static constexpr size_t kDataElementAlignOffset = T::kAlignOffset;
842 static constexpr size_t kDataElementSize =
843 ((T::kSize + T::kAlign - 1) / T::kAlign) * T::kAlign;
844 static_assert((kDataElementSize % kDataElementAlign) == 0);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700845 template <typename StaticVector>
846 static bool FromFlatbuffer(
James Kuszmaul692780f2023-12-20 14:01:56 -0800847 StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700848 return to->FromNotInlineFlatbuffer(from);
849 }
850 template <typename StaticVector>
851 static void ResizeVector(StaticVector *target, size_t size) {
852 target->resize_not_inline(size);
853 }
854};
855// Specialization for "normal" scalar inline data (ints, floats, doubles,
856// enums).
857template <typename T>
858struct InlineWrapper<T, true,
859 typename std::enable_if_t<!std::is_class<T>::value>> {
860 typedef T Type;
861 typedef T ObjectType;
862 typedef T FlatbufferType;
863 typedef T ConstFlatbufferType;
James Kuszmaul6be41022023-12-20 11:55:28 -0800864 typedef T *FlatbufferObjectType;
Austin Schuhf8440852024-05-31 10:46:50 -0700865 static constexpr size_t kDataElementAlign = alignof(T);
866 static constexpr size_t kDataElementAlignOffset = 0;
867 static constexpr size_t kDataElementSize = sizeof(T);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700868 template <typename StaticVector>
869 static bool FromFlatbuffer(
James Kuszmaul692780f2023-12-20 14:01:56 -0800870 StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700871 return to->FromInlineFlatbuffer(from);
872 }
873 template <typename StaticVector>
874 static void ResizeVector(StaticVector *target, size_t size) {
875 target->resize_inline(size, SetZero::kYes);
876 }
877};
878// Specialization for booleans, given that flatbuffers uses uint8_t's for bools.
879template <>
880struct InlineWrapper<bool, true, void> {
881 typedef uint8_t Type;
882 typedef uint8_t ObjectType;
883 typedef uint8_t FlatbufferType;
884 typedef uint8_t ConstFlatbufferType;
James Kuszmaul6be41022023-12-20 11:55:28 -0800885 typedef uint8_t *FlatbufferObjectType;
Austin Schuhf8440852024-05-31 10:46:50 -0700886 static constexpr size_t kDataElementAlign = 1u;
887 static constexpr size_t kDataElementAlignOffset = 0;
888 static constexpr size_t kDataElementSize = 1u;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700889 template <typename StaticVector>
890 static bool FromFlatbuffer(
James Kuszmaul692780f2023-12-20 14:01:56 -0800891 StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700892 return to->FromInlineFlatbuffer(from);
893 }
894 template <typename StaticVector>
895 static void ResizeVector(StaticVector *target, size_t size) {
896 target->resize_inline(size, SetZero::kYes);
897 }
898};
899// Specialization for flatbuffer structs.
900// The flatbuffers codegen uses struct pointers rather than references or the
901// such, so it needs to be treated special.
902template <typename T>
903struct InlineWrapper<T, true,
904 typename std::enable_if_t<std::is_class<T>::value>> {
905 typedef T Type;
906 typedef T ObjectType;
907 typedef T *FlatbufferType;
908 typedef const T *ConstFlatbufferType;
James Kuszmaul6be41022023-12-20 11:55:28 -0800909 typedef T *FlatbufferObjectType;
Austin Schuhf8440852024-05-31 10:46:50 -0700910 static constexpr size_t kDataElementAlign = alignof(T);
911 static constexpr size_t kDataElementAlignOffset = 0;
912 static constexpr size_t kDataElementSize = sizeof(T);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700913 template <typename StaticVector>
914 static bool FromFlatbuffer(
James Kuszmaul692780f2023-12-20 14:01:56 -0800915 StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700916 return to->FromInlineFlatbuffer(from);
917 }
918 template <typename StaticVector>
919 static void ResizeVector(StaticVector *target, size_t size) {
920 target->resize_inline(size, SetZero::kYes);
921 }
922};
923} // namespace internal
924 //
925template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
926 bool kNullTerminate>
927bool Vector<T, kStaticLength, kInline, kForceAlign,
James Kuszmaul692780f2023-12-20 14:01:56 -0800928 kNullTerminate>::FromFlatbuffer(ConstFlatbuffer &vector) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700929 return internal::InlineWrapper<T, kInline>::FromFlatbuffer(this, vector);
930}
931
932template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
933 bool kNullTerminate>
934void Vector<T, kStaticLength, kInline, kForceAlign, kNullTerminate>::resize(
935 size_t size) {
936 internal::InlineWrapper<T, kInline>::ResizeVector(this, size);
937}
938
939} // namespace aos::fbs
940#endif // AOS_FLATBUFFERS_STATIC_VECTOR_H_