Skip to content

winit::TabLeft/Event::Suspended doesn't function as intended on WASM #13486

Description

@nuzzles

Bevy version

0.13

What went wrong

JavaScript is an interpreted language, and the browser will essentially suspend code execution completely if the tab is hidden. Bevy's systems are tied to requestAnimationFrame, which is also completely paused when the user is tabbed away from the window.

In other words: On wasm, the main thread gets suspended when it is hidden (e.g. when the user switches tabs). This means that the app.update() function will not be called, because bevy's scheduler only runs app.update() when the browser's requestAnimationFrame is called (and that happens only when the tab is visible).

What problem does this cause?

  • No systems will execute whilst the tab is inactive. This means networking crates will disconnect easily (no communications being sent while alt-tabbed).
  • Networking crates that don't disconnect may experience buffer overflows for bounded protocols, as they will process hundreds of missed packets all at once. This could result in dropped packets.

There's a very long discussion on how this problem affects lightyear available here. We eventually come to a solution, which I'll propose below.

The part about winit::TabLeft

So yeah, there is technically events like Event::Suspended in winit that are supposed to fire when the tab gets left. However, as aforementioned, no code executes when the tab is hidden. As a result, this event is handled only when you return to the tab!

< user leaves tab >

... 30 seconds

< user goes back to the tab >

--> new event: Event::Suspended
--> new event: Event::Resumed

And what use is that? It's delivering no value currently. I'd call this a bug.

The solution

A solution, credited to @Nul-led , is to add a very small plugin (~10 lines of code) to spawn a web worker which sends a message every 1 second (configurable time). A callback is added to the web browser's DOM events to execute the main schedule exactly 1 time.

The code can be seen here: https://github.com/cBournhonesque/lightyear/pull/371/files#diff-5ec95d0ed493b4b63bba9ae693c990e6e1612d2fbe6309df678bf21fb75fbc1b

What I want

Selfishly, I want to see this in bevy. It's a very small amount of code but could be massively beneficial to many crates who want to keep a continuous run in the background when the tab is inactive.

Specifically, I think this should be a

pub struct WebExecutionPlugin {
    callback_ms: f32 // default = 250.0 ms
}

that can be added to any application.

Furthermore, it would be nice to have a

#[derive(States)]
pub enum WindowFocusState {
    Inactive,
    Active,
}

that changes, depending on whether the window or tab is visible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-WindowingPlatform-agnostic interface layer to run your app inC-BugAn unexpected or incorrect behaviorO-WebSpecific to web (WASM) buildsS-Needs-DesignThis issue requires design work to think about how it would best be accomplished

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions