Add fetcher-side utilities for constants code

This make it possible to use the ConstantsFetcher<> to actually get at
published constants easily.

Change-Id: Ib5a7b91ae69b91a221ecef36f1bdc117b89e9fc4
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/frc971/constants/BUILD b/frc971/constants/BUILD
index 891067c..c17dc31 100644
--- a/frc971/constants/BUILD
+++ b/frc971/constants/BUILD
@@ -4,10 +4,12 @@
         "constants_sender_lib.h",
     ],
     target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
     deps = [
         "//aos:flatbuffer_merge",
         "//aos:json_to_flatbuffer",
         "//aos/events:event_loop",
+        "//aos/events:shm_event_loop",
         "//aos/network:team_number",
         "@com_github_gflags_gflags//:gflags",
         "@com_github_google_glog//:glog",
diff --git a/frc971/constants/constants_sender_lib.h b/frc971/constants/constants_sender_lib.h
index 92c14fa..48cacb5 100644
--- a/frc971/constants/constants_sender_lib.h
+++ b/frc971/constants/constants_sender_lib.h
@@ -2,6 +2,7 @@
 #define FRC971_CONSTANTS_CONSTANTS_SENDER_H_
 
 #include "aos/events/event_loop.h"
+#include "aos/events/shm_event_loop.h"
 #include "aos/flatbuffer_merge.h"
 #include "aos/json_to_flatbuffer.h"
 #include "aos/network/team_number.h"
@@ -27,11 +28,9 @@
         constants_path_(constants_path),
         event_loop_(event_loop),
         sender_(event_loop_->MakeSender<ConstantsData>(channel_name_)) {
-    event_loop->OnRun([this]() {
-      typename aos::Sender<ConstantsData>::Builder builder =
-          sender_.MakeBuilder();
-      builder.CheckOk(builder.Send(GetConstantsForTeamNumber(builder.fbb())));
-    });
+    typename aos::Sender<ConstantsData>::Builder builder =
+        sender_.MakeBuilder();
+    builder.CheckOk(builder.Send(GetConstantsForTeamNumber(builder.fbb())));
   }
 
  private:
@@ -63,6 +62,56 @@
   aos::Sender<ConstantsData> sender_;
 };
 
+// This class fetches the current constants for the device, with appropriate
+// CHECKs to ensure that (a) the constants never change and (b) that the
+// constants are always available. This can be paired with WaitForConstants to
+// create the conditions for (b). In simulation, the constants should simply be
+// sent before starting up other EventLoops.
+template <typename ConstantsData>
+class ConstantsFetcher {
+ public:
+  ConstantsFetcher(aos::EventLoop *event_loop,
+                   std::string_view channel = "/constants")
+      : fetcher_(event_loop->MakeFetcher<ConstantsData>(channel)) {
+    CHECK(fetcher_.Fetch())
+        << "Constants information must be available at startup.";
+    event_loop->MakeNoArgWatcher<ConstantsData>(channel, []() {
+      LOG(FATAL)
+          << "Don't know how to handle changes to constants information.";
+    });
+  }
+
+  const ConstantsData& constants() const {
+    return *fetcher_.get();
+  }
+
+ private:
+  aos::Fetcher<ConstantsData> fetcher_;
+};
+
+// Blocks until data is available on the requested channel using a ShmEventLoop.
+// This is for use during initialization in C++ binaries so that we can delay
+// initialization until everything is available. This allows applications to
+// depend on constants data during their initialization.
+template <typename ConstantsData>
+void WaitForConstants(const aos::Configuration *config,
+                      std::string_view channel = "/constants") {
+  aos::ShmEventLoop event_loop(config);
+  aos::Fetcher fetcher = event_loop.MakeFetcher<ConstantsData>(channel);
+  event_loop.MakeNoArgWatcher<ConstantsData>(
+      channel, [&event_loop]() { event_loop.Exit(); });
+  event_loop.OnRun([&event_loop, &fetcher]() {
+    // If the constants were already published, we don't need to wait for them.
+    if (fetcher.Fetch()) {
+      event_loop.Exit();
+    }
+  });
+  LOG(INFO) << "Waiting for constants data on " << channel << " "
+            << ConstantsData::GetFullyQualifiedName();
+  event_loop.Run();
+  LOG(INFO) << "Got constants data.";
+}
+
 }  // namespace frc971::constants
 
 #endif  // FRC971_CONSTANTS_CONSTANTS_SENDER_H_
