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