blob: be41b63f64a2399965063ff1b4daa6d400f4531d [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:
Austin Schuh02e0d772024-05-30 16:41:06 -070021 static constexpr size_t kBufferSize = T::kRootSize;
22 static constexpr size_t kAlign = T::kAlign;
James Kuszmaul1c9693f2023-12-08 09:45:26 -080023 // 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 Kuszmaulf5eb4682023-09-22 17:16:59 -070042 Builder(Allocator *allocator)
43 : ResizeableObject(
James Kuszmaul1c9693f2023-12-08 09:45:26 -080044 allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
James Kuszmaulf5eb4682023-09-22 17:16:59 -070045 allocator),
46 flatbuffer_start_(BufferStart(buffer_)),
47 flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize),
48 this) {
49 SetPrefix();
50 }
James Kuszmaul9a8cdcd2024-01-13 14:10:39 -080051 Builder(std::unique_ptr<Allocator> allocator =
Austin Schuh02e0d772024-05-30 16:41:06 -070052 std::make_unique<AlignedVectorAllocator>())
James Kuszmaulf5eb4682023-09-22 17:16:59 -070053 : ResizeableObject(
James Kuszmaul1c9693f2023-12-08 09:45:26 -080054 allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
James Kuszmaulf5eb4682023-09-22 17:16:59 -070055 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 Kuszmaul9052bdb2023-12-20 11:51:46 -080081 FlatbufferSpan<const typename T::Flatbuffer> AsFlatbufferSpan() const {
82 return {buffer()};
83 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -070084
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 Kuszmauldde65632023-12-07 16:12:26 -080092 T &operator*() { return *get(); }
93 T *operator->() { return get(); }
James Kuszmaulf5eb4682023-09-22 17:16:59 -070094
95 private:
96 size_t Alignment() const override { return flatbuffer_.t.Alignment(); }
James Kuszmaulf5eb4682023-09-22 17:16:59 -070097 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 Schuhf8440852024-05-31 10:46:50 -0700105 // Manually aligns the start of the actual flatbuffer to handle the alignment
106 // offset.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700107 static size_t BufferStart(std::span<uint8_t> buffer) {
Austin Schuhf8440852024-05-31 10:46:50 -0700108 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 Kuszmaulf5eb4682023-09-22 17:16:59 -0700113 reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t),
Austin Schuhf8440852024-05-31 10:46:50 -0700114 T::kAlign, T::kAlignOffset) -
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700115 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_