blob: 90c4c863762fc10833edb93166629ae25e9a80d9 [file] [log] [blame]
Brian Silverman2ee175e2023-07-11 16:32:08 -07001use std::{marker::PhantomData, pin::Pin, ptr};
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
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 Silverman2ee175e2023-07-11 16:32:08 -0700115pub struct SimulatedEventLoopHolder(UniquePtr<EventLoop>);
Brian Silverman2a607072022-07-23 16:12:23 -0700116
Brian Silverman2ee175e2023-07-11 16:32:08 -0700117impl 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 Silverman2a607072022-07-23 16:12:23 -0700121 }
122}
123
Brian Silverman2ee175e2023-07-11 16:32:08 -0700124// 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.
126unsafe impl EventLoopHolder for SimulatedEventLoopHolder {
127 fn into_raw(self) -> *mut ffi::aos::EventLoop {
128 self.0.into_raw()
Brian Silverman2a607072022-07-23 16:12:23 -0700129 }
Brian Silverman2a607072022-07-23 16:12:23 -0700130
Brian Silverman2ee175e2023-07-11 16:32:08 -0700131 unsafe fn from_raw(raw: *mut ffi::aos::EventLoop) -> Self {
132 Self(UniquePtr::from_raw(raw))
Brian Silverman2a607072022-07-23 16:12:23 -0700133 }
134}
135
136#[cfg(test)]
137mod 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 Snaiderc8b7e752023-09-14 14:27:53 -0700146 use aos_test_init::test_init;
Brian Silverman2a607072022-07-23 16:12:23 -0700147 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 Silverman2a607072022-07-23 16:12:23 -0700169
Brian Silverman2ee175e2023-07-11 16:32:08 -0700170 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 Silverman2a607072022-07-23 16:12:23 -0700186 });
Brian Silverman2ee175e2023-07-11 16:32:08 -0700187 })
188 };
Brian Silverman2a607072022-07-23 16:12:23 -0700189
Brian Silverman2ee175e2023-07-11 16:32:08 -0700190 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 Silverman2a607072022-07-23 16:12:23 -0700209 });
Brian Silverman2ee175e2023-07-11 16:32:08 -0700210 })
211 };
Brian Silverman2a607072022-07-23 16:12:23 -0700212
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}