Wrap SimulatedEventLoop for Rust

Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I9fd543ae49ec4df4eb881591b7bed7ed9dc2c102
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 123a832..59d27ad 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -2,7 +2,7 @@
 load("//aos:flatbuffers.bzl", "cc_static_flatbuffer")
 load("//aos:config.bzl", "aos_config")
 load("//tools/build_rules:autocxx.bzl", "autocxx_library")
-load("@rules_rust//rust:defs.bzl", "rust_doc_test")
+load("@rules_rust//rust:defs.bzl", "rust_doc_test", "rust_test")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -477,6 +477,56 @@
     ],
 )
 
+cc_library(
+    name = "simulated_event_loop_for_rust",
+    hdrs = ["simulated_event_loop_for_rust.h"],
+    deps = [
+        ":simulated_event_loop",
+        "//aos:for_rust",
+        "//third_party/cargo:cxx_cc",
+    ],
+)
+
+autocxx_library(
+    name = "simulated_event_loop_rs",
+    srcs = ["simulated_event_loop.rs"],
+    crate_name = "aos_events_simulated_event_loop",
+    libs = [
+        ":simulated_event_loop",
+        ":simulated_event_loop_for_rust",
+    ],
+    override_cc_toolchain = "@llvm_toolchain//:cc-clang-x86_64-linux",
+    rs_deps = [
+        "@com_github_google_flatbuffers//rust",
+        "//aos:configuration_rust_fbs",
+        "//aos:flatbuffers_rs",
+        "//third_party/cargo:futures",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":event_loop_runtime",
+        "//aos:configuration_rs",
+        "//aos:uuid_rs",
+    ],
+)
+
+rust_test(
+    name = "simulated_event_loop_rs_test",
+    crate = ":simulated_event_loop_rs",
+    data = [
+        ":multinode_pingpong_test_combined_config",
+    ],
+    # TODO: Make Rust play happy with pic vs nopic. Details at:
+    # https://github.com/bazelbuild/rules_rust/issues/118
+    rustc_flags = ["-Crelocation-model=static"],
+    deps = [
+        ":ping_rust_fbs",
+        "//aos:init_rs",
+        "//third_party/cargo:futures",
+        "@rules_rust//tools/runfiles",
+    ],
+)
+
 cc_test(
     name = "event_scheduler_test",
     srcs = ["event_scheduler_test.cc"],
diff --git a/aos/events/simulated_event_loop.rs b/aos/events/simulated_event_loop.rs
new file mode 100644
index 0000000..06fcee8
--- /dev/null
+++ b/aos/events/simulated_event_loop.rs
@@ -0,0 +1,248 @@
+use std::{
+    marker::PhantomData,
+    mem::ManuallyDrop,
+    ops::{Deref, DerefMut},
+    pin::Pin,
+    ptr,
+};
+
+use autocxx::WithinBox;
+use cxx::UniquePtr;
+use futures::{future::pending, never::Never};
+
+pub use aos_configuration::{Channel, Configuration, ConfigurationExt, Node};
+use aos_configuration_fbs::aos::Configuration as RustConfiguration;
+pub use aos_events_event_loop_runtime::EventLoop;
+use aos_events_event_loop_runtime::EventLoopRuntime;
+use aos_flatbuffers::{transmute_table_to, Flatbuffer};
+
+autocxx::include_cpp! (
+#include "aos/events/simulated_event_loop.h"
+#include "aos/events/simulated_event_loop_for_rust.h"
+
+safety!(unsafe)
+
+generate!("aos::ExitHandle")
+generate!("aos::SimulatedEventLoopFactoryForRust")
+
+extern_cpp_type!("aos::Configuration", crate::Configuration)
+extern_cpp_type!("aos::Node", crate::Node)
+extern_cpp_type!("aos::EventLoop", crate::EventLoop)
+);
+
+/// A Rust-owned C++ `SimulatedEventLoopFactory` object.
+///
+/// Owning one of these via a heap allocation makes things a lot simpler, and all the functions
+/// that are called repeatedly are heavyweight enough this is not a performance concern.
+///
+/// We don't want to own the `SimulatedEventLoopFactory` directly because C++ maintains multiple
+/// pointers to it, so we can't create Rust mutable references to it.
+pub struct SimulatedEventLoopFactory<'config> {
+    // SAFETY: This stores a pointer to the configuration, whose lifetime is `'config`.
+    event_loop_factory: Pin<Box<ffi::aos::SimulatedEventLoopFactoryForRust>>,
+    // This represents the config pointer C++ is storing.
+    _marker: PhantomData<&'config Configuration>,
+}
+
+impl<'config> SimulatedEventLoopFactory<'config> {
+    pub fn new<'new_config: 'config>(
+        config: &'new_config impl Flatbuffer<RustConfiguration<'static>>,
+    ) -> Self {
+        // SAFETY: `_marker` represents the lifetime of this pointer we're handing off to C++ to
+        // store.
+        let event_loop_factory = unsafe {
+            ffi::aos::SimulatedEventLoopFactoryForRust::new(transmute_table_to::<Configuration>(
+                &config.message()._tab,
+            ))
+        }
+        .within_box();
+        Self {
+            event_loop_factory,
+            _marker: PhantomData,
+        }
+    }
+
+    fn as_mut(&mut self) -> Pin<&mut ffi::aos::SimulatedEventLoopFactoryForRust> {
+        self.event_loop_factory.as_mut()
+    }
+
+    /// Creates a Rust-owned EventLoop.
+    ///
+    /// You probably don't want to call this directly if you're creating a Rust application. This
+    /// is intended for creating C++ applications. Use [`make_runtime`] instead when creating Rust
+    /// applications.
+    pub fn make_event_loop(&mut self, name: &str, node: Option<&Node>) -> UniquePtr<EventLoop> {
+        // SAFETY:
+        // * `self` has a valid C++ object.
+        // * C++ doesn't need the lifetimes of `name` or `node` to last any longer than this method
+        //   call.
+        // * The return value is `'static` because it's wrapped in `unique_ptr`.
+        //
+        // Note that dropping `self` before the return value will abort from C++, but this is still
+        // sound.
+        unsafe {
+            self.as_mut()
+                .MakeEventLoop(name, node.map_or(ptr::null(), |p| p))
+        }
+    }
+
+    /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying EventLoop.
+    pub fn make_runtime(&mut self, name: &str, node: Option<&Node>) -> SimulatedEventLoopRuntime {
+        SimulatedEventLoopRuntime::new(self.make_event_loop(name, node))
+    }
+
+    pub fn make_exit_handle(&mut self) -> ExitHandle {
+        ExitHandle(self.as_mut().MakeExitHandle())
+    }
+
+    pub fn run(&mut self) {
+        self.as_mut().Run();
+    }
+
+    // TODO(Brian): Expose OnStartup. Just take a callback for creating things, and rely on
+    // dropping the created objects instead of OnShutdown.
+    // pub fn spawn_on_startup(&mut self, spawner: impl FnMut());
+}
+
+// TODO(Brian): Move this and the `generate!` somewhere else once we wrap ShmEventLoop, which also
+// uses it.
+pub struct ExitHandle(UniquePtr<ffi::aos::ExitHandle>);
+
+impl ExitHandle {
+    /// Exits the EventLoops represented by this handle. You probably want to immediately return
+    /// from the context this is called in. Awaiting [`exit`] instead of using this function is an
+    /// easy way to do that.
+    pub fn exit_sync(mut self) {
+        self.0.as_mut().unwrap().Exit();
+    }
+
+    /// Exits the EventLoops represented by this handle, and never returns. Immediately awaiting
+    /// this from a [`EventLoopRuntime::spawn`]ed task is usually what you want, it will ensure
+    /// that no more code from that task runs.
+    pub async fn exit(self) -> Never {
+        self.exit_sync();
+        pending().await
+    }
+}
+
+pub struct SimulatedEventLoopRuntime(ManuallyDrop<EventLoopRuntime<'static>>);
+
+impl SimulatedEventLoopRuntime {
+    pub fn new(event_loop: UniquePtr<EventLoop>) -> Self {
+        // SAFETY: We own the underlying EventLoop, so `'static` is the correct lifetime. Anything
+        // using this `EventLoopRuntime` will need to borrow the object we're returning, which will
+        // ensure it stays alive.
+        let runtime = unsafe { EventLoopRuntime::<'static>::new(event_loop.into_raw()) };
+        Self(ManuallyDrop::new(runtime))
+    }
+}
+
+impl Drop for SimulatedEventLoopRuntime {
+    fn drop(&mut self) {
+        let event_loop = self.raw_event_loop();
+        // SAFETY: We're not going to touch this field again.
+        unsafe { ManuallyDrop::drop(&mut self.0) };
+        // SAFETY: `new` created this from `into_raw`. We just dropped the only Rust reference to
+        // it.
+        unsafe { UniquePtr::from_raw(event_loop) };
+    }
+}
+
+impl Deref for SimulatedEventLoopRuntime {
+    type Target = EventLoopRuntime<'static>;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for SimulatedEventLoopRuntime {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use std::cell::RefCell;
+
+    use futures::future::pending;
+    use runfiles::Runfiles;
+
+    use aos_configuration::read_config_from;
+    use aos_init::test_init;
+    use ping_rust_fbs::aos::examples::PingBuilder;
+
+    // A really basic test of the functionality here.
+    #[test]
+    fn smoke_test() {
+        #[derive(Debug, Default)]
+        struct GlobalState {
+            watcher_count: u32,
+            startup_count: u32,
+        }
+
+        thread_local!(static GLOBAL_STATE: RefCell<GlobalState> = Default::default());
+
+        test_init();
+        let r = Runfiles::create().unwrap();
+        let config = read_config_from(
+            &r.rlocation("org_frc971/aos/events/multinode_pingpong_test_combined_config.json"),
+        )
+        .unwrap();
+        let mut event_loop_factory = SimulatedEventLoopFactory::new(&config);
+        {
+            let pi1 = Some(config.message().get_node("pi1").unwrap());
+            let mut runtime1 = event_loop_factory.make_runtime("runtime1", pi1);
+            let channel = runtime1
+                .configuration()
+                .get_channel("/test", "aos.examples.Ping", "test", pi1)
+                .unwrap();
+            let mut runtime2 = event_loop_factory.make_runtime("runtime2", pi1);
+
+            {
+                let mut watcher = runtime1.make_raw_watcher(channel);
+                let exit_handle = event_loop_factory.make_exit_handle();
+                runtime1.spawn(async move {
+                    watcher.next().await;
+                    GLOBAL_STATE.with(|g| {
+                        let g = &mut *g.borrow_mut();
+                        g.watcher_count = g.watcher_count + 1;
+                    });
+                    exit_handle.exit().await
+                });
+            }
+
+            {
+                let mut sender = runtime2.make_raw_sender(channel);
+                runtime2.spawn(async move {
+                    GLOBAL_STATE.with(|g| {
+                        let g = &mut *g.borrow_mut();
+                        g.startup_count = g.startup_count + 1;
+                    });
+
+                    let mut builder = sender.make_builder();
+                    let ping = PingBuilder::new(builder.fbb()).finish();
+                    // SAFETY: We're using the correct message type.
+                    unsafe { builder.send(ping) }.expect("send should succeed");
+                    pending().await
+                });
+            }
+
+            GLOBAL_STATE.with(|g| {
+                let g = g.borrow();
+                assert_eq!(0, g.watcher_count);
+                // TODO(Brian): Use an OnRun wrapper to defer setting this until it actually starts,
+                // then check it.
+                //assert_eq!(0, g.startup_count);
+            });
+            event_loop_factory.run();
+            GLOBAL_STATE.with(|g| {
+                let g = g.borrow();
+                assert_eq!(1, g.watcher_count);
+                assert_eq!(1, g.startup_count);
+            });
+        }
+    }
+}
diff --git a/aos/events/simulated_event_loop_for_rust.h b/aos/events/simulated_event_loop_for_rust.h
new file mode 100644
index 0000000..a8d630c
--- /dev/null
+++ b/aos/events/simulated_event_loop_for_rust.h
@@ -0,0 +1,33 @@
+#ifndef AOS_EVENTS_SIMULATED_EVENT_LOOP_FOR_RUST_H_
+#define AOS_EVENTS_SIMULATED_EVENT_LOOP_FOR_RUST_H_
+
+#include "aos/events/simulated_event_loop.h"
+#include "aos/for_rust.h"
+#include "cxx.h"
+
+namespace aos {
+
+class SimulatedEventLoopFactoryForRust {
+ public:
+  SimulatedEventLoopFactoryForRust(const Configuration *configuration)
+      : factory_(configuration) {}
+
+  std::unique_ptr<EventLoop> MakeEventLoop(rust::Str name, const Node *node) {
+    return factory_.MakeEventLoop(RustStrToStringView(name), node);
+  }
+
+  void Run() { factory_.Run(); }
+
+  std::unique_ptr<ExitHandle> MakeExitHandle() {
+    return factory_.MakeExitHandle();
+  }
+
+  const Configuration *configuration() { return factory_.configuration(); }
+
+ private:
+  SimulatedEventLoopFactory factory_;
+};
+
+}  // namespace aos
+
+#endif  // AOS_EVENTS_SIMULATED_EVENT_LOOP_FOR_RUST_H_