blob: 0677efbb24e1d795ca893ec82e028f837f574b7b [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 Snaider163800b2023-07-12 00:21:17 -04008pub use aos_events_event_loop_runtime::{CppExitHandle, EventLoop, 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)
23extern_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.
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
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 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
Brian Silverman2ee175e2023-07-11 16:32:08 -070080 /// 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 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
Brian Silverman2ee175e2023-07-11 16:32:08 -0700124pub struct SimulatedEventLoopHolder(UniquePtr<EventLoop>);
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.
128 pub unsafe fn new(event_loop: UniquePtr<EventLoop>) -> Self {
129 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 {
136 fn into_raw(self) -> *mut ffi::aos::EventLoop {
137 self.0.into_raw()
Brian Silverman2a607072022-07-23 16:12:23 -0700138 }
Brian Silverman2a607072022-07-23 16:12:23 -0700139
Brian Silverman2ee175e2023-07-11 16:32:08 -0700140 unsafe fn from_raw(raw: *mut ffi::aos::EventLoop) -> Self {
141 Self(UniquePtr::from_raw(raw))
Brian Silverman2a607072022-07-23 16:12:23 -0700142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 use std::cell::RefCell;
150
151 use futures::future::pending;
152 use runfiles::Runfiles;
153
154 use aos_configuration::read_config_from;
Adam Snaiderc8b7e752023-09-14 14:27:53 -0700155 use aos_test_init::test_init;
Brian Silverman2a607072022-07-23 16:12:23 -0700156 use ping_rust_fbs::aos::examples::PingBuilder;
157
158 // A really basic test of the functionality here.
159 #[test]
160 fn smoke_test() {
161 #[derive(Debug, Default)]
162 struct GlobalState {
163 watcher_count: u32,
164 startup_count: u32,
165 }
166
167 thread_local!(static GLOBAL_STATE: RefCell<GlobalState> = Default::default());
168
169 test_init();
170 let r = Runfiles::create().unwrap();
171 let config = read_config_from(
172 &r.rlocation("org_frc971/aos/events/multinode_pingpong_test_combined_config.json"),
173 )
174 .unwrap();
175 let mut event_loop_factory = SimulatedEventLoopFactory::new(&config);
176 {
177 let pi1 = Some(config.message().get_node("pi1").unwrap());
Brian Silverman2a607072022-07-23 16:12:23 -0700178
Brian Silverman2ee175e2023-07-11 16:32:08 -0700179 let exit_handle = event_loop_factory.make_exit_handle();
180 let _runtime1 = {
181 let pi1_ref = &pi1;
182 event_loop_factory.make_runtime("runtime1", pi1, move |runtime1| {
183 assert!(pi1_ref.unwrap().has_name());
184 let channel = runtime1
185 .get_raw_channel("/test", "aos.examples.Ping")
186 .unwrap();
187 let mut watcher = runtime1.make_raw_watcher(channel);
188 runtime1.spawn(async move {
189 watcher.next().await;
190 GLOBAL_STATE.with(|g| {
191 let g = &mut *g.borrow_mut();
192 g.watcher_count = g.watcher_count + 1;
193 });
194 exit_handle.exit().await
Brian Silverman2a607072022-07-23 16:12:23 -0700195 });
Brian Silverman2ee175e2023-07-11 16:32:08 -0700196 })
197 };
Brian Silverman2a607072022-07-23 16:12:23 -0700198
Brian Silverman2ee175e2023-07-11 16:32:08 -0700199 let _runtime2 = {
200 let pi1_ref = &pi1;
201 event_loop_factory.make_runtime("runtime2", pi1, |runtime2| {
202 assert!(pi1_ref.unwrap().has_name());
203 let channel = runtime2
204 .get_raw_channel("/test", "aos.examples.Ping")
205 .unwrap();
206 let mut sender = runtime2.make_raw_sender(channel);
207 runtime2.spawn(async move {
208 GLOBAL_STATE.with(|g| {
209 let g = &mut *g.borrow_mut();
210 g.startup_count = g.startup_count + 1;
211 });
212
213 let mut builder = sender.make_builder();
214 let ping = PingBuilder::new(builder.fbb()).finish();
215 // SAFETY: We're using the correct message type.
216 unsafe { builder.send(ping) }.expect("send should succeed");
217 pending().await
Brian Silverman2a607072022-07-23 16:12:23 -0700218 });
Brian Silverman2ee175e2023-07-11 16:32:08 -0700219 })
220 };
Brian Silverman2a607072022-07-23 16:12:23 -0700221
222 GLOBAL_STATE.with(|g| {
223 let g = g.borrow();
224 assert_eq!(0, g.watcher_count);
225 // TODO(Brian): Use an OnRun wrapper to defer setting this until it actually starts,
226 // then check it.
227 //assert_eq!(0, g.startup_count);
228 });
229 event_loop_factory.run();
230 GLOBAL_STATE.with(|g| {
231 let g = g.borrow();
232 assert_eq!(1, g.watcher_count);
233 assert_eq!(1, g.startup_count);
234 });
235 }
236 }
237}