Support using static flatbuffers in EventLoop API

This makes it so that you can use the MakeStaticBuilder() method instead
of the MakeBuilder() method to use the new flatbuffer API. Because of
how the templating works out, we do not (for better or for worse) need
to create a new Sender type.

Change-Id: I2b94f809acdb80a6bed60e356fd17365aa54a243
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/events/BUILD b/aos/events/BUILD
index c398e7b..d18f621 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -1,5 +1,6 @@
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library", "flatbuffer_rust_library")
 load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
+load("//aos/flatbuffers:generate.bzl", "static_flatbuffer")
 load("//aos:flatbuffers.bzl", "cc_static_flatbuffer")
 load("//aos:config.bzl", "aos_config")
 load("//tools/build_rules:autocxx.bzl", "autocxx_library")
@@ -7,17 +8,15 @@
 
 package(default_visibility = ["//visibility:public"])
 
-flatbuffer_cc_library(
+static_flatbuffer(
     name = "test_message_fbs",
-    srcs = ["test_message.fbs"],
-    gen_reflections = 1,
-    target_compatible_with = ["@platforms//os:linux"],
+    src = "test_message.fbs",
 )
 
 cc_static_flatbuffer(
     name = "test_message_schema",
     function = "aos::TestMessageSchema",
-    target = ":test_message_fbs_reflection_out",
+    target = ":test_message_fbs_fbs_reflection_out",
 )
 
 flatbuffer_cc_library(
@@ -132,6 +131,7 @@
         "//aos:ftrace",
         "//aos:realtime",
         "//aos:uuid",
+        "//aos/flatbuffers:builder",
         "//aos/ipc_lib:data_alignment",
         "//aos/logging",
         "//aos/time",
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index f57053f..d0e3f2a 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -20,6 +20,7 @@
 #include "aos/events/event_loop_generated.h"
 #include "aos/events/timing_statistics.h"
 #include "aos/flatbuffers.h"
+#include "aos/flatbuffers/builder.h"
 #include "aos/ftrace.h"
 #include "aos/ipc_lib/data_alignment.h"
 #include "aos/json_to_flatbuffer.h"
@@ -183,9 +184,21 @@
   // Returns the associated flatbuffers-style allocator. This must be
   // deallocated before the message is sent.
   ChannelPreallocatedAllocator *fbb_allocator() {
-    fbb_allocator_ = ChannelPreallocatedAllocator(
-        reinterpret_cast<uint8_t *>(data()), size(), channel());
-    return &fbb_allocator_;
+    CHECK(!static_allocator_.has_value())
+        << ": May not mix-and-match static and raw flatbuffer builders.";
+    if (fbb_allocator_.has_value()) {
+      CHECK(!fbb_allocator_.value().allocated())
+          << ": May not have multiple active allocators on a single sender.";
+    }
+    return &fbb_allocator_.emplace(reinterpret_cast<uint8_t *>(data()), size(),
+                                   channel());
+  }
+
+  fbs::SpanAllocator *static_allocator() {
+    CHECK(!fbb_allocator_.has_value())
+        << ": May not mix-and-match static and raw flatbuffer builders.";
+    return &static_allocator_.emplace(
+        std::span<uint8_t>{reinterpret_cast<uint8_t *>(data()), size()});
   }
 
   // Index of the buffer which is currently exposed by data() and the various
@@ -228,7 +241,11 @@
   internal::RawSenderTiming timing_;
   Ftrace ftrace_;
 
-  ChannelPreallocatedAllocator fbb_allocator_{nullptr, 0, nullptr};
+  // Depending on which API is being used, we will populate either
+  // fbb_allocator_ (for use with FlatBufferBuilders) or the SpanAllocator (for
+  // use with the static flatbuffer API).
+  std::optional<ChannelPreallocatedAllocator> fbb_allocator_;
+  std::optional<fbs::SpanAllocator> static_allocator_;
 };
 
 // Needed for compatibility with glog
@@ -325,11 +342,54 @@
 };
 
 // Sends messages to a channel.
