bevy icon indicating copy to clipboard operation
bevy copied to clipboard

fix: rewrite winit loop

Open pietrosophya opened this issue 11 months ago • 43 comments

Objective

  • Simplifies/clarifies the winit loop.
  • Fixes #12612.

Solution

The Winit loop runs following this flow:

  • NewEvents
  • Any number of other events, that can be 0, including RequestRedraw
  • AboutToWait

Bevy also uses the UpdateMode, to define how the next loop has to run. It can be essentially:

  • Continuous, using ControlFlow::Wait for windowed apps, and ControlFlow::Poll for windowless apps
  • Reactive/ReactiveLowPower, using ControlFlow::WaitUntil with a specific wait delay

The changes are made to follow this pattern, so that

  • NewEvents define if the WaitUntil has been canceled because we received a Winit event.
  • AboutToWait:
    • checks if the window has to be redrawn
    • otherwise calls app.update() if the WaitUntil timeout has elapsed
    • updates the ControlFlow accordingly

To make the code more logical:

  • AboutToWait checks if any Bevy's RequestRedraw event has been emitted
  • create_windows is run every cycle, at the beginning of the loop
  • the ActiveState (that could be renamed ActivityState) is updated in AboutToWait, symmetrically for WillSuspend/WillResume
  • the AppExit events are checked every loop cycle, to exit the app early

Platform-specific testing

  • [x] Windows
  • [x] MacOs
  • [x] Linux (x11)
  • [x] Linux (Wayland)
  • [x] Android
  • [x] iOS
  • [x] WASM/WebGL2 (Chrome)
  • [x] WASM/WebGL2 (Firefox)
  • [x] WASM/WebGL2 (Safari)
  • [x] WASM/WebGpu (Chrome)

pietrosophya avatar Mar 23 '24 16:03 pietrosophya

Does this also fix #12106?

alice-i-cecile avatar Mar 23 '24 16:03 alice-i-cecile

I don't have a Windows machine unfortunately.

I tested these successfully on MacOS:

  • the game_menu example (using the Quit button)
  • the ecs_guide example (windowless, it exits correctly when the game ends)
  • the low_power example (using both CTRL+C from the command line, and the window close button)
  • the desk_toy example (right click on the logo)

pietrosophya avatar Mar 23 '24 17:03 pietrosophya

I tested this on my M1 Macbook, both by using ci and by running random examples. It all seems to work!

BD103 avatar Mar 23 '24 21:03 BD103

@pietrosophya I've added a section on platform-specific testing to the PR description: please feel free to update the checklist as reviewer reports come in :)

alice-i-cecile avatar Mar 24 '24 14:03 alice-i-cecile

@alice-i-cecile thank you! I also tested WASM on both Chrome and Firefox, do I have to update the list or is it for reviewers only to check platforms off?

pietrosophya avatar Mar 24 '24 16:03 pietrosophya

Go ahead and check this off yourself :) I've also added Safari: I forgot that its web support is also different and weird.

alice-i-cecile avatar Mar 24 '24 22:03 alice-i-cecile

wasm/webgl2 support is mostly the same across browsers for the event loop, but wasm/webgpu needs to be tested too

mockersf avatar Mar 26 '24 08:03 mockersf

Tested on windows 11 with most of the window example. Seems to work fine.

hymm avatar Mar 29 '24 06:03 hymm

Tested every example relating to windows and a few other ones on Linux with X11 and XMonad, no issues found.

kristoff3r avatar Mar 29 '24 14:03 kristoff3r

Tested the window examples on Linux with Wayland and Sway, with the Wayland feature enabled and disabled: Only issue i noticed was transparent_window not working when the Wayland feature is enabled, but the issue is the same on main / 13.1, was also mentioned in #10929 already. Noticed no issues from this pr.

wolf-in-space avatar Mar 29 '24 16:03 wolf-in-space

WASM works on Safari for WebGL2 :)

I was unable to get WASM for WebGPU working on Chrome--it just gave me a black screen--but I might be doing something wrong. Could someone else please test this?

BD103 avatar Mar 29 '24 19:03 BD103

@BD103 I just updated the branch, a few days ago I fixed another bug on WebGPU that was causing the black screen. It works now.

pietrosophya avatar Mar 30 '24 08:03 pietrosophya

Solved latest main branch conflicts

pietrosophya avatar Apr 02 '24 13:04 pietrosophya

Android doesn't compile on this PR, https://github.com/Sophya/bevy/pull/1 should fix that

mockersf avatar Apr 02 '24 18:04 mockersf

