Remove need for EventLoopProxy to be NonSend (#14198)

# Objective

- Continue to pare down the uses on NonSend resources in the engine. In
this case, EventLoopProxy used to be `!Sync`, but is now `Sync` in the
latest version of winit.

## Solution

- New type `EventLoopProxy` as `EventLoopProxyWrapper` to make it into a
normal resource.
- Update the `custom_user_event` example as it no longer needs to
indirectly access the `EventLoopProxy` through a static variable
anymore.

## Testing

- Ran the example. The resource exists just for users to use, so there
aren't any in engine uses for it currently.

---

## Changelog

- make EventLoopProxy into a regular resource. 

## Migration Guide

`EventLoopProxy` has been renamed to `EventLoopProxyWrapper` and is now
`Send`, making it an ordinary resource.

Before:
```rust
event_loop_system(event_loop: NonSend<EventLoopProxy<MyEvent>>) {
    event_loop.send_event(MyEvent);
}
```

After:
```rust
event_loop_system(event_loop: Res<EventLoopProxy<MyEvent>>) {
    event_loop.send_event(MyEvent);
}
```
This commit is contained in:
Mike 2024-07-15 23:59:01 -07:00 committed by GitHub
parent 1042f09c2e
commit cfcb56f5b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 35 deletions

View file

@ -12,6 +12,7 @@
//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
//! See `winit_runner` for details.
use bevy_derive::Deref;
use bevy_window::RawHandleWrapperHolder;
use std::marker::PhantomData;
use winit::event_loop::EventLoop;
@ -25,6 +26,7 @@ use bevy_ecs::prelude::*;
use bevy_window::{exit_on_all_closed, Window, WindowCreated};
pub use system::create_windows;
use system::{changed_windows, despawn_windows};
pub use winit::event_loop::EventLoopProxy;
pub use winit_config::*;
pub use winit_event::*;
pub use winit_windows::*;
@ -142,12 +144,14 @@ impl<T: Event> Plugin for WinitPlugin<T> {
#[derive(Debug, Default, Clone, Copy, Event)]
pub struct WakeUp;
/// A re-export of [`winit::event_loop::EventLoopProxy`].
/// A wrapper type around [`winit::event_loop::EventLoopProxy`] with the specific
/// [`winit::event::Event::UserEvent`] used in the [`WinitPlugin`].
///
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
///
/// Use `NonSend<EventLoopProxy>` to receive this resource.
pub type EventLoopProxy<T> = winit::event_loop::EventLoopProxy<T>;
/// Use `Res<EventLoopProxy>` to receive this resource.
#[derive(Resource, Deref)]
pub struct EventLoopProxyWrapper<T: 'static>(winit::event_loop::EventLoopProxy<T>);
trait AppSendEvent {
fn send(&mut self, event: impl Into<WinitEvent>);

View file

@ -37,8 +37,8 @@ use bevy_window::{PrimaryWindow, RawHandleWrapper};
use crate::accessibility::AccessKitAdapters;
use crate::system::CachedWindow;
use crate::{
converters, create_windows, AppSendEvent, CreateWindowParams, UpdateMode, WinitEvent,
WinitSettings, WinitWindows,
converters, create_windows, AppSendEvent, CreateWindowParams, EventLoopProxyWrapper,
UpdateMode, WinitEvent, WinitSettings, WinitWindows,
};
/// Persistent state that is used to run the [`App`] according to the current
@ -763,7 +763,7 @@ pub fn winit_runner<T: Event>(mut app: App) -> AppExit {
.unwrap();
app.world_mut()
.insert_non_send_resource(event_loop.create_proxy());
.insert_resource(EventLoopProxyWrapper(event_loop.create_proxy()));
let mut runner_state = WinitAppRunnerState::new(app);

View file

@ -1,9 +1,8 @@
//! Shows how to create a custom event that can be handled by `winit`'s event loop.
use bevy::prelude::*;
use bevy::winit::{EventLoopProxy, WakeUp, WinitPlugin};
use bevy::winit::{EventLoopProxyWrapper, WakeUp, WinitPlugin};
use std::fmt::Formatter;
use std::sync::OnceLock;
#[derive(Default, Debug, Event)]
enum CustomEvent {
@ -21,8 +20,6 @@ impl std::fmt::Display for CustomEvent {
}
}
static EVENT_LOOP_PROXY: OnceLock<EventLoopProxy<CustomEvent>> = OnceLock::new();
fn main() {
let winit_plugin = WinitPlugin::<CustomEvent>::default();
@ -39,7 +36,6 @@ fn main() {
Startup,
(
setup,
expose_event_loop_proxy,
#[cfg(target_arch = "wasm32")]
wasm::setup_js_closure,
),
@ -52,11 +48,10 @@ fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default());
}
fn send_event(input: Res<ButtonInput<KeyCode>>) {
let Some(event_loop_proxy) = EVENT_LOOP_PROXY.get() else {
return;
};
fn send_event(
input: Res<ButtonInput<KeyCode>>,
event_loop_proxy: Res<EventLoopProxyWrapper<CustomEvent>>,
) {
if input.just_pressed(KeyCode::Space) {
let _ = event_loop_proxy.send_event(CustomEvent::WakeUp);
}
@ -64,18 +59,15 @@ fn send_event(input: Res<ButtonInput<KeyCode>>) {
// This simulates sending a custom event through an external thread.
#[cfg(not(target_arch = "wasm32"))]
if input.just_pressed(KeyCode::KeyE) {
let handler = std::thread::spawn(|| {
let _ = event_loop_proxy.send_event(CustomEvent::Key('e'));
let event_loop_proxy = event_loop_proxy.clone();
let handler = std::thread::spawn(move || {
let _ = event_loop_proxy.clone().send_event(CustomEvent::Key('e'));
});
handler.join().unwrap();
}
}
fn expose_event_loop_proxy(event_loop_proxy: NonSend<EventLoopProxy<CustomEvent>>) {
EVENT_LOOP_PROXY.set((*event_loop_proxy).clone()).unwrap();
}
fn handle_event(mut events: EventReader<CustomEvent>) {
for evt in events.read() {
info!("Received event: {evt:?}");
@ -87,19 +79,21 @@ fn handle_event(mut events: EventReader<CustomEvent>) {
/// the loop if that's currently waiting for a timeout or a user event.
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm {
use crate::{CustomEvent, EVENT_LOOP_PROXY};
use super::*;
use bevy::winit::EventLoopProxy;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::KeyboardEvent;
pub(crate) fn setup_js_closure() {
pub(crate) fn setup_js_closure(event_loop: Res<EventLoopProxyWrapper<CustomEvent>>) {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let event_loop = event_loop.clone();
let closure = Closure::wrap(Box::new(move |event: KeyboardEvent| {
let key = event.key();
if key == "e" {
send_custom_event('e').unwrap();
send_custom_event('e', &event_loop).unwrap();
}
}) as Box<dyn FnMut(KeyboardEvent)>);
@ -110,13 +104,9 @@ pub(crate) mod wasm {
closure.forget();
}
fn send_custom_event(ch: char) -> Result<(), String> {
if let Some(proxy) = EVENT_LOOP_PROXY.get() {
proxy
.send_event(CustomEvent::Key(ch))
.map_err(|_| "Failed to send event".to_string())
} else {
Err("Event loop proxy not found".to_string())
}
fn send_custom_event(ch: char, proxy: &EventLoopProxy<CustomEvent>) -> Result<(), String> {
proxy
.send_event(CustomEvent::Key(ch))
.map_err(|_| "Failed to send event".to_string())
}
}

View file

@ -7,7 +7,7 @@ use bevy::{
prelude::*,
utils::Duration,
window::{PresentMode, RequestRedraw, WindowPlugin},
winit::{EventLoopProxy, WakeUp, WinitSettings},
winit::{EventLoopProxyWrapper, WakeUp, WinitSettings},
};
fn main() {
@ -55,7 +55,7 @@ enum ExampleMode {
fn update_winit(
mode: Res<ExampleMode>,
mut winit_config: ResMut<WinitSettings>,
event_loop_proxy: NonSend<EventLoopProxy<WakeUp>>,
event_loop_proxy: Res<EventLoopProxyWrapper<WakeUp>>,
mut redraw_request_events: EventWriter<RequestRedraw>,
) {
use ExampleMode::*;