blob: 36225c024bd6612a74e2ce625775fb75c4924ed6 [file] [log] [blame]
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001#ifndef AOS_FLATBUFFERS_BUILDER_H_
2#define AOS_FLATBUFFERS_BUILDER_H_
3#include "aos/flatbuffers.h"
4#include "aos/flatbuffers/static_table.h"
5namespace 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.
18template <typename T>
19class Builder final : public ResizeableObject {
20 public:
21 static constexpr size_t kBufferSize = T::kUnalignedBufferSize;
James Kuszmaul1c9693f2023-12-08 09:45:26 -080022 // 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 Kuszmaulf5eb4682023-09-22 17:16:59 -070041 Builder(Allocator *allocator)
42 : ResizeableObject(
James Kuszmaul1c9693f2023-12-08 09:45:26 -080043 allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
James Kuszmaulf5eb4682023-09-22 17:16:59 -070044 allocator),
45 flatbuffer_start_(BufferStart(buffer_)),
46 flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize),
47 this) {
48 SetPrefix();
49 }
James Kuszmaul9a8cdcd2024-01-13 14:10:39 -080050 Builder(std::unique_ptr<Allocator> allocator =
51 std::make_unique<VectorAllocator>())
James Kuszmaulf5eb4682023-09-22 17:16:59 -070052 : ResizeableObject(
James Kuszmaul1c9693f2023-12-08 09:45:26 -080053 allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
James Kuszmaulf5eb4682023-09-22 17:16:59 -070054 std::move(allocator)),
55 flatbuffer_start_(BufferStart(buffer_)),
56 flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize),
57 this) {
58 SetPrefix();
59 }
60 Builder(Builder &&other)
61 : ResizeableObject(std::move(other)),
62 flatbuffer_(std::move(other.flatbuffer_)) {
63 flatbuffer_start_ = other.flatbuffer_start_;
64 other.flatbuffer_start_ = 0;
65 }
66
67 ~Builder() {
68 if (allocator() != nullptr) {
69 allocator()->Deallocate(buffer_);
70 }
71 }
72
73 // Returns an object containing the current raw flatbuffer type. Note that if
74 // the allocator type allows changes to the structure/amount of allocated
75 // memory, the underlying buffer will not be stable and so the returned
76 // FlatbufferSpan may be invalidated by mutations to the flatbuffer.
77 FlatbufferSpan<typename T::Flatbuffer> AsFlatbufferSpan() {
78 return {buffer()};
79 }
James Kuszmaul9052bdb2023-12-20 11:51:46 -080080 FlatbufferSpan<const typename T::Flatbuffer> AsFlatbufferSpan() const {
81 return {buffer()};
82 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -070083
84 // Returns true if the flatbuffer is validly constructed. Should always return
85 // true (barring some sort of memory corruption). Exposed for convenience.
86 bool Verify() { return AsFlatbufferSpan().Verify(); }
87
88 // Returns the actual object for you to operate on and construct the
89 // flatbuffer. Unlike AsFlatbufferSpan(), this will be stable.
90 T *get() { return &flatbuffer_.t; }
James Kuszmauldde65632023-12-07 16:12:26 -080091 T &operator*() { return *get(); }
92 T *operator->() { return get(); }
James Kuszmaulf5eb4682023-09-22 17:16:59 -070093
94 private:
95 size_t Alignment() const override { return flatbuffer_.t.Alignment(); }
96 size_t AbsoluteOffsetOffset() const override { return 0; }
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 }
105 // Because the allocator API doesn't provide a way for us to request a
106 // strictly aligned buffer, manually align the start of the actual flatbuffer
107 // data if needed.
108 static size_t BufferStart(std::span<uint8_t> buffer) {
109 return aos::fbs::PaddedSize(
110 reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t),
111 T::kAlign) -
112 reinterpret_cast<size_t>(buffer.data());
113 }
114
115 // Some allocators don't do a great job of supporting arbitrary alignments; if
116 // the alignment of the buffer changes, we need to reshuffle everything to
117 // continue guaranteeing alignment.
118 void ObserveBufferModification() override {
119 const size_t new_start = BufferStart(buffer_);
120 if (new_start != flatbuffer_start_) {
121 const size_t used_size = flatbuffer_.t.buffer().size();
122 CHECK_LT(flatbuffer_start_ + used_size, buffer_.size());
123 CHECK_LT(new_start + used_size, buffer_.size());
124 memmove(buffer_.data() + new_start, buffer_.data() + flatbuffer_start_,
125 used_size);
126 flatbuffer_.t.UpdateBuffer(
127 internal::GetSubSpan(buffer_, new_start, used_size),
128 buffer_.data() + new_start, 0);
129 flatbuffer_start_ = new_start;
130 SetPrefix();
131 }
132 }
133 using ResizeableObject::SubObject;
134 SubObject GetSubObject(size_t index) override {
135 CHECK_EQ(0u, index);
136 return {reinterpret_cast<uoffset_t *>(buffer_.data()), &flatbuffer_.t,
137 &flatbuffer_start_};
138 }
139 // Offset from the start of the buffer to the actual start of the flatbuffer
140 // (identical to the root offset of the flatbuffer).
141 size_t flatbuffer_start_;
142 internal::TableMover<T> flatbuffer_;
143};
144} // namespace aos::fbs
145#endif // AOS_FLATBUFFERS_BUILDER_H_