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