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}