Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 1 | use std::{ |
| 2 | marker::PhantomData, |
| 3 | mem::ManuallyDrop, |
| 4 | ops::{Deref, DerefMut}, |
| 5 | pin::Pin, |
| 6 | ptr, |
| 7 | }; |
| 8 | |
| 9 | use autocxx::WithinBox; |
| 10 | use cxx::UniquePtr; |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 11 | |
| 12 | pub use aos_configuration::{Channel, Configuration, ConfigurationExt, Node}; |
| 13 | use aos_configuration_fbs::aos::Configuration as RustConfiguration; |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 14 | use aos_events_event_loop_runtime::EventLoopRuntime; |
Adam Snaider | 163800b | 2023-07-12 00:21:17 -0400 | [diff] [blame] | 15 | pub use aos_events_event_loop_runtime::{CppExitHandle, EventLoop, ExitHandle}; |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 16 | use aos_flatbuffers::{transmute_table_to, Flatbuffer}; |
| 17 | |
| 18 | autocxx::include_cpp! ( |
| 19 | #include "aos/events/simulated_event_loop.h" |
| 20 | #include "aos/events/simulated_event_loop_for_rust.h" |
| 21 | |
| 22 | safety!(unsafe) |
| 23 | |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 24 | generate!("aos::SimulatedEventLoopFactoryForRust") |
| 25 | |
Adam Snaider | 163800b | 2023-07-12 00:21:17 -0400 | [diff] [blame] | 26 | extern_cpp_type!("aos::ExitHandle", crate::CppExitHandle) |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 27 | extern_cpp_type!("aos::Configuration", crate::Configuration) |
| 28 | extern_cpp_type!("aos::Node", crate::Node) |
| 29 | extern_cpp_type!("aos::EventLoop", crate::EventLoop) |
| 30 | ); |
| 31 | |
| 32 | /// A Rust-owned C++ `SimulatedEventLoopFactory` object. |
| 33 | /// |
| 34 | /// Owning one of these via a heap allocation makes things a lot simpler, and all the functions |
| 35 | /// that are called repeatedly are heavyweight enough this is not a performance concern. |
| 36 | /// |
| 37 | /// We don't want to own the `SimulatedEventLoopFactory` directly because C++ maintains multiple |
| 38 | /// pointers to it, so we can't create Rust mutable references to it. |
| 39 | pub struct SimulatedEventLoopFactory<'config> { |
| 40 | // SAFETY: This stores a pointer to the configuration, whose lifetime is `'config`. |
| 41 | event_loop_factory: Pin<Box<ffi::aos::SimulatedEventLoopFactoryForRust>>, |
| 42 | // This represents the config pointer C++ is storing. |
| 43 | _marker: PhantomData<&'config Configuration>, |
| 44 | } |
| 45 | |
| 46 | impl<'config> SimulatedEventLoopFactory<'config> { |
| 47 | pub fn new<'new_config: 'config>( |
| 48 | config: &'new_config impl Flatbuffer<RustConfiguration<'static>>, |
| 49 | ) -> Self { |
| 50 | // SAFETY: `_marker` represents the lifetime of this pointer we're handing off to C++ to |
| 51 | // store. |
| 52 | let event_loop_factory = unsafe { |
| 53 | ffi::aos::SimulatedEventLoopFactoryForRust::new(transmute_table_to::<Configuration>( |
| 54 | &config.message()._tab, |
| 55 | )) |
| 56 | } |
| 57 | .within_box(); |
| 58 | Self { |
| 59 | event_loop_factory, |
| 60 | _marker: PhantomData, |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | fn as_mut(&mut self) -> Pin<&mut ffi::aos::SimulatedEventLoopFactoryForRust> { |
| 65 | self.event_loop_factory.as_mut() |
| 66 | } |
| 67 | |
| 68 | /// Creates a Rust-owned EventLoop. |
| 69 | /// |
| 70 | /// You probably don't want to call this directly if you're creating a Rust application. This |
| 71 | /// is intended for creating C++ applications. Use [`make_runtime`] instead when creating Rust |
| 72 | /// applications. |
| 73 | pub fn make_event_loop(&mut self, name: &str, node: Option<&Node>) -> UniquePtr<EventLoop> { |
| 74 | // SAFETY: |
| 75 | // * `self` has a valid C++ object. |
| 76 | // * C++ doesn't need the lifetimes of `name` or `node` to last any longer than this method |
| 77 | // call. |
| 78 | // * The return value is `'static` because it's wrapped in `unique_ptr`. |
| 79 | // |
| 80 | // Note that dropping `self` before the return value will abort from C++, but this is still |
| 81 | // sound. |
| 82 | unsafe { |
| 83 | self.as_mut() |
| 84 | .MakeEventLoop(name, node.map_or(ptr::null(), |p| p)) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying EventLoop. |
| 89 | pub fn make_runtime(&mut self, name: &str, node: Option<&Node>) -> SimulatedEventLoopRuntime { |
| 90 | SimulatedEventLoopRuntime::new(self.make_event_loop(name, node)) |
| 91 | } |
| 92 | |
| 93 | pub fn make_exit_handle(&mut self) -> ExitHandle { |
Adam Snaider | 163800b | 2023-07-12 00:21:17 -0400 | [diff] [blame] | 94 | self.as_mut().MakeExitHandle().into() |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 95 | } |
| 96 | |
| 97 | pub fn run(&mut self) { |
| 98 | self.as_mut().Run(); |
| 99 | } |
| 100 | |
| 101 | // TODO(Brian): Expose OnStartup. Just take a callback for creating things, and rely on |
| 102 | // dropping the created objects instead of OnShutdown. |
| 103 | // pub fn spawn_on_startup(&mut self, spawner: impl FnMut()); |
| 104 | } |
| 105 | |
Brian Silverman | 2a60707 | 2022-07-23 16:12:23 -0700 | [diff] [blame] | 106 | pub struct SimulatedEventLoopRuntime(ManuallyDrop<EventLoopRuntime<'static>>); |
| 107 | |
| 108 | impl SimulatedEventLoopRuntime { |
| 109 | pub fn new(event_loop: UniquePtr<EventLoop>) -> Self { |
| 110 | // SAFETY: We own the underlying EventLoop, so `'static` is the correct lifetime. Anything |
| 111 | // using this `EventLoopRuntime` will need to borrow the object we're returning, which will |
| 112 | // ensure it stays alive. |
| 113 | let runtime = unsafe { EventLoopRuntime::<'static>::new(event_loop.into_raw()) }; |
| 114 | Self(ManuallyDrop::new(runtime)) |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | impl Drop for SimulatedEventLoopRuntime { |
| 119 | fn drop(&mut self) { |
| 120 | let event_loop = self.raw_event_loop(); |
| 121 | // SAFETY: We're not going to touch this field again. |
| 122 | unsafe { ManuallyDrop::drop(&mut self.0) }; |
| 123 | // SAFETY: `new` created this from `into_raw`. We just dropped the only Rust reference to |
| 124 | // it. |
| 125 | unsafe { UniquePtr::from_raw(event_loop) }; |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | impl Deref for SimulatedEventLoopRuntime { |
| 130 | type Target = EventLoopRuntime<'static>; |
| 131 | fn deref(&self) -> &Self::Target { |
| 132 | &self.0 |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | impl DerefMut for SimulatedEventLoopRuntime { |
| 137 | fn deref_mut(&mut self) -> &mut Self::Target { |
| 138 | &mut self.0 |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | #[cfg(test)] |
| 143 | mod tests { |
| 144 | use super::*; |
| 145 | |
| 146 | use std::cell::RefCell; |
| 147 | |
| 148 | use futures::future::pending; |
| 149 | use runfiles::Runfiles; |
| 150 | |
| 151 | use aos_configuration::read_config_from; |
| 152 | use aos_init::test_init; |
| 153 | use ping_rust_fbs::aos::examples::PingBuilder; |
| 154 | |
| 155 | // A really basic test of the functionality here. |
| 156 | #[test] |
| 157 | fn smoke_test() { |
| 158 | #[derive(Debug, Default)] |
| 159 | struct GlobalState { |
| 160 | watcher_count: u32, |
| 161 | startup_count: u32, |
| 162 | } |
| 163 | |
| 164 | thread_local!(static GLOBAL_STATE: RefCell<GlobalState> = Default::default()); |
| 165 | |
| 166 | test_init(); |
| 167 | let r = Runfiles::create().unwrap(); |
| 168 | let config = read_config_from( |
| 169 | &r.rlocation("org_frc971/aos/events/multinode_pingpong_test_combined_config.json"), |
| 170 | ) |
| 171 | .unwrap(); |
| 172 | let mut event_loop_factory = SimulatedEventLoopFactory::new(&config); |
| 173 | { |
| 174 | let pi1 = Some(config.message().get_node("pi1").unwrap()); |
| 175 | let mut runtime1 = event_loop_factory.make_runtime("runtime1", pi1); |
| 176 | let channel = runtime1 |
| 177 | .configuration() |
| 178 | .get_channel("/test", "aos.examples.Ping", "test", pi1) |
| 179 | .unwrap(); |
| 180 | let mut runtime2 = event_loop_factory.make_runtime("runtime2", pi1); |
| 181 | |
| 182 | { |
| 183 | let mut watcher = runtime1.make_raw_watcher(channel); |
| 184 | let exit_handle = event_loop_factory.make_exit_handle(); |
| 185 | runtime1.spawn(async move { |
| 186 | watcher.next().await; |
| 187 | GLOBAL_STATE.with(|g| { |
| 188 | let g = &mut *g.borrow_mut(); |
| 189 | g.watcher_count = g.watcher_count + 1; |
| 190 | }); |
| 191 | exit_handle.exit().await |
| 192 | }); |
| 193 | } |
| 194 | |
| 195 | { |
| 196 | let mut sender = runtime2.make_raw_sender(channel); |
| 197 | runtime2.spawn(async move { |
| 198 | GLOBAL_STATE.with(|g| { |
| 199 | let g = &mut *g.borrow_mut(); |
| 200 | g.startup_count = g.startup_count + 1; |
| 201 | }); |
| 202 | |
| 203 | let mut builder = sender.make_builder(); |
| 204 | let ping = PingBuilder::new(builder.fbb()).finish(); |
| 205 | // SAFETY: We're using the correct message type. |
| 206 | unsafe { builder.send(ping) }.expect("send should succeed"); |
| 207 | pending().await |
| 208 | }); |
| 209 | } |
| 210 | |
| 211 | GLOBAL_STATE.with(|g| { |
| 212 | let g = g.borrow(); |
| 213 | assert_eq!(0, g.watcher_count); |
| 214 | // TODO(Brian): Use an OnRun wrapper to defer setting this until it actually starts, |
| 215 | // then check it. |
| 216 | //assert_eq!(0, g.startup_count); |
| 217 | }); |
| 218 | event_loop_factory.run(); |
| 219 | GLOBAL_STATE.with(|g| { |
| 220 | let g = g.borrow(); |
| 221 | assert_eq!(1, g.watcher_count); |
| 222 | assert_eq!(1, g.startup_count); |
| 223 | }); |
| 224 | } |
| 225 | } |
| 226 | } |