blob: 75e0fe211a2f6dd089658cc21125d25fd0eb14bf [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 }
50 Builder(std::unique_ptr<Allocator> allocator)
51 : ResizeableObject(
James Kuszmaul1c9693f2023-12-08 09:45:26 -080052 allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
James Kuszmaulf5eb4682023-09-22 17:16:59 -070053 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; }
James Kuszmauldde65632023-12-07 16:12:26 -080087 T &operator*() { return *get(); }
88 T *operator->() { return get(); }
James Kuszmaulf5eb4682023-09-22 17:16:59 -070089
90 private:
91 size_t Alignment() const override { return flatbuffer_.t.Alignment(); }
92 size_t AbsoluteOffsetOffset() const override { return 0; }
93 size_t NumberOfSubObjects() const override { return 1; }
94 void SetPrefix() {
95 // We can't do much if the provided buffer isn't at least 4-byte aligned,
96 // because we are required to put the root table offset at the start of the
97 // buffer.
98 CHECK_EQ(reinterpret_cast<size_t>(buffer_.data()) % alignof(uoffset_t), 0u);
99 *reinterpret_cast<uoffset_t *>(buffer_.data()) = flatbuffer_start_;
100 }
101 // Because the allocator API doesn't provide a way for us to request a
102 // strictly aligned buffer, manually align the start of the actual flatbuffer
103 // data if needed.
104 static size_t BufferStart(std::span<uint8_t> buffer) {
105 return aos::fbs::PaddedSize(
106 reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t),
107 T::kAlign) -
108 reinterpret_cast<size_t>(buffer.data());
109 }
110
111 // Some allocators don't do a great job of supporting arbitrary alignments; if
112 // the alignment of the buffer changes, we need to reshuffle everything to
113 // continue guaranteeing alignment.
114 void ObserveBufferModification() override {
115 const size_t new_start = BufferStart(buffer_);
116 if (new_start != flatbuffer_start_) {
117 const size_t used_size = flatbuffer_.t.buffer().size();
118 CHECK_LT(flatbuffer_start_ + used_size, buffer_.size());
119 CHECK_LT(new_start + used_size, buffer_.size());
120 memmove(buffer_.data() + new_start, buffer_.data() + flatbuffer_start_,
121 used_size);
122 flatbuffer_.t.UpdateBuffer(
123 internal::GetSubSpan(buffer_, new_start, used_size),
124 buffer_.data() + new_start, 0);
125 flatbuffer_start_ = new_start;
126 SetPrefix();
127 }
128 }
129 using ResizeableObject::SubObject;
130 SubObject GetSubObject(size_t index) override {
131 CHECK_EQ(0u, index);
132 return {reinterpret_cast<uoffset_t *>(buffer_.data()), &flatbuffer_.t,
133 &flatbuffer_start_};
134 }
135 // Offset from the start of the buffer to the actual start of the flatbuffer
136 // (identical to the root offset of the flatbuffer).
137 size_t flatbuffer_start_;
138 internal::TableMover<T> flatbuffer_;
139};
140} // namespace aos::fbs
141#endif // AOS_FLATBUFFERS_BUILDER_H_