lumen_core/
plugin_events.rs1use std::any::Any;
21use std::sync::{Mutex, OnceLock};
22
23use crossbeam_channel::{Receiver, Sender, unbounded};
24
25pub enum QueuedEvent {
27 Bytes(Vec<u8>),
29 Value(Box<dyn Any + Send>),
32}
33
34impl QueuedEvent {
35 pub fn bytes(&self) -> Option<&[u8]> {
37 match self {
38 QueuedEvent::Bytes(bytes) => Some(bytes),
39 QueuedEvent::Value(_) => None,
40 }
41 }
42}
43
44impl std::fmt::Debug for QueuedEvent {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 QueuedEvent::Bytes(bytes) => f.debug_tuple("Bytes").field(bytes).finish(),
48 QueuedEvent::Value(_) => f.write_str("Value(..)"),
49 }
50 }
51}
52
53static PLUGIN_EVENT_TX: OnceLock<Sender<QueuedEvent>> = OnceLock::new();
54static PLUGIN_EVENT_RX: OnceLock<Mutex<Receiver<QueuedEvent>>> = OnceLock::new();
55static PLUGIN_EVENT_WAKER: Mutex<Option<crate::app::EventLoopWaker>> = Mutex::new(None);
56
57fn init_plugin_event_channel() -> &'static Sender<QueuedEvent> {
58 PLUGIN_EVENT_TX.get_or_init(|| {
59 let (tx, rx) = unbounded();
60 let _ = PLUGIN_EVENT_RX.set(Mutex::new(rx));
61 tx
62 })
63}
64
65pub fn init_plugin_events() {
68 let _ = init_plugin_event_channel();
69}
70
71pub fn set_plugin_event_waker(waker: crate::app::EventLoopWaker) {
78 if let Ok(mut slot) = PLUGIN_EVENT_WAKER.lock() {
79 *slot = Some(waker);
80 }
81}
82
83pub fn push_plugin_event(bytes: Vec<u8>) -> bool {
89 push(QueuedEvent::Bytes(bytes))
90}
91
92pub fn push_plugin_value(value: Box<dyn Any + Send>) -> bool {
99 push(QueuedEvent::Value(value))
100}
101
102fn push(event: QueuedEvent) -> bool {
103 let sent = init_plugin_event_channel().send(event).is_ok();
104 if sent
105 && let Ok(slot) = PLUGIN_EVENT_WAKER.lock()
106 && let Some(waker) = slot.as_ref()
107 {
108 waker.wake();
109 }
110 sent
111}
112
113pub fn drain_plugin_events() -> Vec<QueuedEvent> {
116 let Some(rx_lock) = PLUGIN_EVENT_RX.get() else {
117 return Vec::new();
118 };
119 let Ok(rx) = rx_lock.lock() else {
120 return Vec::new();
121 };
122 let mut events = Vec::new();
123 while let Ok(bytes) = rx.try_recv() {
124 events.push(bytes);
125 }
126 events
127}
128
129pub fn plugin_events_pending() -> bool {
135 PLUGIN_EVENT_RX
136 .get()
137 .and_then(|rx_lock| rx_lock.lock().ok().map(|rx| !rx.is_empty()))
138 .unwrap_or(false)
139}
140
141pub fn discard_plugin_events() {
147 let Some(rx_lock) = PLUGIN_EVENT_RX.get() else {
148 return;
149 };
150 let Ok(rx) = rx_lock.lock() else {
151 return;
152 };
153 while rx.try_recv().is_ok() {}
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 static SERIAL: Mutex<()> = Mutex::new(());
163
164 fn bytes_of(events: Vec<QueuedEvent>) -> Vec<Vec<u8>> {
166 events
167 .iter()
168 .filter_map(|event| event.bytes().map(<[u8]>::to_vec))
169 .collect()
170 }
171
172 #[test]
173 fn an_event_says_which_form_it_travels_in() {
174 let bytes = QueuedEvent::Bytes(vec![7, 8]);
175 let value = QueuedEvent::Value(Box::new(3_u32));
176 assert_eq!(bytes.bytes(), Some(&[7, 8][..]));
177 assert_eq!(value.bytes(), None);
178 assert_eq!(format!("{bytes:?}"), "Bytes([7, 8])");
179 assert_eq!(format!("{value:?}"), "Value(..)");
180 }
181
182 #[test]
183 fn a_value_travels_as_itself_beside_the_bytes() {
184 let _guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
185 discard_plugin_events();
186 assert!(push_plugin_event(vec![1]));
187 assert!(push_plugin_value(Box::new(String::from("typed"))));
188 assert!(plugin_events_pending());
189 let mut drained = drain_plugin_events().into_iter();
190 assert_eq!(
191 drained.next().and_then(|e| e.bytes().map(<[u8]>::to_vec)),
192 Some(vec![1])
193 );
194 match drained.next() {
195 Some(QueuedEvent::Value(value)) => {
196 assert_eq!(
197 value
198 .downcast::<String>()
199 .ok()
200 .as_deref()
201 .map(String::as_str),
202 Some("typed")
203 );
204 }
205 other => panic!("expected the value back, got {other:?}"),
206 }
207 assert!(drained.next().is_none());
208 }
209
210 #[test]
211 fn events_queue_report_pending_and_drain_in_order() {
212 let _guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
213 discard_plugin_events();
214 assert!(!plugin_events_pending());
215 assert!(push_plugin_event(vec![1]));
216 assert!(push_plugin_event(vec![2, 3]));
217 assert!(plugin_events_pending());
218 assert_eq!(bytes_of(drain_plugin_events()), vec![vec![1], vec![2, 3]]);
219 assert!(!plugin_events_pending());
220 assert!(drain_plugin_events().is_empty());
221 }
222
223 #[test]
224 fn discard_empties_the_bus() {
225 let _guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
226 discard_plugin_events();
227 assert!(push_plugin_event(vec![9]));
228 discard_plugin_events();
229 assert!(!plugin_events_pending());
230 assert!(drain_plugin_events().is_empty());
231 }
232
233 #[test]
234 fn a_push_wakes_a_parked_loop() {
235 let _guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
236 discard_plugin_events();
237 let parked = std::sync::Arc::new((Mutex::new(false), std::sync::Condvar::new()));
241 let waker_side = std::sync::Arc::clone(&parked);
242 set_plugin_event_waker(crate::app::EventLoopWaker(std::sync::Arc::new(move || {
243 let (flag, cv) = &*waker_side;
244 *flag.lock().unwrap_or_else(|e| e.into_inner()) = true;
245 cv.notify_all();
246 })));
247
248 let loop_side = std::sync::Arc::clone(&parked);
249 let parked_loop = std::thread::spawn(move || {
250 let (flag, cv) = &*loop_side;
251 let mut woken = flag.lock().unwrap_or_else(|e| e.into_inner());
252 while !*woken {
253 let (g, timed_out) = cv
254 .wait_timeout(woken, std::time::Duration::from_secs(10))
255 .unwrap_or_else(|e| e.into_inner());
256 woken = g;
257 assert!(!timed_out.timed_out(), "the push never woke the loop");
258 }
259 drain_plugin_events()
260 });
261
262 assert!(push_plugin_event(vec![5]));
263 let delivered = parked_loop.join().expect("parked loop");
264 assert_eq!(bytes_of(delivered), vec![vec![5]]);
265
266 if let Ok(mut slot) = PLUGIN_EVENT_WAKER.lock() {
268 *slot = None;
269 }
270 }
271
272 #[test]
273 fn an_undrained_event_counts_as_pending_work() {
274 let _guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner());
275 discard_plugin_events();
276 let world = bevy_ecs::world::World::new();
282 assert!(push_plugin_event(vec![7]));
283 assert!(
284 crate::tick::work_pending(&world),
285 "a queued plugin event must report pending so a parked driver schedules a tick"
286 );
287 discard_plugin_events();
288 assert!(!plugin_events_pending());
289 }
290}