Wrap SimulatedEventLoop for Rust
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I9fd543ae49ec4df4eb881591b7bed7ed9dc2c102
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);
+ });
+ }
+ }
+}