From 44504c718dcb8f174eebfeeabb70a18f179adcdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Mon, 24 Aug 2026 10:27:35 +0200 Subject: [PATCH] Fix eject hanging the tab on ROM-based tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump @valknar/vpinball-wasm to 0.3.3, which fixes a genuine infinite busy-wait in the PinMAME plugin's Controller::Stop() under Emscripten (no OS thread ever clears the flag it spins on) — this fired from dispose()'s synchronous script Exit event on any ROM-based table, hanging the tab permanently. Sequence unmount cleanup as stop() then two animation frames then dispose(), instead of dispose() alone, matching the consumer pattern the engine fix expects (stop() only takes effect on its next internal step, so disposing immediately after can still race it). Co-Authored-By: Claude Sonnet 5 --- components/hud/CoinDoorHud.tsx | 7 ++----- lib/pinball/usePinballInstance.ts | 14 +++++++++++++- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- pnpm-workspace.yaml | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/components/hud/CoinDoorHud.tsx b/components/hud/CoinDoorHud.tsx index 1565c2e..c7c3f49 100644 --- a/components/hud/CoinDoorHud.tsx +++ b/components/hud/CoinDoorHud.tsx @@ -64,11 +64,8 @@ export default function CoinDoorHud({ instanceRef, tableInfo, loadTimeMs }: Coin }; const handleEject = () => { - // dispose() (called by usePinballInstance's unmount cleanup, triggered by - // this navigation) tears the instance down immediately. Calling stop() - // here too raced it — stop() defers teardown to the engine's next - // internal step, and dispose() freeing WASM memory before that step runs - // hung the tab. + // usePinballInstance's unmount cleanup (triggered by this navigation) + // handles stop()+dispose() itself, sequenced to avoid hanging the tab. router.push("/"); }; diff --git a/lib/pinball/usePinballInstance.ts b/lib/pinball/usePinballInstance.ts index d5eee96..182f17e 100644 --- a/lib/pinball/usePinballInstance.ts +++ b/lib/pinball/usePinballInstance.ts @@ -104,8 +104,20 @@ export function usePinballInstance( return () => { disposedRef.current = true; - instanceRef.current?.dispose(); + const instance = instanceRef.current; instanceRef.current = null; + if (instance) { + // stop() halts the engine's render loop, but only takes effect on + // its next internal step rather than immediately. Calling dispose() + // synchronously right after — freeing WASM memory before that step + // runs — is what hangs the tab. Give it a couple of frames first. + instance.stop(); + requestAnimationFrame(() => { + requestAnimationFrame(() => { + instance.dispose(); + }); + }); + } }; // Intentionally run once: PlayView mounts a fresh PinballCanvasImpl // (via `key`) per table rather than swapping tableData in place. diff --git a/package.json b/package.json index f3b4e1e..5e95305 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint": "eslint" }, "dependencies": { - "@valknar/vpinball-wasm": "0.3.2", + "@valknar/vpinball-wasm": "0.3.3", "next": "16.3.2", "react": "19.2.8", "react-dom": "19.2.8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 583f582..745a054 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@valknar/vpinball-wasm': - specifier: 0.3.2 - version: 0.3.2 + specifier: 0.3.3 + version: 0.3.3 next: specifier: 16.3.2 version: 16.3.2(@babel/core@7.29.7(supports-color@7.2.0))(@types/node@20.19.43)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -754,8 +754,8 @@ packages: cpu: [x64] os: [win32] - '@valknar/vpinball-wasm@0.3.2': - resolution: {integrity: sha512-52vkjVzev92khiO8a50N2afYIviX1hHCMzTilN24Pe/7pu5m9LlSQVS3o11rRdItSF5h1RVmTLpoh1ENYrLQJw==, tarball: https://dev.pivoine.art/api/packages/valknar/npm/%40valknar%2Fvpinball-wasm/-/0.3.2/vpinball-wasm-0.3.2.tgz} + '@valknar/vpinball-wasm@0.3.3': + resolution: {integrity: sha512-It2VO8FrmBa7u+aom4m+JCWaVv6ZOhRn0x8BqqrFbGo2ptTb64G8SgqRXuGU8GldhIvgl3/ug+PsSg3EhETZpg==, tarball: https://dev.pivoine.art/api/packages/valknar/npm/%40valknar%2Fvpinball-wasm/-/0.3.3/vpinball-wasm-0.3.3.tgz} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -2645,7 +2645,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@valknar/vpinball-wasm@0.3.2': {} + '@valknar/vpinball-wasm@0.3.3': {} acorn-jsx@5.3.2(acorn@8.18.0): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ebfed2f..e3bb47e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,5 +2,5 @@ allowBuilds: sharp: false unrs-resolver: false minimumReleaseAgeExclude: - - '@valknar/vpinball-wasm@0.2.0 || 0.3.1 || 0.3.2' + - '@valknar/vpinball-wasm@0.2.0 || 0.3.1 || 0.3.2 || 0.3.3' updateNotifier: false