2 Commits
Author SHA1 Message Date
valknarandClaude Sonnet 5 c7f7cb6afa Bump version to 0.3.1 for the focus-loss freeze fix
CI / Build wasm engine (push) Successful in 10m50s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 15:17:21 +02:00
valknarandClaude Sonnet 5 99ae5b9eda Fix PinMAME busy-wait freeze on browser/window focus loss
core.vbs auto-wires Controller.Pause = True into every table's Paused
event, which Player::OnFocusChanged() fires on focus loss. PinMAME's
own pause handling in usrintrf.c is a "while (g_fPause) { ...;
draw_screen(); ... }" busy-wait written for a real-OS-thread host,
where a separate thread later flips g_fPause back to 0. Under our
single-threaded Emscripten cooperative-scheduling model there is no
second thread to clear it, so entering that loop spins forever at
100% CPU with the tab completely unresponsive.

cpu_run_emscripten_step() now checks g_fPause up front and skips
cpu_timeslice() (and therefore that loop) entirely while paused;
emulation simply doesn't advance until Controller.Pause is set back
to False and the step function is called again next frame.

Confirmed fixed by hands-on testing: unfocusing the browser during a
PinMAME ROM session no longer freezes the tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-23 15:15:48 +02:00
3 changed files with 18 additions and 5 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@valknar/vpinball-wasm",
"version": "0.2.0",
"version": "0.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@valknar/vpinball-wasm",
"version": "0.2.0",
"version": "0.3.1",
"license": "SEE LICENSE IN LICENSE",
"devDependencies": {
"typescript": "^5.6.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@valknar/vpinball-wasm",
"version": "0.3.0",
"version": "0.3.1",
"description": "Visual Pinball's engine compiled to WebAssembly - real .vpx tables, real VBScript, WebGL2 rendering, in the browser",
"license": "SEE LICENSE IN LICENSE",
"type": "module",
@@ -1,8 +1,8 @@
diff --git a/src/cpuexec.c b/src/cpuexec.c
index 07db6a1..0bfcbe7 100644
index 07db6a1..19da31a 100644
--- a/src/cpuexec.c
+++ b/src/cpuexec.c
@@ -466,6 +466,65 @@ void cpu_run(void)
@@ -466,6 +466,78 @@ void cpu_run(void)
#endif
}
@@ -29,6 +29,19 @@ index 07db6a1..0bfcbe7 100644
+ once it has fully quit (cpu_post_run() has already been invoked). */
+int cpu_run_emscripten_step(void)
+{
+ /* usrintrf.c's per-frame video update contains a "while (g_fPause) {
+ ...; draw_screen(); ... }" busy-wait (see the VPINMAME/LIBPINMAME
+ branch around updatescreen()) that assumes a real OS thread will
+ later flip g_fPause back to 0 from outside it. We're single-threaded
+ here, so entering that loop at all would spin forever burning 100%
+ CPU with no way out - nothing else can ever run to clear the flag.
+ Skip cpu_timeslice() (and therefore that loop) entirely while paused;
+ the emulation simply doesn't advance until Controller.Pause is set
+ back to False and this function is called again next frame. */
+ extern int g_fPause;
+ if (g_fPause)
+ return 1;
+
+ const double frame_target = timer_get_time() + (1.0 / 60.0);
+
+ while (!time_to_quit && !time_to_reset && timer_get_time() < frame_target)