mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Merge remote-tracking branch 'upstream/master' into crash-page
This commit is contained in:
commit
528fe889fa
|
@ -28,6 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Fixnums are now implemented with `num_traits` trait definitions.
|
- Fixnums are now implemented with `num_traits` trait definitions.
|
||||||
- Rather than having our own sync with Statics, use the standard portable
|
- Rather than having our own sync with Statics, use the standard portable
|
||||||
atomics crate. These are reexported for convenience.
|
atomics crate. These are reexported for convenience.
|
||||||
|
- `Mgba` no longer implements `Write`. You're unlikely to notice as
|
||||||
|
`agb::println!` is unchanged.
|
||||||
|
- Writes of long messages to mgba are split over multiple log messages if they
|
||||||
|
overflow mgba's buffer. On a panic, only the final message will be Fatal with
|
||||||
|
the preceding ones (if needed) being Info.
|
||||||
|
|
||||||
## [0.19.1] - 2024/03/06
|
## [0.19.1] - 2024/03/06
|
||||||
|
|
||||||
|
|
|
@ -32,15 +32,13 @@ pub(crate) fn test_runner_measure_cycles() {
|
||||||
NUMBER_OF_CYCLES.set(0);
|
NUMBER_OF_CYCLES.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mgba {
|
pub struct Mgba {}
|
||||||
bytes_written: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mgba {
|
impl Mgba {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Option<Self> {
|
pub fn new() -> Option<Self> {
|
||||||
if is_running_in_mgba() {
|
if is_running_in_mgba() {
|
||||||
Some(Mgba { bytes_written: 0 })
|
Some(Mgba {})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -51,30 +49,32 @@ impl Mgba {
|
||||||
output: core::fmt::Arguments,
|
output: core::fmt::Arguments,
|
||||||
level: DebugLevel,
|
level: DebugLevel,
|
||||||
) -> Result<(), core::fmt::Error> {
|
) -> Result<(), core::fmt::Error> {
|
||||||
write!(self, "{output}")?;
|
let mut writer = MgbaWriter { bytes_written: 0 };
|
||||||
|
write!(&mut writer, "{output}")?;
|
||||||
self.set_level(level);
|
self.set_level(level);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MgbaWriter {
|
||||||
|
bytes_written: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl Mgba {
|
impl Mgba {
|
||||||
pub fn set_level(&mut self, level: DebugLevel) {
|
pub fn set_level(&mut self, level: DebugLevel) {
|
||||||
DEBUG_LEVEL.set(DEBUG_FLAG_CODE | level as u16);
|
DEBUG_LEVEL.set(DEBUG_FLAG_CODE | level as u16);
|
||||||
self.bytes_written = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Write for Mgba {
|
impl core::fmt::Write for MgbaWriter {
|
||||||
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
|
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
|
||||||
let mut str_iter = s.bytes();
|
for b in s.bytes() {
|
||||||
while self.bytes_written < 255 {
|
if self.bytes_written > 255 {
|
||||||
match str_iter.next() {
|
DEBUG_LEVEL.set(DEBUG_FLAG_CODE | DebugLevel::Info as u16);
|
||||||
Some(byte) => {
|
self.bytes_written = 0;
|
||||||
OUTPUT_STRING.set(self.bytes_written, byte);
|
|
||||||
self.bytes_written += 1;
|
|
||||||
}
|
|
||||||
None => return Ok(()),
|
|
||||||
}
|
}
|
||||||
|
OUTPUT_STRING.set(self.bytes_written, b);
|
||||||
|
self.bytes_written += 1;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,12 +54,11 @@ impl<'bitmap, 'gba> BitmapTextRender<'bitmap, 'gba> {
|
||||||
for y in 0..letter.height as usize {
|
for y in 0..letter.height as usize {
|
||||||
for x in 0..letter.width as usize {
|
for x in 0..letter.width as usize {
|
||||||
let rendered = letter.bit_absolute(x, y);
|
let rendered = letter.bit_absolute(x, y);
|
||||||
if rendered {
|
let x = x as i32 + self.head_position.x;
|
||||||
self.bitmap.draw_point(
|
let y = y as i32 + y_position_start;
|
||||||
x as i32 + self.head_position.x,
|
|
||||||
y as i32 + y_position_start,
|
if rendered && (0..=WIDTH).contains(&x) && (0..=HEIGHT).contains(&y) {
|
||||||
self.colour,
|
self.bitmap.draw_point(x, y, self.colour);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use agb::save::{Error, SaveManager};
|
||||||
use agb::Gba;
|
use agb::Gba;
|
||||||
|
|
||||||
static HIGH_SCORE: AtomicU32 = AtomicU32::new(0);
|
static HIGH_SCORE: AtomicU32 = AtomicU32::new(0);
|
||||||
|
static SAVE_OFFSET: usize = 1;
|
||||||
|
|
||||||
pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
|
pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
|
||||||
gba.save.init_sram();
|
gba.save.init_sram();
|
||||||
|
@ -18,7 +19,7 @@ pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
|
||||||
save_high_score(&mut gba.save, 0)?;
|
save_high_score(&mut gba.save, 0)?;
|
||||||
} else {
|
} else {
|
||||||
let mut buffer = [0; 4];
|
let mut buffer = [0; 4];
|
||||||
access.read(1, &mut buffer)?;
|
access.read(SAVE_OFFSET, &mut buffer)?;
|
||||||
let high_score = u32::from_le_bytes(buffer);
|
let high_score = u32::from_le_bytes(buffer);
|
||||||
|
|
||||||
let score = if high_score > 100 { 0 } else { high_score };
|
let score = if high_score > 100 { 0 } else { high_score };
|
||||||
|
@ -35,8 +36,8 @@ pub fn load_high_score() -> u32 {
|
||||||
|
|
||||||
pub fn save_high_score(save: &mut SaveManager, score: u32) -> Result<(), Error> {
|
pub fn save_high_score(save: &mut SaveManager, score: u32) -> Result<(), Error> {
|
||||||
save.access()?
|
save.access()?
|
||||||
.prepare_write(1..5)?
|
.prepare_write(SAVE_OFFSET..SAVE_OFFSET + 4)?
|
||||||
.write(1, &score.to_le_bytes())?;
|
.write(SAVE_OFFSET, &score.to_le_bytes())?;
|
||||||
HIGH_SCORE.store(score, Ordering::SeqCst);
|
HIGH_SCORE.store(score, Ordering::SeqCst);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,8 +121,10 @@ pub fn entry(mut gba: agb::Gba) -> ! {
|
||||||
oam: unmanaged,
|
oam: unmanaged,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut current_level = 0;
|
let saved_level = save::load_max_level() as usize;
|
||||||
let mut maximum_level = save::load_max_level() as usize;
|
|
||||||
|
let mut current_level = saved_level;
|
||||||
|
let mut maximum_level = saved_level;
|
||||||
loop {
|
loop {
|
||||||
if current_level >= level::Level::num_levels() {
|
if current_level >= level::Level::num_levels() {
|
||||||
current_level = 0;
|
current_level = 0;
|
||||||
|
|
|
@ -5,6 +5,7 @@ use agb::{
|
||||||
};
|
};
|
||||||
|
|
||||||
static MAXIMUM_LEVEL: AtomicU32 = AtomicU32::new(0);
|
static MAXIMUM_LEVEL: AtomicU32 = AtomicU32::new(0);
|
||||||
|
static SAVE_OFFSET: usize = 0xFF;
|
||||||
|
|
||||||
pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
|
pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
|
||||||
gba.save.init_sram();
|
gba.save.init_sram();
|
||||||
|
@ -20,7 +21,7 @@ pub fn init_save(gba: &mut Gba) -> Result<(), Error> {
|
||||||
save_max_level(&mut gba.save, 0)?;
|
save_max_level(&mut gba.save, 0)?;
|
||||||
} else {
|
} else {
|
||||||
let mut buffer = [0; 4];
|
let mut buffer = [0; 4];
|
||||||
access.read(1, &mut buffer)?;
|
access.read(SAVE_OFFSET, &mut buffer)?;
|
||||||
let max_level = u32::from_le_bytes(buffer);
|
let max_level = u32::from_le_bytes(buffer);
|
||||||
|
|
||||||
if max_level > 100 {
|
if max_level > 100 {
|
||||||
|
@ -39,8 +40,8 @@ pub fn load_max_level() -> u32 {
|
||||||
|
|
||||||
pub fn save_max_level(save: &mut SaveManager, level: u32) -> Result<(), Error> {
|
pub fn save_max_level(save: &mut SaveManager, level: u32) -> Result<(), Error> {
|
||||||
save.access()?
|
save.access()?
|
||||||
.prepare_write(1..5)?
|
.prepare_write(SAVE_OFFSET..SAVE_OFFSET + 4)?
|
||||||
.write(1, &level.to_le_bytes())?;
|
.write(SAVE_OFFSET, &level.to_le_bytes())?;
|
||||||
MAXIMUM_LEVEL.store(level, Ordering::SeqCst);
|
MAXIMUM_LEVEL.store(level, Ordering::SeqCst);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
3
justfile
3
justfile
|
@ -103,8 +103,7 @@ build-mgba-wasm:
|
||||||
|
|
||||||
build-combo-rom-site:
|
build-combo-rom-site:
|
||||||
just _build-rom "examples/combo" "AGBGAMES"
|
just _build-rom "examples/combo" "AGBGAMES"
|
||||||
mkdir -p website/agb/public
|
gzip -9 -c examples/target/examples/combo.gba > website/agb/src/app/combo.gba.gz
|
||||||
gzip -9 -c examples/target/examples/combo.gba > website/agb/public/combo.gba.gz
|
|
||||||
|
|
||||||
|
|
||||||
setup-app-build: build-mgba-wasm build-combo-rom-site build-website-backtrace
|
setup-app-build: build-mgba-wasm build-combo-rom-site build-website-backtrace
|
||||||
|
|
|
@ -10,12 +10,11 @@ import { GbaKey, KeyBindings } from "./bindings";
|
||||||
import { styled } from "styled-components";
|
import { styled } from "styled-components";
|
||||||
import { useFrameSkip } from "./useFrameSkip.hook";
|
import { useFrameSkip } from "./useFrameSkip.hook";
|
||||||
import { useController } from "./useController.hook";
|
import { useController } from "./useController.hook";
|
||||||
|
import { useLocalStorage } from "./useLocalStorage.hook";
|
||||||
type Module = any;
|
|
||||||
|
|
||||||
interface MgbaProps {
|
interface MgbaProps {
|
||||||
gameUrl: string;
|
gameUrl: URL;
|
||||||
volume?: Number;
|
volume?: number;
|
||||||
controls: KeyBindings;
|
controls: KeyBindings;
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
}
|
}
|
||||||
|
@ -42,10 +41,12 @@ export interface MgbaHandle {
|
||||||
buttonRelease: (key: GbaKey) => void;
|
buttonRelease: (key: GbaKey) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadGame(gameUrl: string): Promise<ArrayBuffer> {
|
async function downloadGame(gameUrl: URL): Promise<ArrayBuffer> {
|
||||||
const game = await fetch(gameUrl);
|
const game = await fetch(gameUrl);
|
||||||
|
|
||||||
if (gameUrl.endsWith(".gz")) {
|
const gameUrlString = gameUrl.toString();
|
||||||
|
|
||||||
|
if (gameUrlString.endsWith(".gz")) {
|
||||||
const decompressedStream = (await game.blob())
|
const decompressedStream = (await game.blob())
|
||||||
.stream()
|
.stream()
|
||||||
.pipeThrough(new DecompressionStream("gzip"));
|
.pipeThrough(new DecompressionStream("gzip"));
|
||||||
|
@ -55,28 +56,73 @@ async function downloadGame(gameUrl: string): Promise<ArrayBuffer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SaveGame {
|
||||||
|
[gameName: string]: number[];
|
||||||
|
}
|
||||||
|
|
||||||
export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
|
export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
|
||||||
({ gameUrl, volume, controls, paused }, ref) => {
|
({ gameUrl, volume, controls, paused }, ref) => {
|
||||||
const canvas = useRef(null);
|
const canvas = useRef(null);
|
||||||
const mgbaModule = useRef<Module>({} as mGBAEmulator);
|
const mgbaModule = useRef<mGBAEmulator>();
|
||||||
|
|
||||||
|
const [saveGame, setSaveGame] = useLocalStorage<SaveGame>(
|
||||||
|
{},
|
||||||
|
"agbrswebplayer/savegames"
|
||||||
|
);
|
||||||
|
const gameUrlString = gameUrl.toString();
|
||||||
|
|
||||||
const [state, setState] = useState(MgbaState.Uninitialised);
|
const [state, setState] = useState(MgbaState.Uninitialised);
|
||||||
const [gameLoaded, setGameLoaded] = useState(false);
|
const [gameLoaded, setGameLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function beforeUnload() {
|
||||||
|
const gameSplit = gameUrlString.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);
|
||||||
|
};
|
||||||
|
}, [gameUrlString, saveGame, setSaveGame]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state !== MgbaState.Initialised) return;
|
||||||
|
|
||||||
|
const gameSplit = gameUrlString.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]));
|
||||||
|
}, [gameUrlString, saveGame, state]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state !== MgbaState.Initialised) return;
|
if (state !== MgbaState.Initialised) return;
|
||||||
(async () => {
|
(async () => {
|
||||||
const gameData = await downloadGame(gameUrl);
|
const gameData = await downloadGame(gameUrl);
|
||||||
const gameSplit = gameUrl.split("/");
|
const gameSplit = gameUrlString.split("/");
|
||||||
const gameBaseName = gameSplit[gameSplit.length - 1];
|
const gameBaseName = gameSplit[gameSplit.length - 1];
|
||||||
|
|
||||||
const gamePath = `${MGBA_ROM_DIRECTORY}/${gameBaseName}`;
|
const gamePath = `${MGBA_ROM_DIRECTORY}/${gameBaseName}`;
|
||||||
mgbaModule.current.FS.writeFile(gamePath, new Uint8Array(gameData));
|
mgbaModule.current?.FS.writeFile(gamePath, new Uint8Array(gameData));
|
||||||
mgbaModule.current.loadGame(gamePath);
|
mgbaModule.current?.loadGame(gamePath);
|
||||||
mgbaModule.current.setVolume(0.1); // for some reason you have to do this or you get no sound
|
mgbaModule.current?.setVolume(0.1); // for some reason you have to do this or you get no sound
|
||||||
setGameLoaded(true);
|
setGameLoaded(true);
|
||||||
})();
|
})();
|
||||||
}, [state, gameUrl]);
|
}, [state, gameUrl, gameUrlString]);
|
||||||
|
|
||||||
// init mgba
|
// init mgba
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -85,22 +131,19 @@ export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
|
||||||
if (state !== MgbaState.Uninitialised) return;
|
if (state !== MgbaState.Uninitialised) return;
|
||||||
|
|
||||||
setState(MgbaState.Initialising);
|
setState(MgbaState.Initialising);
|
||||||
mgbaModule.current = {
|
|
||||||
canvas: canvas.current,
|
|
||||||
};
|
|
||||||
|
|
||||||
mGBA(mgbaModule.current).then((module: Module) => {
|
const mModule = await mGBA({ canvas: canvas.current });
|
||||||
mgbaModule.current = module;
|
mgbaModule.current = mModule;
|
||||||
module.FSInit();
|
await mModule.FSInit();
|
||||||
setState(MgbaState.Initialised);
|
await mModule.FSSync();
|
||||||
});
|
setState(MgbaState.Initialised);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (state === MgbaState.Initialised)
|
if (state === MgbaState.Initialised)
|
||||||
return () => {
|
return () => {
|
||||||
try {
|
try {
|
||||||
mgbaModule.current.quitGame();
|
mgbaModule.current?.quitGame();
|
||||||
mgbaModule.current.quitMgba();
|
mgbaModule.current?.quitMgba();
|
||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
@ -119,30 +162,31 @@ export const Mgba = forwardRef<MgbaHandle, MgbaProps>(
|
||||||
? "Return"
|
? "Return"
|
||||||
: value.toLowerCase().replace("arrow", "").replace("key", "");
|
: value.toLowerCase().replace("arrow", "").replace("key", "");
|
||||||
|
|
||||||
mgbaModule.current.bindKey(binding, key);
|
mgbaModule.current?.bindKey(binding, key);
|
||||||
}
|
}
|
||||||
}, [controls, gameLoaded]);
|
}, [controls, gameLoaded]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gameLoaded) return;
|
if (!gameLoaded) return;
|
||||||
mgbaModule.current.setVolume(volume ?? 1.0);
|
mgbaModule.current?.setVolume(volume ?? 1.0);
|
||||||
}, [gameLoaded, volume]);
|
}, [gameLoaded, volume]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gameLoaded) return;
|
if (!gameLoaded) return;
|
||||||
|
|
||||||
if (paused) {
|
if (paused) {
|
||||||
mgbaModule.current.pauseGame();
|
mgbaModule.current?.pauseGame();
|
||||||
} else {
|
} else {
|
||||||
mgbaModule.current.resumeGame();
|
mgbaModule.current?.resumeGame();
|
||||||
}
|
}
|
||||||
}, [gameLoaded, paused]);
|
}, [gameLoaded, paused]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
return {
|
return {
|
||||||
restart: () => mgbaModule.current.quickReload(),
|
restart: () => mgbaModule.current?.quickReload(),
|
||||||
buttonPress: (key: GbaKey) => mgbaModule.current.buttonPress(key),
|
buttonPress: (key: GbaKey) => mgbaModule.current?.buttonPress(key),
|
||||||
buttonRelease: (key: GbaKey) => mgbaModule.current.buttonUnpress(key),
|
buttonRelease: (key: GbaKey) => mgbaModule.current?.buttonUnpress(key),
|
||||||
|
saveGame: () => {},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ const StartButtonWrapper = styled.button`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface MgbaWrapperProps {
|
interface MgbaWrapperProps {
|
||||||
gameUrl: string;
|
gameUrl: URL;
|
||||||
isPlaying?: boolean;
|
isPlaying?: boolean;
|
||||||
setIsPlaying?: (isPlaying: boolean) => void;
|
setIsPlaying?: (isPlaying: boolean) => void;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,6 @@ export const MgbaWrapper = forwardRef<MgbaHandle, MgbaWrapperProps>(
|
||||||
restart: () => mgbaRef.current?.restart(),
|
restart: () => mgbaRef.current?.restart(),
|
||||||
buttonPress: (key: GbaKey) => mgbaRef.current?.buttonPress(key),
|
buttonPress: (key: GbaKey) => mgbaRef.current?.buttonPress(key),
|
||||||
buttonRelease: (key: GbaKey) => mgbaRef.current?.buttonRelease(key),
|
buttonRelease: (key: GbaKey) => mgbaRef.current?.buttonRelease(key),
|
||||||
hardReset: () => setMgbaId((id) => id + 1),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useAvoidItchIoScrolling();
|
useAvoidItchIoScrolling();
|
||||||
|
@ -123,7 +122,6 @@ export const MgbaWrapper = forwardRef<MgbaHandle, MgbaWrapperProps>(
|
||||||
)}
|
)}
|
||||||
{isPlaying ? (
|
{isPlaying ? (
|
||||||
<Mgba
|
<Mgba
|
||||||
key={mgbaId}
|
|
||||||
ref={mgbaRef}
|
ref={mgbaRef}
|
||||||
gameUrl={gameUrl}
|
gameUrl={gameUrl}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { MutableRefObject, useEffect } from "react";
|
||||||
import { mGBAEmulator } from "./vendor/mgba";
|
import { mGBAEmulator } from "./vendor/mgba";
|
||||||
import { GbaKey } from "./bindings";
|
import { GbaKey } from "./bindings";
|
||||||
|
|
||||||
export function useController(mgbaModule: MutableRefObject<mGBAEmulator>) {
|
export function useController(
|
||||||
|
mgbaModule: MutableRefObject<mGBAEmulator | undefined>
|
||||||
|
) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let stopped = false;
|
let stopped = false;
|
||||||
|
|
||||||
|
@ -64,13 +66,13 @@ export function useController(mgbaModule: MutableRefObject<mGBAEmulator>) {
|
||||||
|
|
||||||
for (let oldButton of previouslyPressedButtons) {
|
for (let oldButton of previouslyPressedButtons) {
|
||||||
if (!currentlyPressed.has(oldButton)) {
|
if (!currentlyPressed.has(oldButton)) {
|
||||||
mgbaModule.current.buttonUnpress(oldButton);
|
mgbaModule.current?.buttonUnpress(oldButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let newButton of currentlyPressed) {
|
for (let newButton of currentlyPressed) {
|
||||||
if (!previouslyPressedButtons.has(newButton)) {
|
if (!previouslyPressedButtons.has(newButton)) {
|
||||||
mgbaModule.current.buttonPress(newButton);
|
mgbaModule.current?.buttonPress(newButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { MutableRefObject, useEffect } from "react";
|
import { MutableRefObject, useEffect } from "react";
|
||||||
import { mGBAEmulator } from "./vendor/mgba";
|
import { mGBAEmulator } from "./vendor/mgba";
|
||||||
|
|
||||||
export function useFrameSkip(mgbaModule: MutableRefObject<mGBAEmulator>) {
|
export function useFrameSkip(
|
||||||
|
mgbaModule: MutableRefObject<mGBAEmulator | undefined>
|
||||||
|
) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let previous: number | undefined = undefined;
|
let previous: number | undefined = undefined;
|
||||||
let stopped = false;
|
let stopped = false;
|
||||||
|
@ -23,12 +25,12 @@ export function useFrameSkip(mgbaModule: MutableRefObject<mGBAEmulator>) {
|
||||||
if (totalTime >= 1 / 60) {
|
if (totalTime >= 1 / 60) {
|
||||||
totalTime -= 1 / 60;
|
totalTime -= 1 / 60;
|
||||||
if (paused) {
|
if (paused) {
|
||||||
mgbaModule.current.resumeGame();
|
mgbaModule.current?.resumeGame();
|
||||||
paused = false;
|
paused = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
mgbaModule.current.pauseGame();
|
mgbaModule.current?.pauseGame();
|
||||||
paused = true;
|
paused = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,8 @@ function shouldStartPlaying(isTouchScreen: boolean | undefined) {
|
||||||
return !isTouchScreen;
|
return !isTouchScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COMBO_GAME = new URL("combo.gba.gz", import.meta.url);
|
||||||
|
|
||||||
function MgbaWithControllerSides() {
|
function MgbaWithControllerSides() {
|
||||||
const mgba = useRef<MgbaHandle>(null);
|
const mgba = useRef<MgbaHandle>(null);
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ function MgbaWithControllerSides() {
|
||||||
</GameSide>
|
</GameSide>
|
||||||
<GameDisplayWindow>
|
<GameDisplayWindow>
|
||||||
<MgbaWrapper
|
<MgbaWrapper
|
||||||
gameUrl="combo.gba.gz"
|
gameUrl={COMBO_GAME}
|
||||||
ref={mgba}
|
ref={mgba}
|
||||||
isPlaying={playEmulator}
|
isPlaying={playEmulator}
|
||||||
setIsPlaying={setIsPlaying}
|
setIsPlaying={setIsPlaying}
|
||||||
|
|
Loading…
Reference in a new issue