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; |
| 22 | Builder(Allocator *allocator) |
| 23 | : ResizeableObject( |
| 24 | allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kNo), |
| 25 | allocator), |
| 26 | flatbuffer_start_(BufferStart(buffer_)), |
| 27 | flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize), |
| 28 | this) { |
| 29 | SetPrefix(); |
| 30 | } |
| 31 | Builder(std::unique_ptr<Allocator> allocator) |
| 32 | : ResizeableObject( |
| 33 | allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kNo), |
| 34 | std::move(allocator)), |
| 35 | flatbuffer_start_(BufferStart(buffer_)), |
| 36 | flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize), |
| 37 | this) { |
| 38 | SetPrefix(); |
| 39 | } |
| 40 | Builder(Builder &&other) |
| 41 | : ResizeableObject(std::move(other)), |
| 42 | flatbuffer_(std::move(other.flatbuffer_)) { |
| 43 | flatbuffer_start_ = other.flatbuffer_start_; |
| 44 | other.flatbuffer_start_ = 0; |
| 45 | } |
| 46 | |
| 47 | ~Builder() { |
| 48 | if (allocator() != nullptr) { |
| 49 | allocator()->Deallocate(buffer_); |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // Returns an object containing the current raw flatbuffer type. Note that if |
| 54 | // the allocator type allows changes to the structure/amount of allocated |
| 55 | // memory, the underlying buffer will not be stable and so the returned |
| 56 | // FlatbufferSpan may be invalidated by mutations to the flatbuffer. |
| 57 | FlatbufferSpan<typename T::Flatbuffer> AsFlatbufferSpan() { |
| 58 | return {buffer()}; |
| 59 | } |
| 60 | |
| 61 | // Returns true if the flatbuffer is validly constructed. Should always return |
| 62 | // true (barring some sort of memory corruption). Exposed for convenience. |
| 63 | bool Verify() { return AsFlatbufferSpan().Verify(); } |
| 64 | |
| 65 | // Returns the actual object for you to operate on and construct the |
| 66 | // flatbuffer. Unlike AsFlatbufferSpan(), this will be stable. |
| 67 | T *get() { return &flatbuffer_.t; } |
| 68 | |
| 69 | private: |
| 70 | size_t Alignment() const override { return flatbuffer_.t.Alignment(); } |
| 71 | size_t AbsoluteOffsetOffset() const override { return 0; } |
| 72 | size_t NumberOfSubObjects() const override { return 1; } |
| 73 | void SetPrefix() { |
| 74 | // We can't do much if the provided buffer isn't at least 4-byte aligned, |
| 75 | // because we are required to put the root table offset at the start of the |
| 76 | // buffer. |
| 77 | CHECK_EQ(reinterpret_cast<size_t>(buffer_.data()) % alignof(uoffset_t), 0u); |
| 78 | *reinterpret_cast<uoffset_t *>(buffer_.data()) = flatbuffer_start_; |
| 79 | } |
| 80 | // Because the allocator API doesn't provide a way for us to request a |
| 81 | // strictly aligned buffer, manually align the start of the actual flatbuffer |
| 82 | // data if needed. |
| 83 | static size_t BufferStart(std::span<uint8_t> buffer) { |
| 84 | return aos::fbs::PaddedSize( |
| 85 | reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t), |
| 86 | T::kAlign) - |
| 87 | reinterpret_cast<size_t>(buffer.data()); |
| 88 | } |
| 89 | |
| 90 | // Some allocators don't do a great job of supporting arbitrary alignments; if |
| 91 | // the alignment of the buffer changes, we need to reshuffle everything to |
| 92 | // continue guaranteeing alignment. |
| 93 | void ObserveBufferModification() override { |
| 94 | const size_t new_start = BufferStart(buffer_); |
| 95 | if (new_start != flatbuffer_start_) { |
| 96 | const size_t used_size = flatbuffer_.t.buffer().size(); |
| 97 | CHECK_LT(flatbuffer_start_ + used_size, buffer_.size()); |
| 98 | CHECK_LT(new_start + used_size, buffer_.size()); |
| 99 | memmove(buffer_.data() + new_start, buffer_.data() + flatbuffer_start_, |
| 100 | used_size); |
| 101 | flatbuffer_.t.UpdateBuffer( |
| 102 | internal::GetSubSpan(buffer_, new_start, used_size), |
| 103 | buffer_.data() + new_start, 0); |
| 104 | flatbuffer_start_ = new_start; |
| 105 | SetPrefix(); |
| 106 | } |
| 107 | } |
| 108 | using ResizeableObject::SubObject; |
| 109 | SubObject GetSubObject(size_t index) override { |
| 110 | CHECK_EQ(0u, index); |
| 111 | return {reinterpret_cast<uoffset_t *>(buffer_.data()), &flatbuffer_.t, |
| 112 | &flatbuffer_start_}; |
| 113 | } |
| 114 | // Offset from the start of the buffer to the actual start of the flatbuffer |
| 115 | // (identical to the root offset of the flatbuffer). |
| 116 | size_t flatbuffer_start_; |
| 117 | internal::TableMover<T> flatbuffer_; |
| 118 | }; |
| 119 | } // namespace aos::fbs |
| 120 | #endif // AOS_FLATBUFFERS_BUILDER_H_ |