+// The type T used with the Sender may either be a raw flatbuffer type (e.g.,
+// aos::examples::Ping) or the static flatbuffer type (e.g.
+// aos::examples::PingStatic). The Builder type that you use must correspond
+// with the flatbuffer type being used.
 template <typename T>
 class Sender {
  public:
   Sender() {}
 
+  // Represents a single message that is about to be sent on the channel.
+  // Uses the static flatbuffer API rather than the FlatBufferBuilder paradigm.
+  //
+  // Typical usage pattern is:
+  //
+  // Sender<PingStatic>::Builder builder = sender.MakeStaticBuilder()
+  // builder.get()->set_value(971);
+  // builder.CheckOk(builder.Send());
+  class StaticBuilder {
+   public:
+    StaticBuilder(RawSender *sender, fbs::SpanAllocator *allocator)
+        : builder_(allocator), sender_(CHECK_NOTNULL(sender)) {}
+    StaticBuilder(const StaticBuilder &) = delete;
+    StaticBuilder(StaticBuilder &&) = default;
+
+    StaticBuilder &operator=(const StaticBuilder &) = delete;
+    StaticBuilder &operator=(StaticBuilder &&) = default;
+
+    fbs::Builder<T> *builder() {
+      DCHECK(builder_.has_value());
+      return &builder_.value();
+    }
+
+    T *get() { return builder()->get(); }
+
+    RawSender::Error Send() {
+      const auto err = sender_->Send(builder_.value().buffer().size());
+      builder_.reset();
+      return err;
+    }
+
+    // Equivalent to RawSender::CheckOk
+    void CheckOk(const RawSender::Error err) { sender_->CheckOk(err); };
+
+   private:
+    std::optional<fbs::Builder<T>> builder_;
+    RawSender *sender_;
+  };
+
   // Represents a single message about to be sent to the queue.
   // The lifecycle goes:
   //
@@ -399,6 +459,9 @@
   // assigning a default-constructed Builder to it) before calling this method
   // again to overwrite the value in the variable.
   Builder MakeBuilder();
+  StaticBuilder MakeStaticBuilder() {
+    return StaticBuilder(sender_.get(), sender_->static_allocator());
+  }
 
   // Sends a prebuilt flatbuffer.
   // This will copy the data out of the provided flatbuffer, and so does not
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index c861284..bfce034 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -9,6 +9,7 @@
 #include "gtest/gtest.h"
 
 #include "aos/events/test_message_generated.h"
+#include "aos/events/test_message_static.h"
 #include "aos/flatbuffer_merge.h"
 #include "aos/logging/log_message_generated.h"
 #include "aos/logging/logging.h"
@@ -101,6 +102,38 @@
   EXPECT_TRUE(happened);
 }
 
+// Tests that watcher can receive messages from a static sender.
+// This confirms that the "static" flatbuffer API works with the EventLoop
+// senders.
+TEST_P(AbstractEventLoopTest, BasicStatic) {
+  auto loop1 = Make();
+  auto loop2 = MakePrimary();
+
+  aos::Sender<TestMessageStatic> sender =
+      loop1->MakeSender<TestMessageStatic>("/test");
+
+  bool happened = false;
+
+  loop2->OnRun([&]() {
+    happened = true;
+
+    aos::Sender<TestMessageStatic>::StaticBuilder msg =
+        sender.MakeStaticBuilder();
+    msg.get()->set_value(200);
+    CHECK(msg.builder()->Verify());
+    msg.CheckOk(msg.Send());
+  });
+
+  loop2->MakeWatcher("/test", [&](const TestMessage &message) {
+    EXPECT_EQ(message.value(), 200);
+    this->Exit();
+  });
+
+  EXPECT_FALSE(happened);
+  Run();
+  EXPECT_TRUE(happened);
+}
+
 // Tests that watcher can receive messages from a sender, sent via SendDetached.
 TEST_P(AbstractEventLoopTest, BasicSendDetached) {
   auto loop1 = Make();
@@ -3445,7 +3478,7 @@
     builder.MakeBuilder<TestMessage>().Finish();
     // But not a second one.
     EXPECT_DEATH(sender.MakeBuilder().MakeBuilder<TestMessage>().Finish(),
-                 "May not overwrite in-use allocator");
+                 "May not have multiple active allocators");
   }
 
   FlatbufferDetachedBuffer<TestMessage> detached =
@@ -3457,7 +3490,7 @@
   {
     // This is the second one, after the detached one, so it should fail.
     EXPECT_DEATH(sender.MakeBuilder().MakeBuilder<TestMessage>().Finish(),
-                 "May not overwrite in-use allocator");
+                 "May not have multiple active allocators");
   }
 
   // Clear the detached one, and then we should be able to create another.