Create a "static" flatbuffer API

This provides a generated API for working with flatbuffer objects that
generates a statically determined layout for the flatbuffer and uses
that layout to construct the flatbuffer without needing to dynamically
allocate any memory. For situations where dynamic sizing is appropriate,
this API does allow for increasing the size of any vectors in the
flatbuffer objects.

This change includes a checked-in version of the generated code so that
reviewers for this and future changes can readily examine what the
generated code looks like.

Future tasks:
* Support for unions?
* Consider precomputing some constants for sizes/alignments rather than
  massive constant expressions.

Change-Id: I6bf72d6c722d5390ab2239289a8a2a4e118c8d47
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/flatbuffers/builder.h b/aos/flatbuffers/builder.h
new file mode 100644
index 0000000..b556ed3
--- /dev/null
+++ b/aos/flatbuffers/builder.h
@@ -0,0 +1,120 @@
+#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::kUnalignedBufferSize;
+  Builder(Allocator *allocator)
+      : ResizeableObject(
+            allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kNo),
+            allocator),
+        flatbuffer_start_(BufferStart(buffer_)),
+        flatbuffer_(internal::GetSubSpan(buffer_, flatbuffer_start_, T::kSize),
+                    this) {
+    SetPrefix();
+  }
+  Builder(std::unique_ptr<Allocator> allocator)
+      : ResizeableObject(
+            allocator->AllocateOrDie(kBufferSize, T::kAlign, SetZero::kNo),
+            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()};
+  }
+
+  // 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; }
+
+ private:
+  size_t Alignment() const override { return flatbuffer_.t.Alignment(); }
+  size_t AbsoluteOffsetOffset() const override { return 0; }
+  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_;
+  }
+  // Because the allocator API doesn't provide a way for us to request a
+  // strictly aligned buffer, manually align the start of the actual flatbuffer
+  // data if needed.
+  static size_t BufferStart(std::span<uint8_t> buffer) {
+    return aos::fbs::PaddedSize(
+               reinterpret_cast<size_t>(buffer.data()) + sizeof(uoffset_t),
+               T::kAlign) -
+           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_