From 99ae5b9eda49e11c50ebf9e437e12f596e5e89a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Sun, 23 Aug 2026 15:15:48 +0200 Subject: [PATCH] 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 --- ...0004-emscripten-cooperative-scheduling.patch | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/patches/pinmame/0004-emscripten-cooperative-scheduling.patch b/patches/pinmame/0004-emscripten-cooperative-scheduling.patch index 9cdbf13..eb371f0 100644 --- a/patches/pinmame/0004-emscripten-cooperative-scheduling.patch +++ b/patches/pinmame/0004-emscripten-cooperative-scheduling.patch @@ -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)