Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix perf degradation on web builds #11227

Merged
merged 4 commits into from
Jan 6, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 133 additions & 100 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ pub fn winit_runner(mut app: App) {
return;
};

let Ok((mut window, mut cache)) = windows.get_mut(window_entity) else {
let Ok((mut window, _)) = windows.get_mut(window_entity) else {
warn!(
"Window {:?} is missing `Window` component, skipping event {:?}",
window_entity, event
Expand Down Expand Up @@ -655,9 +655,23 @@ pub fn winit_runner(mut app: App) {
window: window_entity,
});
}
WindowEvent::RedrawRequested => {
redraw_requested(
&mut runner_state,
&mut app,
&mut focused_windows_state,
event_loop,
&mut create_window_system_state,
&mut app_exit_event_reader,
&mut redraw_event_reader,
);
}
_ => {}
}

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();
}
Expand Down Expand Up @@ -730,105 +744,9 @@ pub fn winit_runner(mut app: App) {
}
}
Event::AboutToWait => {
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::<Entity, With<PrimaryWindow>>();
let entity = query.single(&app.world);
app.world.entity_mut(entity).remove::<RawHandleWrapper>();
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);
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);
}
}
}

if let Some(app_redraw_events) =
app.world.get_resource::<Events<RequestRedraw>>()
{
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_exit_events) = app.world.get_resource::<Events<AppExit>>() {
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);
let (_, winit_windows, _, _) = event_writer_system_state.get_mut(&mut app.world);
for window in winit_windows.windows.values() {
window.request_redraw();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferably also call Window::request_redraw() in WindowEvent::RedrawRequested.

I would generally recommend not to use Event::AboutToWait at all unless you have a very specific use-case in mind.

}
}
_ => (),
Expand Down Expand Up @@ -857,3 +775,118 @@ fn react_to_resize(
height: window.height(),
});
}

fn redraw_requested(
runner_state: &mut WinitAppRunnerState,
app: &mut App,
focused_windows_state: &mut SystemState<(Res<WinitSettings>, Query<&Window>)>,
event_loop: &EventLoopWindowTarget<()>,
create_window_system_state: &mut SystemState<(
Commands,
Query<(Entity, &mut Window), Added<Window>>,
EventWriter<WindowCreated>,
NonSendMut<WinitWindows>,
NonSendMut<AccessKitAdapters>,
ResMut<WinitActionHandlers>,
ResMut<AccessibilityRequested>,
)>,
app_exit_event_reader: &mut ManualEventReader<AppExit>,
redraw_event_reader: &mut ManualEventReader<RequestRedraw>,
) {
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::<Entity, With<PrimaryWindow>>();
let entity = query.single(&app.world);
app.world.entity_mut(entity).remove::<RawHandleWrapper>();
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);
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);
}
}
}

if let Some(app_redraw_events) = app.world.get_resource::<Events<RequestRedraw>>() {
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_exit_events) = app.world.get_resource::<Events<AppExit>>() {
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);
}
}