bevy_extern_events/lib.rs
1//! # Why
2//!
3//! Because at some point you might want to interact with code outside of Bevy
4//! (External SDKs, Native Platform Code, non-Bevy crates).
5//! With the help of this crate you can queue events from anywhere and
6//! they will be available via the typical `EventReader` mechanism inside your Bevy Systems.
7//!
8//! **Note** that this comes at the cost of us having a global static `RwLock`-based Queue
9//! that we poll every frame (`PreUpdate`) to forward into an `EventWriter`.
10//! Events are Boxed because I found no other way of having a global static generic Datatype without using `Any`.
11//!
12//! Therefore I suggest using this for non-every-frame interaction and rolling a custom solution otherwise.
13//!
14//! # Example:
15//!
16//! ```
17//! use bevy::prelude::*;
18//! use bevy_extern_events::{queue_event, ExternEvent, ExternEventsPlugin};
19//!
20//! #[derive(Default)]
21//! pub struct MyEvent;
22//!
23//! #[derive(Resource, Reflect, Default)]
24//! pub struct MyEventResource(i32);
25//!
26//! pub fn event_system(
27//! mut res: ResMut<MyEventResource>,
28//! mut native_events: EventReader<ExternEvent<MyEvent>>,
29//! ) {
30//! for _e in native_events.read() {
31//! res.0 += 1;
32//! }
33//! }
34//!
35//! fn test() {
36//! let mut app = App::new();
37//! app.init_resource::<MyEventResource>()
38//! // register `ExternEventsPlugin` with our event type
39//! .add_plugins(ExternEventsPlugin::<MyEvent>::default())
40//! // register our system that will react to these events
41//! .add_systems(Update, event_system);
42//!
43//! // can be called any thread, from anywhere (for example c ffi)
44//! queue_event(MyEvent::default());
45//!
46//! // next pre-update will forward extern events to the bevy events system
47//! // this will trigger `event_system` of this example
48//! app.update();
49//!
50//! assert_eq!(app.world.resource::<MyEventResource>().0, 1);
51//! }
52//! ```
53use std::{marker::PhantomData, sync::Mutex};
54
55use bevy::prelude::*;
56use generic_global_variables::*;
57use once_cell::sync::OnceCell;
58
59/// wrapper from external events
60#[derive(Event, Default)]
61pub struct ExternEvent<T: Send + Sync + Default>(pub T);
62
63/// Bevy plugin for convenient proper installation.
64/// Registers the event and the polling systems.
65#[derive(Default)]
66pub struct ExternEventsPlugin<T>(PhantomData<T>);
67
68impl<T: Send + Sync + Default> Plugin for ExternEventsPlugin<T>
69where
70 ExternEvent<T>: Event,
71{
72 fn build(&self, app: &mut App) {
73 app.add_event::<ExternEvent<T>>()
74 .add_systems(PreUpdate, poll_events_system);
75 }
76}
77
78/// external entry point to queue events from anywhere from any thread
79pub fn queue_event<T: 'static + Send + Sync>(event: T) {
80 let arc = get_global(Mutex::<Vec<T>>::default);
81 arc.lock().unwrap().push(event);
82}
83
84/// solutionn to do generic global statics using `generic_global_variables`
85fn get_global<T: Send + Sync>(f: impl FnOnce() -> T) -> Entry<T> {
86 static GLOBALS: OnceCell<GenericGlobal> = OnceCell::new();
87
88 let globals = GLOBALS.get_or_init(GenericGlobal::new);
89 globals.get_or_init(f)
90}
91
92/// bevy system to run `PreUpdate` polling the event queue and pushing each event into the `EventWriter`
93fn poll_events_system<T: 'static + Send + Sync + Default>(mut writer: EventWriter<ExternEvent<T>>) {
94 let arc = get_global(Mutex::<Vec<T>>::default);
95 while let Some(e) = arc.lock().unwrap().pop() {
96 info!("poll event");
97 writer.send(ExternEvent(e));
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use bevy::prelude::*;
104
105 use crate::{queue_event, ExternEvent, ExternEventsPlugin};
106
107 #[derive(Default)]
108 pub struct MyEvent;
109
110 #[derive(Resource, Reflect, Default)]
111 pub struct MyEventResource(i32);
112
113 pub fn event_system(
114 mut res: ResMut<MyEventResource>,
115 mut native_events: EventReader<ExternEvent<MyEvent>>,
116 ) {
117 for _e in native_events.read() {
118 res.0 += 1;
119 }
120 }
121
122 #[test]
123 fn smoke() {
124 let mut app = App::new();
125 app.init_resource::<MyEventResource>()
126 .add_plugins(ExternEventsPlugin::<MyEvent>::default())
127 .add_systems(Update, event_system);
128
129 queue_event(MyEvent::default());
130
131 app.update();
132
133 assert_eq!(app.world.resource::<MyEventResource>().0, 1);
134
135 app.update();
136
137 // make sure no other event was forwarded
138 assert_eq!(app.world.resource::<MyEventResource>().0, 1);
139 }
140}