Zemanzo / web-marbles

Aims to recreate Marble Racing from the ground up, as a fully web based game
https://playmarbl.es/
GNU General Public License v3.0
23 stars 12 forks source link

Level refuses to load sometimes #224

Closed Zemanzo closed 5 years ago

Zemanzo commented 5 years ago

Expected behavior When loading the client, the level should load after a brief moment of downloading and parsing.

Actual behavior When loading the client, the level sometimes does not load at all. The marbles are synced without a problem, but the environment never shows up.

Reproduction steps

  1. Go to /client.
  2. Wait for the level to load.
  3. It may or may not load.

Platform OS: Windows 10 Browser version: Firefox Developer Edition 68.0b10 (64-bit) Build version: v1.2.0

Additional context Happens both locally and remotely. The console does log the level ID that will be loaded. A refresh fixes the issue.

Sanquinary commented 5 years ago

Seems to be a problem with Firefox only. (just tested on v67.0.1 and experienced the same) After some testing it seems like it has something to do with hardware acceleration not being turned on by default(?)

https://user-media-prod-cdn.itsre-sumo.mozilla.net/uploads/gallery/images/2017-08-27-03-46-52-fd0019.png

Not experienced this on Chrome.

Platform OS: Windows 10 Browser version: Google Chrome Version 74.0.3729.169 (Official Build) (64-bit) Build version: v1.2.0

Qaomen commented 5 years ago

This bug's been kicking me in the shins for a good amount of hours but I think I found the cause. Findings below for those interested:

After a lot of testing (and getting unlucky), I found out that Firefox automatically terminates the worker thread that is used to load the level data. Since it only happens if it's spawned/referenced in local scope like it is now, I'm guessing it's garbage collecting. (This doesn't completely check out since the worker is referenced inside a Promise, but maybe it's still considered unreachable in some way, idk)

It also turns out that because a lot of data processing happens within a Promise chain in the Worker (meaning it's not processed during the onmessage event), it causes Firefox to sometimes terminate the thread while it is still resolving the Promises! As a result, the worker thread never sends a message back and the Promise on the main thread is never resolved or rejected.

To make the bug easily reproducable, add some slowdown code (like the one below) right before or inside levelIO.load() and keep an eye on the threads in the debugger. The worker thread usually disappears after the first message is logged, but before the second one ever shows up. It doesn't seem to happen if you add this code outside of the .thens (aka making it part of the onmessage event), which is why I think the Promise chain plays a role as well.

console.log("Doin' slow stuff!");
let ew = 0;
for(let i = 0; i < 1000000000; i++) {
    ew += Math.sqrt(i * 0.01) % 2;
}
console.log(`Done with the lag: ${ew}`);

Chrome doesn't seem to garbage collect spawned threads, even if you spawn one and intentionally lose the reference to it, which is why the bug didn't happen there. (We're also not cleaning these threads up, it seems!)

This is what I can gather from it at least, can't say I'm exactly experienced with the garbage collecting process. Either way, a fix should be coming out soon.

Zemanzo commented 5 years ago

Damn, that's actually incredible. Awesome that you managed to figure it out! Good to have it documented too.