Предотвратить рекурсивную функцию, выполняющую setTimeout, из состава - PullRequest
1 голос
/ 02 июля 2019

Я написал эмулятор Chip-8 на JavaScript (источник) и создал версию для браузера, которую можно воспроизвести здесь .

Содержит HTML-выбор:

<select>
  <option value="PONG">PONG</option>
  <option value="TETRIS">TETRIS</option>
</select>

, который загружает ПЗУ из файла при каждом его выборе:

document.querySelector('select').addEventListener('change', event => {
  const rom = event.target.value
  loadRom(rom)
})

Эта loadRom функция извлекает ПЗУ, преобразует его в полезную форму и загружает в экземпляркласса процессора.У cpu есть цикл fetch-decode-execute, который вызывается с step().Я создал эту cycle (основную) функцию для вызова себя в 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()
}

Это прекрасно работает, пока я не загружу новый ROM с помощью select.Теперь цикл составлен, и игра идет в два раза быстрее.Каждый раз, когда вы загружаете новое ПЗУ, оно снова соединяется и создает новый цикл.

Как я могу создать бесконечный цикл, но остановить его и запустить новый, не смешивая его?

Ответы [ 2 ]

1 голос
/ 02 июля 2019

Начнем с того, что текущий таймаут должен быть постоянной переменной, а затем вызовите clearTimeout с ним прямо перед вызовом loadRom.Если ничего еще не загружено, clearTimeout просто ничего не сделает.

Но поскольку у вас есть также await s, вам нужно будет проверить, загружается ли новый диск, пока await продолжаются.Один из способов сделать это - использовать другую постоянную переменную, используемую текущую romBuffer - если она не совпадает с romBuffer в замыкании функции, тогда начался другой ROM, поэтому немедленно возвращайтесь (и нерекурсивно создать тайм-аут).

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()
};
0 голосов
/ 02 июля 2019

Попробуйте использовать setInerval и следите за ручкой.

Затем очистите его при загрузке нового (или того же) рома.

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)

    // if rom is loaded, clear it!
    if(this.lastLoad)
        clearInterval(this.lastLoad);

    let timer = 0
    async function cycle() {
        timer++
        if (timer % 5 === 0) cpu.tick()

        await cpu.step()
    }

    // keep track the handle.
    this.lastLoad = setInterval(cycle, 3);
}
...