a start of a wrapper around mgba

This commit is contained in:
Corwin 2023-04-13 21:32:40 +01:00
parent ef1af49a5a
commit 7098e6937b
No known key found for this signature in database
4 changed files with 109 additions and 25 deletions

BIN
website/public/game.gba Executable file

Binary file not shown.

View file

@ -1,24 +1,18 @@
import React from 'react'; import React, { useState } from 'react';
import logo from './logo.svg'; import { Mgba } from './mgba';
import './App.css';
function App() { function App() {
const [onGame, setOnGame] = useState(false);
const [volume, setVolume] = useState(1.0);
return ( return (
<div className="App"> <div>
<header className="App-header"> {
<img src={logo} className="App-logo" alt="logo" /> onGame && <><Mgba gameUrl="/game.gba" volume={volume} />
<p> <input type="range" value={volume} min="0" max="1" step="0.05" onChange={(e) => setVolume(Number(e.target.value))}></input></>
Edit <code>src/App.tsx</code> and save to reload. }
</p> <button onClick={() => setOnGame(!onGame)}>{onGame ? "End Game" : "Start Game"}</button>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div> </div>
); );
} }

View file

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App'; import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement document.getElementById('root') as HTMLElement
@ -12,8 +10,3 @@ root.render(
<App /> <App />
</React.StrictMode> </React.StrictMode>
); );
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

97
website/src/mgba.tsx Normal file
View file

@ -0,0 +1,97 @@
import { FC, useEffect, useRef, useState } from "react";
import mGBA from "./vendor/mgba";
type Module = any;
interface MgbaProps {
gameUrl: string,
volume?: Number,
}
enum MgbaState {
Uninitialised,
Initialising,
Initialised,
}
const MGBA_ROM_DIRECTORY = "/data/games";
export const Mgba: FC<MgbaProps> = ({ gameUrl, volume }) => {
const canvas = useRef(null);
const mgbaModule = useRef<Module>({});
const [state, setState] = useState(MgbaState.Uninitialised);
useEffect(() => {
if (state !== MgbaState.Initialised) return;
(async () => {
const game = await fetch(gameUrl);
const gameData = await game.arrayBuffer();
const gamePath = `${MGBA_ROM_DIRECTORY}/${gameUrl}`;
mgbaModule.current.FS.writeFile(gamePath, new Uint8Array(gameData));
mgbaModule.current.loadGame(gamePath);
})()
}, [state, gameUrl]);
// init mgba
useEffect(() => {
(async () => {
if (canvas === null) return;
if (state !== MgbaState.Uninitialised) return;
setState(MgbaState.Initialising);
mgbaModule.current = {
canvas: canvas.current,
locateFile: (file: string) => {
if (file === "mgba.wasm") {
return "/vendor/mgba.wasm";
}
return file;
}
};
mGBA(mgbaModule.current).then((module: Module) => {
mgbaModule.current = module;
module.FSInit();
setState(MgbaState.Initialised);
});
}
)();
if (state === MgbaState.Initialised)
return () => {
try {
mgbaModule.current.quitGame();
mgbaModule.current.quitMgba();
} catch { }
};
}, [state]);
useEffect(() => {
if (state !== MgbaState.Initialised) return;
mgbaModule.current.setVolume(volume ?? 1.0);
}, [state, volume]);
return <>
<canvas ref={canvas}></canvas>
<button onClick={() => {
if (state !== MgbaState.Initialised) return;
mgbaModule.current.saveState(0);
}}>Save State</button>
<button onClick={() => {
if (state !== MgbaState.Initialised) return;
mgbaModule.current.loadState(0);
}}>Load State</button>
<button onClick={() => {
if (state !== MgbaState.Initialised) return;
mgbaModule.current.quickReload(0);
}}>Restart</button>
</>;
};