diff --git a/frc971/constants/constants_sender_test.cc b/frc971/constants/constants_sender_test.cc
index 3edc320..1441767 100644
--- a/frc971/constants/constants_sender_test.cc
+++ b/frc971/constants/constants_sender_test.cc
@@ -40,11 +40,10 @@
   ConstantSender<testdata::ConstantsData, testdata::ConstantsList> test971(
       constants_sender_event_loop_.get(),
       "frc971/constants/testdata/test_constants.json", "/constants");
-  test_event_loop->MakeWatcher("/constants",
-                               [](const testdata::ConstantsData &data) {
-                                 EXPECT_EQ(data.max_roller_voltage(), 12);
-                                 EXPECT_EQ(data.min_roller_voltage(), -12);
-                               });
+  ConstantsFetcher<testdata::ConstantsData> fetcher(test_event_loop.get());
+  EXPECT_EQ(fetcher.constants().max_roller_voltage(), 12);
+  EXPECT_EQ(fetcher.constants().min_roller_voltage(), -12);
+  // Ensure that the watcher in ConstantsFetcher never triggers.
   event_loop_factory_.RunFor(std::chrono::seconds(1));
 }
 
@@ -57,14 +56,42 @@
   ConstantSender<testdata::ConstantsData, testdata::ConstantsList> test971(
       constants_sender_event_loop_.get(),
       "frc971/constants/testdata/test_constants.json", 9971, "/constants");
-  test_event_loop->MakeWatcher("/constants",
-                               [](const testdata::ConstantsData &data) {
-                                 EXPECT_EQ(data.max_roller_voltage(), 6);
-                                 EXPECT_EQ(data.min_roller_voltage(), -6);
-                               });
+  ConstantsFetcher<testdata::ConstantsData> fetcher(test_event_loop.get());
+  EXPECT_EQ(fetcher.constants().max_roller_voltage(), 6);
+  EXPECT_EQ(fetcher.constants().min_roller_voltage(), -6);
   event_loop_factory_.RunFor(std::chrono::seconds(1));
 }
 
+// Tests that the ConstantsFetcher dies when there is no data available during
+// construction.
+TEST_F(ConstantSenderTest, NoDataOnStartup) {
+  std::unique_ptr<aos::EventLoop> test_event_loop =
+      event_loop_factory_.MakeEventLoop("constants");
+  EXPECT_DEATH(ConstantsFetcher<testdata::ConstantsData>(test_event_loop.get()),
+               "information must be available at startup");
+}
+
+// Tests that the ConstantsFetcher dies when there is a change to the constants
+// data.
+TEST_F(ConstantSenderTest, DieOnDataUpdate) {
+  std::unique_ptr<aos::EventLoop> test_event_loop =
+      event_loop_factory_.MakeEventLoop("constants");
+  ConstantSender<testdata::ConstantsData, testdata::ConstantsList> test971(
+      constants_sender_event_loop_.get(),
+      "frc971/constants/testdata/test_constants.json", 9971, "/constants");
+  ConstantsFetcher<testdata::ConstantsData> fetcher(test_event_loop.get());
+  auto sender =
+      constants_sender_event_loop_->MakeSender<testdata::ConstantsData>(
+          "/constants");
+  constants_sender_event_loop_->OnRun([&sender]() {
+      auto builder = sender.MakeBuilder();
+      builder.CheckOk(builder.Send(
+          builder.MakeBuilder<testdata::ConstantsData>().Finish()));
+      });
+  EXPECT_DEATH(event_loop_factory_.RunFor(std::chrono::seconds(1)),
+               "changes to constants");
+}
+
 // When given a team number that it not recognized we kill the program.
 
 TEST_F(ConstantSenderTest, TeamNotFound) {