blob: be41b63f64a2399965063ff1b4daa6d400f4531d [file] [log] [blame]
#ifndef AOS_FLATBUFFERS_BUILDER_H_
#define AOS_FLATBUFFERS_BUILDER_H_
#include "aos/flatbuffers.h"
#include "aos/flatbuffers/static_table.h"
namespace aos::fbs {
// Builder class to handle the memory for a static flatbuffer object. This
// fulfills a similar role to the FlatBufferBuilder type in the traditional API.
// Typical usage:
// aos::fbs::VectorAllocator allocator;
// Builder<TestTableStatic> builder(&allocator);
// TestTableStatic *object = builder.get();
// object->set_scalar(123);
//
// At all points you will have a valid and complete flatbuffer, so you never
// need to call Finish() or anything. You can just directly use the flatbuffer
// as if it is a real flatbuffer.
template <typename T>
class Builder final : public ResizeableObject {
public:
static constexpr size_t kBufferSize = T::kRootSize;
static constexpr size_t kAlign = T::kAlign;
// Note on memory initialization: We zero-initialize all the memory that we
// create at the start. While this can be overkill, it is simpler to manage
// the alternatives, and we don't currently have a clear performance need for
// doing this more piecemeal. Note that the memory that this zero-initializes
// falls into three categories:
// 1. Padding that we need to zero-initialize (arguably we could get away
// without initializing our padding, but leaving uninitialized memory
// floating around generally isn't great, especially since we generally
// want to be able to compress flatbuffer messages efficiently).
// 2. Memory that will end up getting used.
// 3. Memory corresponding to sub-tables/vectors that may or may not end up
// getting used; if it is not used, we want it to get zero-initialized
// since it will effectively be padding. If it is used, then the
// zero-initialization is redundant.
// For messages with large byte buffers (e.g., for camera messages), we
// typically expect that the user will end up dynamically resizing the buffer
// rather than having the length statically set. In those cases, the user
// still has the ability to select whether or not the new memory gets
// zero-initialized.
Builder(Allocator *allocator)
: ResizeableObject(
allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
allocator),
flatbuffer_start_(BufferStart(buffer_)),
flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize),
this) {
SetPrefix();
}
Builder(std::unique_ptr<Allocator> allocator =
std::make_unique<AlignedVectorAllocator>())
: ResizeableObject(
allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kYes),
std::move(allocator)),
flatbuffer_start_(BufferStart(buffer_)),
flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize),
this) {
SetPrefix();
}
Builder(Builder &&other)
: ResizeableObject(std::move(other)),
flatbuffer_(std::move(other.flatbuffer_)) {
flatbuffer_start_ = other.flatbuffer_start_;
other.flatbuffer_start_ = 0;
}
~Builder() {
if (allocator() != nullptr) {
allocator()->Deallocate(buffer_);
}
}
// Returns an object containing the current raw flatbuffer type. Note that if
// the allocator type allows changes to the structure/amount of allocated
// memory, the underlying buffer will not be stable and so the returned
// FlatbufferSpan may be invalidated by mutations to the flatbuffer.
FlatbufferSpan<typename T::Flatbuffer> AsFlatbufferSpan() {
return {buffer()};
}
FlatbufferSpan<const typename T::Flatbuffer> AsFlatbufferSpan() const {
return {buffer()};
}
// Returns true if the flatbuffer is validly constructed. Should always return
// true (barring some sort of memory corruption). Exposed for convenience.
bool Verify() { return AsFlatbufferSpan().Verify(); }
// Returns the actual object for you to operate on and construct the
// flatbuffer. Unlike AsFlatbufferSpan(), this will be stable.
T *get() { return &flatbuffer_.t; }
T &operator*() { return *get(); }
T *operator->() { return get(); }
private:
size_t Alignment() const override { return flatbuffer_.t.Alignment(); }
size_t NumberOfSubObjects() const override { return 1; }
void SetPrefix() {
// We can't do much if the provided buffer isn't at least 4-byte aligned,
// because we are required to put the root table offset at the start of the
// buffer.
CHECK_EQ(reinterpret_cast<size_t>(buffer_.data()) % alignof(uoffset_t), 0u);
*reinterpret_cast<uoffset_t *>(buffer_.data()) = flatbuffer_start_;
}
// Manually aligns the start of the actual flatbuffer to handle the alignment
// offset.
static size_t BufferStart(std::span<uint8_t> buffer) {
CHECK_EQ(reinterpret_cast<size_t>(buffer.data()) % T::kAlign, 0u)
<< "Failed to allocate data of length " << buffer.size()
<< " with alignment " << T::kAlign;
return aos::fbs::AlignOffset(
reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t),
T::kAlign, T::kAlignOffset) -
reinterpret_cast<size_t>(buffer.data());
}
// Some allocators don't do a great job of supporting arbitrary alignments; if
// the alignment of the buffer changes, we need to reshuffle everything to
// continue guaranteeing alignment.
void ObserveBufferModification() override {
const size_t new_start = BufferStart(buffer_);
if (new_start != flatbuffer_start_) {
const size_t used_size = flatbuffer_.t.buffer().size();
CHECK_LT(flatbuffer_start_ + used_size, buffer_.size());
CHECK_LT(new_start + used_size, buffer_.size());
memmove(buffer_.data() + new_start, buffer_.data() + flatbuffer_start_,
used_size);
flatbuffer_.t.UpdateBuffer(
internal::GetSubSpan(buffer_, new_start, used_size),
buffer_.data() + new_start, 0);
flatbuffer_start_ = new_start;
SetPrefix();
}
}
using ResizeableObject::SubObject;
SubObject GetSubObject(size_t index) override {
CHECK_EQ(0u, index);
return {reinterpret_cast<uoffset_t *>(buffer_.data()), &flatbuffer_.t,
&flatbuffer_start_};
}
// Offset from the start of the buffer to the actual start of the flatbuffer
// (identical to the root offset of the flatbuffer).
size_t flatbuffer_start_;
internal::TableMover<T> flatbuffer_;
};
} // namespace aos::fbs
#endif // AOS_FLATBUFFERS_BUILDER_H_