Skip to content

Commit

Permalink
Add the ability to request a redraw from an external source (#12197)
Browse files Browse the repository at this point in the history
Hi, this is a minimal implementation of #12159. I wasn't sure if the
`EventLoopProxy` should be wrapped somewhat to make it more explicit.

# Objective

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(());
    });
}
```

## Solution

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.

## Changelog

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

---------

Co-authored-by: Kellner, Robin <Robin.Kellner@vector.com>
  • Loading branch information
R081n and RobinKellnerVector authored Mar 4, 2024
1 parent b1a5aab commit d90cb57
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 8 deletions.
24 changes: 18 additions & 6 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,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"))]
Expand Down Expand Up @@ -243,6 +243,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
Expand All @@ -255,7 +264,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
Expand All @@ -281,7 +290,7 @@ pub fn winit_runner(mut app: App) {
SystemState::<CreateWindowParams<Added<Window>>>::from_world(&mut app.world);
let mut winit_events = Vec::default();
// 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,
Expand Down Expand Up @@ -318,8 +327,8 @@ fn handle_winit_event(
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<&Window>)>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
winit_events: &mut Vec<WinitEvent>,
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();
Expand Down Expand Up @@ -698,6 +707,9 @@ fn handle_winit_event(
event_loop.set_control_flow(ControlFlow::Wait);
}
}
Event::UserEvent(RequestRedraw) => {
runner_state.redraw_requested = true;
}
_ => (),
}

Expand All @@ -712,7 +724,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>,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_winit/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,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,
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_winit/src/winit_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
///
Expand All @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit d90cb57

Please sign in to comment.