diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 7a606e3027e00..9e09dfba9ce52 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -71,6 +71,10 @@ pub fn render_system(world: &mut World) { for window in windows.values_mut() { if let Some(wrapped_texture) = window.swap_chain_texture.take() { if let Some(surface_texture) = wrapped_texture.try_unwrap() { + // TODO(clean): winit docs recommends calling pre_present_notify before this. + // though `present()` doesn't present the frame, it schedules it to be presented + // by wgpu. + // https://docs.rs/winit/0.29.9/wasm32-unknown-unknown/winit/window/struct.Window.html#method.pre_present_notify surface_texture.present(); } } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index ec38670e7f395..130acd18434d7 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -142,6 +142,7 @@ impl Plugin for WinitPlugin { let event_loop = event_loop_builder .build() .expect("Failed to build event loop"); + event_loop.set_control_flow(ControlFlow::Wait); // iOS, macOS, and Android don't like it if you create windows before the event loop is // initialized. @@ -665,15 +666,22 @@ pub fn winit_runner(mut app: App) { &mut app_exit_event_reader, &mut redraw_event_reader, ); + if runner_state.redraw_requested { + let (_, winit_windows, _, _) = + event_writer_system_state.get_mut(&mut app.world); + if let Some(window) = winit_windows.get_window(window_entity) { + window.request_redraw(); + } + } } _ => {} } let mut windows = app.world.query::<(&mut Window, &mut CachedWindow)>(); - let (window, mut cache) = windows.get_mut(&mut app.world, window_entity).unwrap(); - - if window.is_changed() { - cache.window = window.clone(); + if let Ok((window, mut cache)) = windows.get_mut(&mut app.world, window_entity) { + if window.is_changed() { + cache.window = window.clone(); + } } } Event::DeviceEvent { @@ -740,13 +748,7 @@ pub fn winit_runner(mut app: App) { app.world.entity_mut(entity).insert(wrapper); } - event_loop.set_control_flow(ControlFlow::Poll); - } - } - Event::AboutToWait => { - let (_, winit_windows, _, _) = event_writer_system_state.get_mut(&mut app.world); - for window in winit_windows.windows.values() { - window.request_redraw(); + event_loop.set_control_flow(ControlFlow::Wait); } } _ => (), @@ -754,6 +756,7 @@ pub fn winit_runner(mut app: App) { }; trace!("starting winit event loop"); + // TODO(clean): the winit docs mention using `spawn` instead of `run` on WASM. if let Err(err) = event_loop.run(event_handler) { error!("winit event loop returned an error: {err}"); } @@ -793,100 +796,105 @@ fn redraw_requested( app_exit_event_reader: &mut ManualEventReader, redraw_event_reader: &mut ManualEventReader, ) { - if runner_state.active.should_run() { - if runner_state.active == ActiveState::WillSuspend { - runner_state.active = ActiveState::Suspended; - #[cfg(target_os = "android")] - { - // Remove the `RawHandleWrapper` from the primary window. - // This will trigger the surface destruction. - let mut query = app.world.query_filtered::>(); - let entity = query.single(&app.world); - app.world.entity_mut(entity).remove::(); - event_loop.set_control_flow(ControlFlow::Wait); - } + if !runner_state.active.should_run() { + return; + } + if runner_state.active == ActiveState::WillSuspend { + runner_state.active = ActiveState::Suspended; + #[cfg(target_os = "android")] + { + // Remove the `RawHandleWrapper` from the primary window. + // This will trigger the surface destruction. + let mut query = app.world.query_filtered::>(); + let entity = query.single(&app.world); + app.world.entity_mut(entity).remove::(); + event_loop.set_control_flow(ControlFlow::Wait); } + } + let (config, windows) = focused_windows_state.get(&app.world); + let focused = windows.iter().any(|window| window.focused); + let should_update = match config.update_mode(focused) { + UpdateMode::Continuous | UpdateMode::Reactive { .. } => { + // `Reactive`: In order for `event_handler` to have been called, either + // we received a window or raw input event, the `wait` elapsed, or a + // redraw was requested (by the app or the OS). There are no other + // conditions, so we can just return `true` here. + true + } + UpdateMode::ReactiveLowPower { .. } => { + runner_state.wait_elapsed + || runner_state.redraw_requested + || runner_state.window_event_received + } + }; + + if app.plugins_state() == PluginsState::Cleaned && should_update { + // reset these on each update + runner_state.wait_elapsed = false; + runner_state.window_event_received = false; + runner_state.redraw_requested = false; + runner_state.last_update = Instant::now(); + + app.update(); + + // decide when to run the next update let (config, windows) = focused_windows_state.get(&app.world); let focused = windows.iter().any(|window| window.focused); - let should_update = match config.update_mode(focused) { - UpdateMode::Continuous | UpdateMode::Reactive { .. } => { - // `Reactive`: In order for `event_handler` to have been called, either - // we received a window or raw input event, the `wait` elapsed, or a - // redraw was requested (by the app or the OS). There are no other - // conditions, so we can just return `true` here. - true - } - UpdateMode::ReactiveLowPower { .. } => { - runner_state.wait_elapsed - || runner_state.redraw_requested - || runner_state.window_event_received + match config.update_mode(focused) { + UpdateMode::Continuous => { + runner_state.redraw_requested = true; } - }; - - if app.plugins_state() == PluginsState::Cleaned && should_update { - // reset these on each update - runner_state.wait_elapsed = false; - runner_state.window_event_received = false; - runner_state.redraw_requested = false; - runner_state.last_update = Instant::now(); - - app.update(); - - // decide when to run the next update - let (config, windows) = focused_windows_state.get(&app.world); - let focused = windows.iter().any(|window| window.focused); - match config.update_mode(focused) { - UpdateMode::Continuous => { - event_loop.set_control_flow(ControlFlow::Poll); - } - UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { wait } => { - if let Some(next) = runner_state.last_update.checked_add(*wait) { - runner_state.scheduled_update = Some(next); - event_loop.set_control_flow(ControlFlow::WaitUntil(next)); - } else { - runner_state.scheduled_update = None; - event_loop.set_control_flow(ControlFlow::Wait); - } + UpdateMode::Reactive { wait } | UpdateMode::ReactiveLowPower { wait } => { + // TODO(bug): this is unexpected behavior. + // When Reactive, user expects bevy to actually wait that amount of time, + // and not potentially infinitely depending on plateform specifics (which this does) + // Need to verify the plateform specifics (whether this can occur in + // rare-but-possible cases) and replace this with a panic or a log warn! + if let Some(next) = runner_state.last_update.checked_add(*wait) { + runner_state.scheduled_update = Some(next); + event_loop.set_control_flow(ControlFlow::WaitUntil(next)); + } else { + runner_state.scheduled_update = None; + event_loop.set_control_flow(ControlFlow::Wait); } } + } - if let Some(app_redraw_events) = app.world.get_resource::>() { - if redraw_event_reader.read(app_redraw_events).last().is_some() { - runner_state.redraw_requested = true; - event_loop.set_control_flow(ControlFlow::Poll); - } + if let Some(app_redraw_events) = app.world.get_resource::>() { + if redraw_event_reader.read(app_redraw_events).last().is_some() { + runner_state.redraw_requested = true; } + } - if let Some(app_exit_events) = app.world.get_resource::>() { - if app_exit_event_reader.read(app_exit_events).last().is_some() { - event_loop.exit(); - } + if let Some(app_exit_events) = app.world.get_resource::>() { + if app_exit_event_reader.read(app_exit_events).last().is_some() { + event_loop.exit(); } } - - // create any new windows - // (even if app did not update, some may have been created by plugin setup) - let ( - commands, - mut windows, - event_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - ) = create_window_system_state.get_mut(&mut app.world); - - create_windows( - event_loop, - commands, - windows.iter_mut(), - event_writer, - winit_windows, - adapters, - handlers, - accessibility_requested, - ); - - create_window_system_state.apply(&mut app.world); } + + // create any new windows + // (even if app did not update, some may have been created by plugin setup) + let ( + commands, + mut windows, + event_writer, + winit_windows, + adapters, + handlers, + accessibility_requested, + ) = create_window_system_state.get_mut(&mut app.world); + + create_windows( + event_loop, + commands, + windows.iter_mut(), + event_writer, + winit_windows, + adapters, + handlers, + accessibility_requested, + ); + + create_window_system_state.apply(&mut app.world); } diff --git a/crates/bevy_winit/src/winit_config.rs b/crates/bevy_winit/src/winit_config.rs index b91e25d340afa..ecece207d6b3d 100644 --- a/crates/bevy_winit/src/winit_config.rs +++ b/crates/bevy_winit/src/winit_config.rs @@ -76,7 +76,7 @@ pub enum UpdateMode { /// - new [window](`winit::event::WindowEvent`) or [raw input](`winit::event::DeviceEvent`) /// events have appeared Reactive { - /// The minimum time from the start of one update to the next. + /// The approximate time from the start of one update to the next. /// /// **Note:** This has no upper limit. /// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`]. @@ -93,7 +93,7 @@ pub enum UpdateMode { /// Use this mode if, for example, you only want your app to update when the mouse cursor is /// moving over a window, not just moving in general. This can greatly reduce power consumption. ReactiveLowPower { - /// The minimum time from the start of one update to the next. + /// The approximate time from the start of one update to the next. /// /// **Note:** This has no upper limit. /// The [`App`](bevy_app::App) will wait indefinitely if you set this to [`Duration::MAX`].