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