James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 1 | #ifndef AOS_FLATBUFFERS_BUILDER_H_ |
| 2 | #define AOS_FLATBUFFERS_BUILDER_H_ |
| 3 | #include "aos/flatbuffers.h" |
| 4 | #include "aos/flatbuffers/static_table.h" |
| 5 | namespace aos::fbs { |
| 6 | |
| 7 | // Builder class to handle the memory for a static flatbuffer object. This |
| 8 | // fulfills a similar role to the FlatBufferBuilder type in the traditional API. |
| 9 | // Typical usage: |
| 10 | // aos::fbs::VectorAllocator allocator; |
| 11 | // Builder<TestTableStatic> builder(&allocator); |
| 12 | // TestTableStatic *object = builder.get(); |
| 13 | // object->set_scalar(123); |
| 14 | // |
| 15 | // At all points you will have a valid and complete flatbuffer, so you never |
| 16 | // need to call Finish() or anything. You can just directly use the flatbuffer |
| 17 | // as if it is a real flatbuffer. |
| 18 | template <typename T> |
| 19 | class Builder final : public ResizeableObject { |
| 20 | public: |
Austin Schuh | 02e0d77 | 2024-05-30 16:41:06 -0700 | [diff] [blame] | 21 | static constexpr size_t kBufferSize = T::kRootSize; |
| 22 | static constexpr size_t kAlign = T::kAlign; |
James Kuszmaul | 1c9693f | 2023-12-08 09:45:26 -0800 | [diff] [blame] | 23 | // Note on memory initialization: We zero-initialize all the memory that we |
| 24 | // create at the start. While this can be overkill, it is simpler to manage |
| 25 | // the alternatives, and we don't currently have a clear performance need for |
| 26 | // doing this more piecemeal. Note that the memory that this zero-initializes |
| 27 | // falls into three categories: |
| 28 | // 1. Padding that we need to zero-initialize (arguably we could get away |
| 29 | // without initializing our padding, but leaving uninitialized memory |
| 30 | // floating around generally isn't great, especially since we generally |
| 31 | // want to be able to compress flatbuffer messages efficiently). |
| 32 | // 2. Memory that will end up getting used. |
| 33 | // 3. Memory corresponding to sub-tables/vectors that may or may not end up |
| 34 | // getting used; if it is not used, we want it to get zero-initialized |
| 35 | // since it will effectively be padding. If it is used, then the |
| 36 | // zero-initialization is redundant. |
| 37 | // For messages with large byte buffers (e.g., for camera messages), we |
| 38 | // typically expect that the user will end up dynamically resizing the buffer |
| 39 | // rather than having the length statically set. In those cases, the user |
| 40 | // still has the ability to select whether or not the new memory gets |
| 41 | // zero-initialized. |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 42 | Builder(Allocator *allocator) |
| 43 | : ResizeableObject( |
James Kuszmaul | 1c9693f | 2023-12-08 09:45:26 -0800 | [diff] [blame] | 44 | allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes), |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 45 | allocator), |
| 46 | flatbuffer_start_(BufferStart(buffer_)), |
| 47 | flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize), |
| 48 | this) { |
| 49 | SetPrefix(); |
| 50 | } |
James Kuszmaul | 9a8cdcd | 2024-01-13 14:10:39 -0800 | [diff] [blame] | 51 | Builder(std::unique_ptr<Allocator> allocator = |
Austin Schuh | 02e0d77 | 2024-05-30 16:41:06 -0700 | [diff] [blame] | 52 | std::make_unique<AlignedVectorAllocator>()) |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 53 | : ResizeableObject( |
James Kuszmaul | 1c9693f | 2023-12-08 09:45:26 -0800 | [diff] [blame] | 54 | allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes), |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 55 | std::move(allocator)), |
| 56 | flatbuffer_start_(BufferStart(buffer_)), |
| 57 | flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize), |
| 58 | this) { |
| 59 | SetPrefix(); |
| 60 | } |
| 61 | Builder(Builder &&other) |
| 62 | : ResizeableObject(std::move(other)), |
| 63 | flatbuffer_(std::move(other.flatbuffer_)) { |
| 64 | flatbuffer_start_ = other.flatbuffer_start_; |
| 65 | other.flatbuffer_start_ = 0; |
| 66 | } |
| 67 | |
| 68 | ~Builder() { |
| 69 | if (allocator() != nullptr) { |
| 70 | allocator()->Deallocate(buffer_); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // Returns an object containing the current raw flatbuffer type. Note that if |
| 75 | // the allocator type allows changes to the structure/amount of allocated |
| 76 | // memory, the underlying buffer will not be stable and so the returned |
| 77 | // FlatbufferSpan may be invalidated by mutations to the flatbuffer. |
| 78 | FlatbufferSpan<typename T::Flatbuffer> AsFlatbufferSpan() { |
| 79 | return {buffer()}; |
| 80 | } |
James Kuszmaul | 9052bdb | 2023-12-20 11:51:46 -0800 | [diff] [blame] | 81 | FlatbufferSpan<const typename T::Flatbuffer> AsFlatbufferSpan() const { |
| 82 | return {buffer()}; |
| 83 | } |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 84 | |
| 85 | // Returns true if the flatbuffer is validly constructed. Should always return |
| 86 | // true (barring some sort of memory corruption). Exposed for convenience. |
| 87 | bool Verify() { return AsFlatbufferSpan().Verify(); } |
| 88 | |
| 89 | // Returns the actual object for you to operate on and construct the |
| 90 | // flatbuffer. Unlike AsFlatbufferSpan(), this will be stable. |
| 91 | T *get() { return &flatbuffer_.t; } |
James Kuszmaul | dde6563 | 2023-12-07 16:12:26 -0800 | [diff] [blame] | 92 | T &operator*() { return *get(); } |
| 93 | T *operator->() { return get(); } |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 94 | |
| 95 | private: |
| 96 | size_t Alignment() const override { return flatbuffer_.t.Alignment(); } |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 97 | size_t NumberOfSubObjects() const override { return 1; } |
| 98 | void SetPrefix() { |
| 99 | // We can't do much if the provided buffer isn't at least 4-byte aligned, |
| 100 | // because we are required to put the root table offset at the start of the |
| 101 | // buffer. |
| 102 | CHECK_EQ(reinterpret_cast<size_t>(buffer_.data()) % alignof(uoffset_t), 0u); |
| 103 | *reinterpret_cast<uoffset_t *>(buffer_.data()) = flatbuffer_start_; |
| 104 | } |
Austin Schuh | f844085 | 2024-05-31 10:46:50 -0700 | [diff] [blame^] | 105 | // Manually aligns the start of the actual flatbuffer to handle the alignment |
| 106 | // offset. |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 107 | static size_t BufferStart(std::span<uint8_t> buffer) { |
Austin Schuh | f844085 | 2024-05-31 10:46:50 -0700 | [diff] [blame^] | 108 | CHECK_EQ(reinterpret_cast<size_t>(buffer.data()) % T::kAlign, 0u) |
| 109 | << "Failed to allocate data of length " << buffer.size() |
| 110 | << " with alignment " << T::kAlign; |
| 111 | |
| 112 | return aos::fbs::AlignOffset( |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 113 | reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t), |
Austin Schuh | f844085 | 2024-05-31 10:46:50 -0700 | [diff] [blame^] | 114 | T::kAlign, T::kAlignOffset) - |
James Kuszmaul | f5eb468 | 2023-09-22 17:16:59 -0700 | [diff] [blame] | 115 | reinterpret_cast<size_t>(buffer.data()); |
| 116 | } |
| 117 | |
| 118 | // Some allocators don't do a great job of supporting arbitrary alignments; if |
| 119 | // the alignment of the buffer changes, we need to reshuffle everything to |
| 120 | // continue guaranteeing alignment. |
| 121 | void ObserveBufferModification() override { |
| 122 | const size_t new_start = BufferStart(buffer_); |
| 123 | if (new_start != flatbuffer_start_) { |
| 124 | const size_t used_size = flatbuffer_.t.buffer().size(); |
| 125 | CHECK_LT(flatbuffer_start_ + used_size, buffer_.size()); |
| 126 | CHECK_LT(new_start + used_size, buffer_.size()); |
| 127 | memmove(buffer_.data() + new_start, buffer_.data() + flatbuffer_start_, |
| 128 | used_size); |
| 129 | flatbuffer_.t.UpdateBuffer( |
| 130 | internal::GetSubSpan(buffer_, new_start, used_size), |
| 131 | buffer_.data() + new_start, 0); |
| 132 | flatbuffer_start_ = new_start; |
| 133 | SetPrefix(); |
| 134 | } |
| 135 | } |
| 136 | using ResizeableObject::SubObject; |
| 137 | SubObject GetSubObject(size_t index) override { |
| 138 | CHECK_EQ(0u, index); |
| 139 | return {reinterpret_cast<uoffset_t *>(buffer_.data()), &flatbuffer_.t, |
| 140 | &flatbuffer_start_}; |
| 141 | } |
| 142 | // Offset from the start of the buffer to the actual start of the flatbuffer |
| 143 | // (identical to the root offset of the flatbuffer). |
| 144 | size_t flatbuffer_start_; |
| 145 | internal::TableMover<T> flatbuffer_; |
| 146 | }; |
| 147 | } // namespace aos::fbs |
| 148 | #endif // AOS_FLATBUFFERS_BUILDER_H_ |