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>
This commit is contained in:
2026-08-23 15:15:48 +02:00
co-authored by Claude Sonnet 5
parent 5570955bc5
commit 99ae5b9eda
@@ -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)