now we can live replay

This commit is contained in:
Steven Hugg 2018-08-23 18:52:56 -04:00
parent caf56e14e3
commit a071cd80db
5 changed files with 53 additions and 29 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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]);
});
});

BIN
test/roms/nes/shoot2.c.rom Normal file

Binary file not shown.