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