From 418f792ef28063babb6e352128a413a0ba31e49b Mon Sep 17 00:00:00 2001 From: Corwin Date: Wed, 10 Apr 2024 23:24:44 +0100 Subject: [PATCH] get saving working --- website/agb/src/app/mgba/mgba.tsx | 89 ++++++++++++++----- .../agb/src/app/mgba/useController.hook.ts | 8 +- website/agb/src/app/mgba/useFrameSkip.hook.ts | 8 +- 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/website/agb/src/app/mgba/mgba.tsx b/website/agb/src/app/mgba/mgba.tsx index 974f5668..708323b0 100644 --- a/website/agb/src/app/mgba/mgba.tsx +++ b/website/agb/src/app/mgba/mgba.tsx @@ -10,12 +10,11 @@ import { GbaKey, KeyBindings } from "./bindings"; import { styled } from "styled-components"; import { useFrameSkip } from "./useFrameSkip.hook"; import { useController } from "./useController.hook"; - -type Module = any; +import { useLocalStorage } from "./useLocalStorage.hook"; interface MgbaProps { gameUrl: string; - volume?: Number; + volume?: number; controls: KeyBindings; paused: boolean; } @@ -55,14 +54,58 @@ async function downloadGame(gameUrl: string): Promise { } } +interface SaveGame { + [gameName: string]: number[]; +} + export const Mgba = forwardRef( ({ gameUrl, volume, controls, paused }, ref) => { const canvas = useRef(null); - const mgbaModule = useRef({} as mGBAEmulator); + const mgbaModule = useRef(); + + const [saveGame, setSaveGame] = useLocalStorage( + {}, + "agbrswebplayer/savegames" + ); const [state, setState] = useState(MgbaState.Uninitialised); const [gameLoaded, setGameLoaded] = useState(false); + useEffect(() => { + function beforeUnload() { + const gameSplit = gameUrl.split("/"); + const gameBaseName = gameSplit[gameSplit.length - 1]; + + const save = mgbaModule.current?.getSave(); + if (!save) return; + + setSaveGame({ + ...saveGame, + [gameBaseName]: [...save], + }); + } + + window.addEventListener("beforeunload", beforeUnload); + + return () => { + window.removeEventListener("beforeunload", beforeUnload); + }; + }, [gameUrl, saveGame, setSaveGame]); + + useEffect(() => { + if (state !== MgbaState.Initialised) return; + + const gameSplit = gameUrl.split("/"); + const gameBaseName = gameSplit[gameSplit.length - 1]; + + const save = saveGame[gameBaseName]; + if (!save) return; + + const savePath = `${MGBA_ROM_DIRECTORY}/${gameBaseName}.sav`; + + mgbaModule.current?.FS.writeFile(savePath, new Uint8Array([0, 1, 2, 3])); + }, [gameUrl, saveGame, state]); + useEffect(() => { if (state !== MgbaState.Initialised) return; (async () => { @@ -71,9 +114,9 @@ export const Mgba = forwardRef( const gameBaseName = gameSplit[gameSplit.length - 1]; const gamePath = `${MGBA_ROM_DIRECTORY}/${gameBaseName}`; - mgbaModule.current.FS.writeFile(gamePath, new Uint8Array(gameData)); - mgbaModule.current.loadGame(gamePath); - mgbaModule.current.setVolume(0.1); // for some reason you have to do this or you get no sound + mgbaModule.current?.FS.writeFile(gamePath, new Uint8Array(gameData)); + mgbaModule.current?.loadGame(gamePath); + mgbaModule.current?.setVolume(0.1); // for some reason you have to do this or you get no sound setGameLoaded(true); })(); }, [state, gameUrl]); @@ -85,22 +128,19 @@ export const Mgba = forwardRef( if (state !== MgbaState.Uninitialised) return; setState(MgbaState.Initialising); - mgbaModule.current = { - canvas: canvas.current, - }; - mGBA(mgbaModule.current).then((module: Module) => { - mgbaModule.current = module; - module.FSInit(); - setState(MgbaState.Initialised); - }); + const mModule = await mGBA({ canvas: canvas.current }); + mgbaModule.current = mModule; + await mModule.FSInit(); + await mModule.FSSync(); + setState(MgbaState.Initialised); })(); if (state === MgbaState.Initialised) return () => { try { - mgbaModule.current.quitGame(); - mgbaModule.current.quitMgba(); + mgbaModule.current?.quitGame(); + mgbaModule.current?.quitMgba(); } catch {} }; }, [state]); @@ -119,30 +159,31 @@ export const Mgba = forwardRef( ? "Return" : value.toLowerCase().replace("arrow", "").replace("key", ""); - mgbaModule.current.bindKey(binding, key); + mgbaModule.current?.bindKey(binding, key); } }, [controls, gameLoaded]); useEffect(() => { if (!gameLoaded) return; - mgbaModule.current.setVolume(volume ?? 1.0); + mgbaModule.current?.setVolume(volume ?? 1.0); }, [gameLoaded, volume]); useEffect(() => { if (!gameLoaded) return; if (paused) { - mgbaModule.current.pauseGame(); + mgbaModule.current?.pauseGame(); } else { - mgbaModule.current.resumeGame(); + mgbaModule.current?.resumeGame(); } }, [gameLoaded, paused]); useImperativeHandle(ref, () => { return { - restart: () => mgbaModule.current.quickReload(), - buttonPress: (key: GbaKey) => mgbaModule.current.buttonPress(key), - buttonRelease: (key: GbaKey) => mgbaModule.current.buttonUnpress(key), + restart: () => mgbaModule.current?.quickReload(), + buttonPress: (key: GbaKey) => mgbaModule.current?.buttonPress(key), + buttonRelease: (key: GbaKey) => mgbaModule.current?.buttonUnpress(key), + saveGame: () => {}, }; }); diff --git a/website/agb/src/app/mgba/useController.hook.ts b/website/agb/src/app/mgba/useController.hook.ts index 4555d526..52bbbc27 100644 --- a/website/agb/src/app/mgba/useController.hook.ts +++ b/website/agb/src/app/mgba/useController.hook.ts @@ -2,7 +2,9 @@ import { MutableRefObject, useEffect } from "react"; import { mGBAEmulator } from "./vendor/mgba"; import { GbaKey } from "./bindings"; -export function useController(mgbaModule: MutableRefObject) { +export function useController( + mgbaModule: MutableRefObject +) { useEffect(() => { let stopped = false; @@ -64,13 +66,13 @@ export function useController(mgbaModule: MutableRefObject) { for (let oldButton of previouslyPressedButtons) { if (!currentlyPressed.has(oldButton)) { - mgbaModule.current.buttonUnpress(oldButton); + mgbaModule.current?.buttonUnpress(oldButton); } } for (let newButton of currentlyPressed) { if (!previouslyPressedButtons.has(newButton)) { - mgbaModule.current.buttonPress(newButton); + mgbaModule.current?.buttonPress(newButton); } } diff --git a/website/agb/src/app/mgba/useFrameSkip.hook.ts b/website/agb/src/app/mgba/useFrameSkip.hook.ts index 8f75309f..c6999ff3 100644 --- a/website/agb/src/app/mgba/useFrameSkip.hook.ts +++ b/website/agb/src/app/mgba/useFrameSkip.hook.ts @@ -1,7 +1,9 @@ import { MutableRefObject, useEffect } from "react"; import { mGBAEmulator } from "./vendor/mgba"; -export function useFrameSkip(mgbaModule: MutableRefObject) { +export function useFrameSkip( + mgbaModule: MutableRefObject +) { useEffect(() => { let previous: number | undefined = undefined; let stopped = false; @@ -23,12 +25,12 @@ export function useFrameSkip(mgbaModule: MutableRefObject) { if (totalTime >= 1 / 60) { totalTime -= 1 / 60; if (paused) { - mgbaModule.current.resumeGame(); + mgbaModule.current?.resumeGame(); paused = false; } } else { if (!paused) { - mgbaModule.current.pauseGame(); + mgbaModule.current?.pauseGame(); paused = true; } }