Add the ability to request a redraw from an external source (#12197)

Hi, this is a minimal implementation of #12159. I wasn't sure if the
`EventLoopProxy` should be wrapped somewhat to make it more explicit.

Minimal implementation of #12159
When using `UpdateMode::Reactive` it is currently not possible to
request a redraw when a long running task is finished or an external
source has new data.

This makes the following possible which will then run an app update once

``` rust
// EventLoopProxy is Send on most architectures
// The EventLoopProxy can also be saved in a thread local for WASM or a static in other architecturecs
pub fn example(proxy: NonSend<EventLoopProxy<()>>) {
    let clone: EventLoopProxy<()> = proxy.clone();
    thread::spawn(move || {
        // do long work
        clone.send_event(());
    });
}
```

By using the EventLoopProxy one can manually send events from external
threads to the event loop as `UserEvent`s.
This simply sets redraw_requested when a `UserEvent` is received.

- Added the ability to request a redraw from an external source

---------

Co-authored-by: Kellner, Robin <Robin.Kellner@vector.com>
This commit is contained in:
Robin Kellner 2024-03-04 18:17:17 +01:00 committed by François
parent f4df8b8545
commit db9d84fb2f
4 changed files with 24 additions and 8 deletions

View file

@ -81,7 +81,7 @@ pub struct WinitPlugin {
impl Plugin for WinitPlugin {
fn build(&self, app: &mut App) {
let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event();
let mut event_loop_builder = EventLoopBuilder::<UserEvent>::with_user_event();
// linux check is needed because x11 might be enabled on other platforms.
#[cfg(all(target_os = "linux", feature = "x11"))]
@ -240,6 +240,15 @@ type CreateWindowParams<'w, 's, F = ()> = (
Res<'w, AccessibilityRequested>,
);
/// The [`winit::event_loop::EventLoopProxy`] with the specific [`winit::event::Event::UserEvent`] used in the [`winit_runner`].
///
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
///
/// Use `NonSend<EventLoopProxy>` to receive this resource.
pub type EventLoopProxy = winit::event_loop::EventLoopProxy<UserEvent>;
type UserEvent = RequestRedraw;
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
///
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
@ -252,7 +261,7 @@ pub fn winit_runner(mut app: App) {
let event_loop = app
.world
.remove_non_send_resource::<EventLoop<()>>()
.remove_non_send_resource::<EventLoop<UserEvent>>()
.unwrap();
app.world
@ -277,7 +286,7 @@ pub fn winit_runner(mut app: App) {
let mut create_window =
SystemState::<CreateWindowParams<Added<Window>>>::from_world(&mut app.world);
// set up the event loop
let event_handler = move |event, event_loop: &EventLoopWindowTarget<()>| {
let event_handler = move |event, event_loop: &EventLoopWindowTarget<UserEvent>| {
handle_winit_event(
&mut app,
&mut app_exit_event_reader,
@ -312,8 +321,8 @@ fn handle_winit_event(
)>,
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<&Window>)>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
event: Event<()>,
event_loop: &EventLoopWindowTarget<()>,
event: Event<UserEvent>,
event_loop: &EventLoopWindowTarget<UserEvent>,
) {
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
@ -690,6 +699,9 @@ fn handle_winit_event(
event_loop.set_control_flow(ControlFlow::Wait);
}
}
Event::UserEvent(RequestRedraw) => {
runner_state.redraw_requested = true;
}
_ => (),
}
}
@ -698,7 +710,7 @@ fn run_app_update_if_should(
runner_state: &mut WinitAppRunnerState,
app: &mut App,
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<&Window>)>,
event_loop: &EventLoopWindowTarget<()>,
event_loop: &EventLoopWindowTarget<UserEvent>,
create_window: &mut SystemState<CreateWindowParams<Added<Window>>>,
app_exit_event_reader: &mut ManualEventReader<AppExit>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,

View file

@ -32,7 +32,7 @@ use crate::{
/// default values.
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_windows<F: QueryFilter + 'static>(
event_loop: &EventLoopWindowTarget<()>,
event_loop: &EventLoopWindowTarget<crate::UserEvent>,
(
mut commands,
mut created_windows,

View file

@ -28,6 +28,8 @@ impl WinitSettings {
///
/// [`Reactive`](UpdateMode::Reactive) if windows have focus,
/// [`ReactiveLowPower`](UpdateMode::ReactiveLowPower) otherwise.
///
/// Use the [`EventLoopProxy`](crate::EventLoopProxy) to request a redraw from outside bevy.
pub fn desktop_app() -> Self {
WinitSettings {
focused_mode: UpdateMode::Reactive {
@ -72,6 +74,7 @@ pub enum UpdateMode {
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
/// - new [window](`winit::event::WindowEvent`) or [raw input](`winit::event::DeviceEvent`)
/// events have appeared
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
Reactive {
/// The approximate time from the start of one update to the next.
///
@ -84,6 +87,7 @@ pub enum UpdateMode {
/// - `wait` time has elapsed since the previous update
/// - a redraw has been requested by [`RequestRedraw`](bevy_window::RequestRedraw)
/// - new [window events](`winit::event::WindowEvent`) have appeared
/// - a redraw has been requested with the [`EventLoopProxy`](crate::EventLoopProxy)
///
/// **Note:** Unlike [`Reactive`](`UpdateMode::Reactive`), this mode will ignore events that
/// don't come from interacting with a window, like [`MouseMotion`](winit::event::DeviceEvent::MouseMotion).

View file

@ -39,7 +39,7 @@ impl WinitWindows {
/// Creates a `winit` window and associates it with our entity.
pub fn create_window(
&mut self,
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
event_loop: &winit::event_loop::EventLoopWindowTarget<crate::UserEvent>,
entity: Entity,
window: &Window,
adapters: &mut AccessKitAdapters,