blob: c75f34e38c53510e805177292d6dbeef292ccf1e [file] [log] [blame]
Adam Snaider49c864c2023-10-09 18:32:48 -07001use std::{marker::PhantomData, pin::Pin, ptr, time::Duration};
Brian Silverman2a607072022-07-23 16:12:23 -07002
3use autocxx::WithinBox;
4use cxx::UniquePtr;
Brian Silverman2a607072022-07-23 16:12:23 -07005
6pub use aos_configuration::{Channel, Configuration, ConfigurationExt, Node};
7use aos_configuration_fbs::aos::Configuration as RustConfiguration;
Adam Snaider88097232023-10-17 18:43:14 -07008pub use aos_events_event_loop_runtime::{CppEventLoop, CppExitHandle, ExitHandle};
Brian Silverman2ee175e2023-07-11 16:32:08 -07009use aos_events_event_loop_runtime::{EventLoopHolder, EventLoopRuntime, EventLoopRuntimeHolder};
Brian Silverman2a607072022-07-23 16:12:23 -070010use aos_flatbuffers::{transmute_table_to, Flatbuffer};
11
12autocxx::include_cpp! (
13#include "aos/events/simulated_event_loop.h"
14#include "aos/events/simulated_event_loop_for_rust.h"
15
16safety!(unsafe)
17
Brian Silverman2a607072022-07-23 16:12:23 -070018generate!("aos::SimulatedEventLoopFactoryForRust")
19
Adam Snaider163800b2023-07-12 00:21:17 -040020extern_cpp_type!("aos::ExitHandle", crate::CppExitHandle)
Brian Silverman2a607072022-07-23 16:12:23 -070021extern_cpp_type!("aos::Configuration", crate::Configuration)
22extern_cpp_type!("aos::Node", crate::Node)
Adam Snaider88097232023-10-17 18:43:14 -070023extern_cpp_type!("aos::EventLoop", crate::CppEventLoop)
Brian Silverman2a607072022-07-23 16:12:23 -070024);
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.
33pub 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
40impl<'config> SimulatedEventLoopFactory<'config> {
Brian Silverman2ee175e2023-07-11 16:32:08 -070041 pub fn new(config: &'config impl Flatbuffer<RustConfiguration<'static>>) -> Self {
Brian Silverman2a607072022-07-23 16:12:23 -070042 // 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
Adam Snaiderb40b72f2023-11-02 19:40:55 -070063 /// is intended for creating C++ applications. Use [`Self::make_runtime`] instead when creating Rust
Brian Silverman2a607072022-07-23 16:12:23 -070064 /// applications.
Adam Snaider88097232023-10-17 18:43:14 -070065 pub fn make_event_loop(&mut self, name: &str, node: Option<&Node>) -> UniquePtr<CppEventLoop> {
Brian Silverman2a607072022-07-23 16:12:23 -070066 // 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 Silverman2ee175e2023-07-11 16:32:08 -070070 // * The return value manages its lifetime via `unique_ptr`.
Brian Silverman2a607072022-07-23 16:12:23 -070071 //
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
Adam Snaiderb40b72f2023-11-02 19:40:55 -070080 /// Creates an [`EventLoopRuntime`] wrapper which also owns its underlying [`CppEventLoop`].
Brian Silverman2ee175e2023-07-11 16:32:08 -070081 ///
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
Adam Snaidere4367cb2023-10-20 15:14:31 -040094 F: for<'event_loop> FnOnce(EventLoopRuntime<'event_loop>),
Brian Silverman2ee175e2023-07-11 16:32:08 -070095 {
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 Silverman2a607072022-07-23 16:12:23 -0700100 }
101
102 pub fn make_exit_handle(&mut self) -> ExitHandle {
Adam Snaider163800b2023-07-12 00:21:17 -0400103 self.as_mut().MakeExitHandle().into()
Brian Silverman2a607072022-07-23 16:12:23 -0700104 }
105
106 pub fn run(&mut self) {
107 self.as_mut().Run();
108 }
109
Adam Snaider49c864c2023-10-09 18:32:48 -0700110 pub fn run_for(&mut self, duration: Duration) {
111 self.as_mut().RunFor(
112 duration
113 .as_nanos()
114 .try_into()
115 .expect("Out of range: Internal clock uses 64 bits"),
116 );
117 }
118
Brian Silverman2a607072022-07-23 16:12:23 -0700119 // TODO(Brian): Expose OnStartup. Just take a callback for creating things, and rely on
120 // dropping the created objects instead of OnShutdown.
121 // pub fn spawn_on_startup(&mut self, spawner: impl FnMut());
122}
123
Adam Snaider88097232023-10-17 18:43:14 -0700124pub struct SimulatedEventLoopHolder(UniquePtr<CppEventLoop>);
Brian Silverman2a607072022-07-23 16:12:23 -0700125
Brian Silverman2ee175e2023-07-11 16:32:08 -0700126impl SimulatedEventLoopHolder {
127 /// SAFETY: `event_loop` must be the exclusive owner of the underlying EventLoop.
Adam Snaider88097232023-10-17 18:43:14 -0700128 pub unsafe fn new(event_loop: UniquePtr<CppEventLoop>) -> Self {
Brian Silverman2ee175e2023-07-11 16:32:08 -0700129 Self(event_loop)
Brian Silverman2a607072022-07-23 16:12:23 -0700130 }
131}
132
Brian Silverman2ee175e2023-07-11 16:32:08 -0700133// SAFETY: The UniquePtr functions we're using here mirror most of the EventLoopHolder requirements
134// exactly. Safety requirements on [`SimulatedEventLoopHolder.new`] take care of the rest.
135unsafe impl EventLoopHolder for SimulatedEventLoopHolder {
Adam Snaider014f88e2023-10-24 13:21:42 -0400136 fn as_raw(&self) -> *const CppEventLoop {
137 self.0
138 .as_ref()
139 .map(|event_loop| event_loop as *const CppEventLoop)
140 .unwrap_or(core::ptr::null())
Brian Silverman2a607072022-07-23 16:12:23 -0700141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 use std::cell::RefCell;
149
150 use futures::future::pending;
151 use runfiles::Runfiles;
152
153 use aos_configuration::read_config_from;
Adam Snaiderc8b7e752023-09-14 14:27:53 -0700154 use aos_test_init::test_init;
Brian Silverman2a607072022-07-23 16:12:23 -0700155 use ping_rust_fbs::aos::examples::PingBuilder;
156
157 // A really basic test of the functionality here.
158 #[test]
159 fn smoke_test() {
160 #[derive(Debug, Default)]
161 struct GlobalState {
162 watcher_count: u32,
163 startup_count: u32,
164 }
165
166 thread_local!(static GLOBAL_STATE: RefCell<GlobalState> = Default::default());
167
168 test_init();
169 let r = Runfiles::create().unwrap();
170 let config = read_config_from(
171 &r.rlocation("org_frc971/aos/events/multinode_pingpong_test_combined_config.json"),
172 )
173 .unwrap();
174 let mut event_loop_factory = SimulatedEventLoopFactory::new(&config);
175 {
176 let pi1 = Some(config.message().get_node("pi1").unwrap());
Brian Silverman2a607072022-07-23 16:12:23 -0700177
Brian Silverman2ee175e2023-07-11 16:32:08 -0700178 let exit_handle = event_loop_factory.make_exit_handle();
179 let _runtime1 = {
180 let pi1_ref = &pi1;
181 event_loop_factory.make_runtime("runtime1", pi1, move |runtime1| {
182 assert!(pi1_ref.unwrap().has_name());
183 let channel = runtime1
184 .get_raw_channel("/test", "aos.examples.Ping")
185 .unwrap();
186 let mut watcher = runtime1.make_raw_watcher(channel);
187 runtime1.spawn(async move {
188 watcher.next().await;
189 GLOBAL_STATE.with(|g| {
190 let g = &mut *g.borrow_mut();
191 g.watcher_count = g.watcher_count + 1;
192 });
193 exit_handle.exit().await
Brian Silverman2a607072022-07-23 16:12:23 -0700194 });
Brian Silverman2ee175e2023-07-11 16:32:08 -0700195 })
196 };
Brian Silverman2a607072022-07-23 16:12:23 -0700197
Brian Silverman2ee175e2023-07-11 16:32:08 -0700198 let _runtime2 = {
199 let pi1_ref = &pi1;
200 event_loop_factory.make_runtime("runtime2", pi1, |runtime2| {
201 assert!(pi1_ref.unwrap().has_name());
202 let channel = runtime2
203 .get_raw_channel("/test", "aos.examples.Ping")
204 .unwrap();
205 let mut sender = runtime2.make_raw_sender(channel);
206 runtime2.spawn(async move {
207 GLOBAL_STATE.with(|g| {
208 let g = &mut *g.borrow_mut();
209 g.startup_count = g.startup_count + 1;
210 });
211
212 let mut builder = sender.make_builder();
213 let ping = PingBuilder::new(builder.fbb()).finish();
214 // SAFETY: We're using the correct message type.
215 unsafe { builder.send(ping) }.expect("send should succeed");
216 pending().await
Brian Silverman2a607072022-07-23 16:12:23 -0700217 });
Brian Silverman2ee175e2023-07-11 16:32:08 -0700218 })
219 };
Brian Silverman2a607072022-07-23 16:12:23 -0700220
221 GLOBAL_STATE.with(|g| {
222 let g = g.borrow();
223 assert_eq!(0, g.watcher_count);
224 // TODO(Brian): Use an OnRun wrapper to defer setting this until it actually starts,
225 // then check it.
226 //assert_eq!(0, g.startup_count);
227 });
228 event_loop_factory.run();
229 GLOBAL_STATE.with(|g| {
230 let g = g.borrow();
231 assert_eq!(1, g.watcher_count);
232 assert_eq!(1, g.startup_count);
233 });
234 }
235 }
236}