I wrote a Chip-8 emulator in JavaScript (source) and made a playable browser version here.
It contains an HTML select:
<select>
<option value="PONG">PONG</option>
<option value="TETRIS">TETRIS</option>
</select>
Which loads a ROM from a file every time one is selected:
document.querySelector('select').addEventListener('change', event => {
const rom = event.target.value
loadRom(rom)
})
This loadRom
function fetches the ROM, converts it to a useful form, and loads it into an instance of a CPU class. The cpu
has a fetch-decode-execute cycle that gets called with step()
. I created this cycle
(main) function to call itself in a setTimeout.
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
cpu.interface.clearDisplay()
cpu.load(romBuffer)
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) {
cpu.tick()
timer = 0
}
await cpu.step()
setTimeout(cycle, 3)
}
cycle()
}
This works fine, until I load a new ROM with the select. Now the cycle is compounded, and the game goes twice as fast. Every time you load a new ROM, it compounds again and creates a new cycle.
How can I create an infinite loop, but stop it and start a brand new one without compounding it?
To start with, have the current timeout to be a persistent variable, and then call clearTimeout
with it right before calling loadRom
. If nothing has been loaded yet, the clearTimeout
just won't do anything.
But because you have await
s as well, you'll need to check whether a new rom gets loaded while the await
s are going on. One way to accomplish this would be to have another persistent variable, the current romBuffer
being used - if it's not the same as the romBuffer
in the function closure, then another rom has started, so return immediately (and don't recursively create a timeout).
let timeout;
let currentRomBuffer;
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
currentRomBuffer = romBuffer;
cpu.interface.clearDisplay()
cpu.load(romBuffer)
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) {
cpu.tick()
timer = 0
}
await cpu.step();
if (romBuffer !== currentRomBuffer) {
return;
}
timeout = setTimeout(cycle, 3);
}
cycle()
};