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