diff --git a/doc/notes.txt b/doc/notes.txt index c6369f24..a56e4484 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -51,10 +51,7 @@ TODO: - tools (memory, disasm) use debugging state - text log debugging script - NES crt should mark raster pos when debugging -- make sure we don't store files in local storage unnecc. -- state buffer/replay - intro/help text for each platform -- make sure controls work with replay feature (we'll have to save control state every frame) - vscode/atom extension? - navigator.getGamepads diff --git a/src/recorder.ts b/src/recorder.ts index 84a63c7d..9925e1fb 100644 --- a/src/recorder.ts +++ b/src/recorder.ts @@ -24,7 +24,8 @@ export class StateRecorderImpl implements EmuRecorder { this.checkpoints = []; this.framerecs = []; this.frameCount = 0; - this.lastSeekFrame = -1; + this.lastSeekFrame = 0; + if (this.callbackStateChanged) this.callbackStateChanged(); } frameRequested() : boolean { @@ -32,32 +33,35 @@ export class StateRecorderImpl implements EmuRecorder { if (this.checkpoints.length >= this.maxCheckpoints) { return false; } - // record the control state, if available - if (this.platform.saveControlsState) { - this.framerecs.push({ - controls:this.platform.saveControlsState(), - seed:getNoiseSeed() - }); + var controls = { + controls:this.platform.saveControlsState(), + seed:getNoiseSeed() + }; + var requested = false; + // are we replaying? then we don't need to save a frame, just replace controls + if (this.lastSeekFrame < this.frameCount) { + this.loadControls(this.lastSeekFrame); + } else { + // record the control state, if available + if (this.platform.saveControlsState) { + this.framerecs.push(controls); + } + // time to save next frame? + requested = (this.frameCount++ % this.checkpointInterval) == 0; } - // pick up where we left off, if we used the seek function - if (this.lastSeekFrame >= 0) { - this.frameCount = this.lastSeekFrame; - this.lastSeekFrame = -1; - // truncate buffers - this.checkpoints = this.checkpoints.slice(0, Math.floor((this.frameCount + this.checkpointInterval - 1) / this.checkpointInterval)); - this.framerecs = this.framerecs.slice(0, this.frameCount); - } - // time to save next frame? - if (this.callbackStateChanged) { - this.callbackStateChanged(); - } - return (this.frameCount++ % this.checkpointInterval) == 0; + this.lastSeekFrame++; + if (this.callbackStateChanged) this.callbackStateChanged(); + return requested; } numFrames() : number { return this.frameCount; } + currentFrame() : number { + return this.lastSeekFrame; + } + recordFrame(state : EmuState) { this.checkpoints.push(state); } @@ -79,8 +83,7 @@ export class StateRecorderImpl implements EmuRecorder { this.platform.loadState(state); while (frame < seekframe) { if (frame < this.framerecs.length) { - this.platform.loadControlsState(this.framerecs[frame].controls); - setNoiseSeed(this.framerecs[frame].seed); + this.loadControls(frame); } frame++; this.platform.advance(frame < seekframe); // TODO: infinite loop? @@ -91,4 +94,10 @@ export class StateRecorderImpl implements EmuRecorder { return 0; } } + + loadControls(frame : number) { + if (this.platform.loadControlsState) + this.platform.loadControlsState(this.framerecs[frame].controls); + setNoiseSeed(this.framerecs[frame].seed); + } } diff --git a/src/ui.ts b/src/ui.ts index d49a9329..da260b02 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -406,6 +406,7 @@ function setCompileOutput(data: WorkerResult) { if (rom) { // TODO instanceof Uint8Array) { try { clearBreakpoint(); // so we can replace memory (TODO: change toolbar btn) + _resetRecording(); platform.loadROM(getCurrentPresetTitle(), rom); if (!userPaused) _resume(); current_output = rom; @@ -550,6 +551,7 @@ function clearBreakpoint() { } function resetAndDebug() { + _disableRecording(); if (platform.setupDebug && platform.readAddress) { // TODO?? clearBreakpoint(); _resume(); @@ -693,6 +695,12 @@ function _disableRecording() { } } +function _resetRecording() { + if (recorderActive) { + stateRecorder.reset(); + } +} + function _enableRecording() { stateRecorder.reset(); platform.setRecorder(stateRecorder); @@ -790,8 +798,8 @@ function setupReplaySlider() { stateRecorder.callbackStateChanged = () => { replayslider.attr('min', 1); replayslider.attr('max', stateRecorder.numFrames()); - replayslider.val(stateRecorder.numFrames()); - updateFrameNo(stateRecorder.numFrames()); + replayslider.val(stateRecorder.currentFrame()); + updateFrameNo(stateRecorder.currentFrame()); }; replayslider.on('input', sliderChanged); replayslider.on('change', sliderChanged); diff --git a/test/cli/testplatforms.js b/test/cli/testplatforms.js index 4add131e..0a281689 100644 --- a/test/cli/testplatforms.js +++ b/test/cli/testplatforms.js @@ -24,15 +24,16 @@ global.Log = require('tss/js/Log.js').Log; includeInThisContext('tss/js/tss/PsgDeviceChannel.js'); includeInThisContext('tss/js/tss/MasterChannel.js'); includeInThisContext('tss/js/tss/AudioLooper.js'); - -var jsnes = require("jsnes/jsnes.min.js"); +includeInThisContext("jsnes/jsnes.min.js"); var emu = require('gen/emu.js'); +var Keys = emu.Keys; var audio = require('gen/audio.js'); var recorder = require('gen/recorder.js'); var _vicdual = require('gen/platform/vicdual.js'); var _apple2 = require('gen/platform/apple2.js'); var _vcs = require('gen/platform/vcs.js'); +var _nes = require('gen/platform/nes.js'); // @@ -108,5 +109,14 @@ describe('Platform Replay', () => { assert.equal(platform.saveState().p.SA, 0xff ^ 0x40); }); + it('Should run nes', () => { + var platform = testPlatform('nes', 'shoot2.c.rom', 70, (platform, frameno) => { + if (frameno == 60) { + keycallback(Keys.VK_Z.c, Keys.VK_Z.c, 1); + } + }); + assert.equal(65, platform.saveControlsState().c1[0]); + }); + }); diff --git a/test/roms/nes/shoot2.c.rom b/test/roms/nes/shoot2.c.rom new file mode 100644 index 00000000..abcfde40 Binary files /dev/null and b/test/roms/nes/shoot2.c.rom differ