on Windows, in CI, with this PR, it takes 6 minutes for the app to close after sending the event AppExit. This will likely break any attempt to merge because the job will timeout

mockersf avatar Apr 02 '24 22:04 mockersf

That's weird, since the loop is now super simple, and it checks for the AppExit event every time it runs the cycle. So, if Windows/CI takes that much to handle the AppExit event, I think the possible causes:

  1. the loop is not running (or it's stuck for a long time)
  2. the event is not sent
  3. event_loop.exit() doesn't stop the loop (so a winit bug?)

Do you see any other possible reason? Do you know which test is taking 6 mins to run?

pietrosophya avatar Apr 03 '24 08:04 pietrosophya

Do you see any other possible reason? Do you know which test is taking 6 mins to run?

I didn't check every example, but all the ones I checked took around 6 minutes to exit

Also, doesn't work on Android:

https://github.com/bevyengine/bevy/assets/8672791/30065e3b-42e0-4942-9e45-7378a18ed027 Device logs.txt

mockersf avatar Apr 03 '24 16:04 mockersf

I fixed the android window setup. It's not perfect, I think it's still redundant in the way it creates the window and/or the RawWindowHandle.

NB: the app doesn't work in android in the same way it doesn't in the main branch, so I think it's unrelated to this PR.

pietrosophya avatar Apr 04 '24 09:04 pietrosophya

Can anyone help me debug why Windows/CI takes that much to close the app? I have a Mac only and no Windows license near me...

pietrosophya avatar Apr 04 '24 09:04 pietrosophya

works for me on iOS and Android 👍

I don't have a Windows either, I usually use Bevy modified CI action to run things on Windows

mockersf avatar Apr 04 '24 19:04 mockersf

Tested on Windows 10. It seems to keep the Window open until I move my mouse, at which point it immediately exits.

msvbg avatar Apr 04 '24 21:04 msvbg

I investigated a bit the issue on discord, I'm no expert in the winit event loop so take with a grain of salt:

Patch for my suggestion on moving AppExit check

diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs
index 3be139185..161082464 100644
--- a/crates/bevy_winit/src/lib.rs
+++ b/crates/bevy_winit/src/lib.rs
@@ -354,13 +354,6 @@ fn handle_winit_event(
         runner_state.redraw_requested = true;
     }
 
-    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();
-            return;
-        }
-    }
-
     // create any new windows
     // (even if app did not update, some may have been created by plugin setup)
     create_windows(event_loop, create_window.get_mut(app.world_mut()));
@@ -785,6 +778,13 @@ fn handle_winit_event(
         _ => (),
     }
 
+    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();
+            return;
+        }
+    }
+
     // We drain events after every received winit event in addition to on app update to ensure
     // the work of pushing events into event queues is spread out over time in case the app becomes
     // dormant for a long stretch.

I'm not sure that's the correct way of doing it, but I hope sharing my observations is useful 🤞

Vrixyz avatar Apr 05 '24 09:04 Vrixyz

I applied this change, it makes some sense since we check for new AppExit events after a cycle has run. But, generally speaking, the cycle should run every time, at most after the WaitUntil timeout.

I checked on Windows 10 Pro, and I wasn't able to reproduce it (I tested clear_color, low_power, multiple_windows, game_menu). How do I run the CI version?

pietrosophya avatar Apr 07 '24 07:04 pietrosophya

run CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo run --example load_gltf --features "bevy_ci_testing" and check how fast it exits without any input

Vrixyz avatar Apr 07 '24 13:04 Vrixyz

It closed immediately after 300 frames on Windows 10 Pro... Any other test I can run here?

pietrosophya avatar Apr 07 '24 13:04 pietrosophya

I triggered a full example run on this PR, it seems only example window_settings is not finishing in time now

see https://github.com/TheBevyFlock/bevy-example-runner/actions/runs/8587214466/job/23530567606

mockersf avatar Apr 07 '24 16:04 mockersf

I think I fixed this: basically, Windows takes some time to create a window, and the loop is put in Poll mode. Then, it was requesting a redraw all the time and never running any App update.

pietrosophya avatar Apr 08 '24 10:04 pietrosophya

Does this introduce any breaking changes that would prevent it from being added to 0.13.3?

BD103 avatar Apr 09 '24 00:04 BD103

No, it just changes the event loop internals without touching any API.

pietrosophya avatar Apr 09 '24 05:04 pietrosophya

Would love to see this in a patch release

extrawurst avatar Apr 09 '24 07:04 extrawurst