-
+
diff --git a/src/common/baseplatform.ts b/src/common/baseplatform.ts
index 46aca836..7a73ea8a 100644
--- a/src/common/baseplatform.ts
+++ b/src/common/baseplatform.ts
@@ -112,7 +112,8 @@ export interface Platform {
getMemoryMap?() : MemoryMap;
setRecorder?(recorder : EmuRecorder) : void;
- advance?(novideo? : boolean) : void;
+ advance?(novideo? : boolean) : number;
+ advanceFrameClock?(trap:DebugCondition, step:number) : number;
showHelp?(tool:string, ident?:string) : void;
resize?() : void;
@@ -179,7 +180,7 @@ export abstract class BasePlatform {
abstract saveState() : EmuState;
abstract pause() : void;
abstract resume() : void;
- abstract advance(novideo? : boolean) : void;
+ abstract advance(novideo? : boolean) : number;
setRecorder(recorder : EmuRecorder) : void {
this.recorder = recorder;
@@ -267,8 +268,9 @@ export abstract class BaseDebugPlatform extends BasePlatform {
this.pollControls();
this.updateRecorder();
this.preFrame();
- this.advance(novideo);
+ var steps = this.advance(novideo);
this.postFrame();
+ return steps;
}
// default debugging
abstract getSP() : number;
@@ -1058,8 +1060,20 @@ export abstract class BaseMachinePlatform
extends BaseDebugPl
}
advance(novideo:boolean) {
- this.machine.advanceFrame(this.getDebugCallback());
+ var steps = this.machine.advanceFrame(this.getDebugCallback());
if (!novideo && this.video) this.video.updateFrame();
+ return steps;
+ }
+
+ advanceFrameClock(trap, step) {
+ if (!(step > 0)) return;
+ if (this.machine instanceof BaseWASMMachine) {
+ return this.machine.advanceFrameClock(trap, step);
+ } else {
+ return this.machine.advanceFrame(() => {
+ return --step <= 0;
+ });
+ }
}
isRunning() {
@@ -1164,6 +1178,8 @@ export abstract class BaseWASMMachine {
statearr : Uint8Array;
cpustateptr : number;
cpustatearr : Uint8Array;
+ ctrlstateptr : number;
+ ctrlstatearr : Uint8Array;
cpu : CPU;
romptr : number;
romlen : number;
@@ -1215,6 +1231,9 @@ export abstract class BaseWASMMachine {
var statesize = this.exports.machine_get_state_size();
this.stateptr = this.exports.malloc(statesize);
this.statearr = new Uint8Array(this.exports.memory.buffer, this.stateptr, statesize);
+ var ctrlstatesize = this.exports.machine_get_controls_state_size();
+ this.ctrlstateptr = this.exports.malloc(ctrlstatesize);
+ this.ctrlstatearr = new Uint8Array(this.exports.memory.buffer, this.ctrlstateptr, ctrlstatesize);
var cpustatesize = this.exports.machine_get_cpu_state_size();
this.cpustateptr = this.exports.malloc(cpustatesize);
this.cpustatearr = new Uint8Array(this.exports.memory.buffer, this.cpustateptr, cpustatesize);
@@ -1271,12 +1290,14 @@ export abstract class BaseWASMMachine {
}
// assume controls buffer is smaller than cpu buffer
saveControlsState() : any {
- this.exports.machine_save_controls_state(this.sys, this.cpustateptr);
- return { controls:this.cpustatearr.slice(0, this.exports.machine_get_controls_state_size()) }
+ //console.log(1, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
+ this.exports.machine_save_controls_state(this.sys, this.ctrlstateptr);
+ //console.log(2, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
+ return { controls:this.ctrlstatearr.slice(0) }
}
loadControlsState(state) : void {
- this.cpustatearr.set(state.controls);
- this.exports.machine_load_controls_state(this.sys, this.cpustateptr);
+ this.ctrlstatearr.set(state.controls);
+ this.exports.machine_load_controls_state(this.sys, this.ctrlstateptr);
}
connectAudio(audio : SampledAudioSink) : void {
this.audio = audio;
diff --git a/src/common/devices.ts b/src/common/devices.ts
index 543624d6..e83078b2 100644
--- a/src/common/devices.ts
+++ b/src/common/devices.ts
@@ -308,6 +308,7 @@ export abstract class BasicScanlineMachine extends BasicMachine implements Raste
this.preFrame();
var clock = 0;
var endLineClock = 0;
+ var steps = 0;
this.probe.logNewFrame();
for (var sl=0; sl void;
callbackNewCheckpoint : (state:EmuState) => void;
- maxCheckpoints : number = 120;
+ maxCheckpoints : number = 300;
platform : Platform;
checkpoints : EmuState[];
framerecs : FrameRec[];
frameCount : number;
lastSeekFrame : number;
+ lastSeekStep : number;
+ lastStepCount : number;
constructor(platform : Platform) {
this.reset();
@@ -29,6 +31,7 @@ export class StateRecorderImpl implements EmuRecorder {
this.framerecs = [];
this.frameCount = 0;
this.lastSeekFrame = 0;
+ this.lastSeekStep = 0;
if (this.callbackStateChanged) this.callbackStateChanged();
}
@@ -50,6 +53,7 @@ export class StateRecorderImpl implements EmuRecorder {
requested = (this.frameCount++ % this.checkpointInterval) == 0;
}
this.lastSeekFrame++;
+ this.lastSeekStep = 0;
if (this.callbackStateChanged) this.callbackStateChanged();
return requested;
}
@@ -61,7 +65,11 @@ export class StateRecorderImpl implements EmuRecorder {
currentFrame() : number {
return this.lastSeekFrame;
}
-
+
+ currentStep() : number {
+ return this.lastSeekStep;
+ }
+
recordFrame(state : EmuState) {
this.checkpoints.push(state);
if (this.callbackNewCheckpoint) this.callbackNewCheckpoint(state);
@@ -82,22 +90,35 @@ export class StateRecorderImpl implements EmuRecorder {
return {frame:foundframe, state:this.checkpoints[foundidx]};
}
- loadFrame(seekframe : number) : number {
- if (seekframe == this.lastSeekFrame)
+ loadFrame(seekframe : number, seekstep? : number) : number {
+ seekframe |= 0;
+ seekstep |= 0;
+ if (seekframe == this.lastSeekFrame && seekstep == this.lastSeekStep) {
return seekframe; // already set to this frame
+ }
// TODO: what if < 1?
let {frame,state} = this.getStateAtOrBefore(seekframe-1);
if (state) {
+ var numSteps = 0;
this.platform.pause();
this.platform.loadState(state);
+ // seek to frame index
while (frame < seekframe) {
if (frame < this.framerecs.length) {
this.loadControls(frame);
}
frame++;
- this.platform.advance(frame < seekframe); // TODO: infinite loop?
+ numSteps = this.platform.advance(frame < seekframe); // TODO: infinite loop?
}
+ // seek to step index
+ // TODO: what if advance() returns clocks, but steps use insns?
+ if (seekstep > 0 && this.platform.advanceFrameClock) {
+ seekstep = this.platform.advanceFrameClock(null, seekstep);
+ }
+ // record new values
this.lastSeekFrame = seekframe;
+ this.lastSeekStep = seekstep;
+ this.lastStepCount = numSteps;
return seekframe;
} else {
return 0;
diff --git a/src/ide/ui.ts b/src/ide/ui.ts
index 62de8b3f..66ad671e 100644
--- a/src/ide/ui.ts
+++ b/src/ide/ui.ts
@@ -1261,6 +1261,7 @@ function clearBreakpoint() {
function resetAndDebug() {
if (!checkRunReady()) return;
+ var wasRecording = recorderActive;
_disableRecording();
if (platform.setupDebug && platform.readAddress) { // TODO??
clearBreakpoint();
@@ -1274,6 +1275,7 @@ function resetAndDebug() {
} else {
platform.reset();
}
+ if (wasRecording) _enableRecording();
}
function _breakExpression() {
@@ -1610,41 +1612,59 @@ function setupDebugControls() {
function setupReplaySlider() {
var replayslider = $("#replayslider");
+ var clockslider = $("#clockslider");
var replayframeno = $("#replay_frame");
- var updateFrameNo = (n) => {
- replayframeno.text(n+"");
+ var clockno = $("#replay_clock");
+ if (!platform.advanceFrameClock) $("#clockdiv").hide(); // TODO: put this test in recorder?
+ var updateFrameNo = () => {
+ replayframeno.text(stateRecorder.lastSeekFrame+"");
+ clockno.text(stateRecorder.lastSeekStep+"");
};
var sliderChanged = (e) => {
_pause();
- var frame = (e.target).value;
- if (stateRecorder.loadFrame(frame)) {
- updateFrameNo(frame);
- projectWindows.tick();
- showDebugInfo(platform.saveState());
+ var frame : number = parseInt(replayslider.val().toString());
+ var step : number = parseInt(clockslider.val().toString());
+ if (stateRecorder.loadFrame(frame, step)) {
+ clockslider.attr('min', 0);
+ clockslider.attr('max', stateRecorder.lastStepCount);
+ updateFrameNo();
+ uiDebugCallback(platform.saveState());
}
};
var setFrameTo = (frame:number) => {
_pause();
if (stateRecorder.loadFrame(frame)) {
replayslider.val(frame);
- updateFrameNo(frame);
- projectWindows.tick();
- showDebugInfo(platform.saveState());
+ updateFrameNo();
+ uiDebugCallback(platform.saveState());
+ }
+ };
+ var setClockTo = (clock:number) => {
+ _pause();
+ var frame : number = parseInt(replayslider.val().toString());
+ if (stateRecorder.loadFrame(frame, clock)) {
+ clockslider.val(clock);
+ updateFrameNo();
+ uiDebugCallback(platform.saveState());
}
};
stateRecorder.callbackStateChanged = () => {
replayslider.attr('min', 1);
replayslider.attr('max', stateRecorder.numFrames());
replayslider.val(stateRecorder.currentFrame());
- updateFrameNo(stateRecorder.currentFrame());
+ clockslider.val(stateRecorder.currentStep());
+ updateFrameNo();
showDebugInfo(platform.saveState());
};
replayslider.on('input', sliderChanged);
- replayslider.on('change', sliderChanged);
+ clockslider.on('input', sliderChanged);
+ //replayslider.on('change', sliderChanged);
$("#replay_min").click(() => { setFrameTo(1) });
$("#replay_max").click(() => { setFrameTo(stateRecorder.numFrames()); });
$("#replay_back").click(() => { setFrameTo(parseInt(replayslider.val().toString()) - 1); });
$("#replay_fwd").click(() => { setFrameTo(parseInt(replayslider.val().toString()) + 1); });
+ $("#clock_back").click(() => { setClockTo(parseInt(clockslider.val().toString()) - 1); });
+ $("#clock_fwd").click(() => { setClockTo(parseInt(clockslider.val().toString()) + 1); });
$("#replay_bar").show();
uitoolbar.add('ctrl+alt+0', 'Start/Stop Replay Recording', 'glyphicon-record', _toggleRecording).prop('id','dbg_record');
}
diff --git a/src/machine/atari7800.ts b/src/machine/atari7800.ts
index 04a2989e..aecd5c17 100644
--- a/src/machine/atari7800.ts
+++ b/src/machine/atari7800.ts
@@ -394,6 +394,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
var rgb;
var mc = 0;
var fc = 0;
+ var steps = 0;
this.probe.logNewFrame();
//console.log(hex(this.cpu.getPC()), hex(this.maria.dll));
// visible lines
@@ -411,6 +412,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
break; // TODO?
}
mc += this.advanceCPU() << 2;
+ steps++;
}
// is this scanline visible?
if (visible) {
@@ -443,6 +445,7 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
break;
}
mc += this.advanceCPU() << 2;
+ steps++;
}
// audio
this.audio && this.audioadapter.generate(this.audio);
@@ -455,7 +458,8 @@ export class Atari7800 extends BasicMachine implements RasterFrameBased {
// TODO let bkcol = this.maria.regs[0x0];
// TODO $(this.video.canvas).css('background-color', COLORS_WEB[bkcol]);
*/
- return (this.lastFrameCycles = fc);
+ this.lastFrameCycles = fc;
+ return steps;
}
getRasterX() { return this.lastFrameCycles % colorClocksPerLine; }
diff --git a/src/platform/astrocade.ts b/src/platform/astrocade.ts
index 9b3d0576..dd047387 100644
--- a/src/platform/astrocade.ts
+++ b/src/platform/astrocade.ts
@@ -40,6 +40,9 @@ class BallyAstrocadePlatform extends BaseZ80MachinePlatform impl
{name:'Screen RAM',start:0x4000,size:0x1000,type:'ram'},
{name:'BIOS Variables',start:0x4fce,size:0x5000-0x4fce,type:'ram'},
] } };
+ showHelp(tool:string, ident:string) {
+ window.open("https://8bitworkshop.com/blog/platforms/astrocade/", "_help"); // TODO
+ }
}
class BallyAstrocadeBIOSPlatform extends BallyAstrocadePlatform implements Platform {
diff --git a/src/platform/atari8.ts b/src/platform/atari8.ts
index aec2c4d0..d000bd39 100644
--- a/src/platform/atari8.ts
+++ b/src/platform/atari8.ts
@@ -489,12 +489,13 @@ const _Atari8Platform = function(mainElement) {
jacanvas.mousedown(rasterPosBreakFn);
}
- advance(novideo : boolean) {
+ advance(novideo : boolean) : number {
var idata = video.getFrameData();
var iofs = 0;
var debugCond = this.getDebugCallback();
var rgb;
var freeClocks = 0;
+ var totalClocks = 0;
// load controls
// TODO
gtia.regs[0x10] = inputs[0] ^ 1;
@@ -518,6 +519,7 @@ const _Atari8Platform = function(mainElement) {
break;
}
cpu.clockPulse();
+ totalClocks++;
}
// 4 ANTIC pulses = 8 pixels
if (antic.v >= 24 && antic.h >= 44 && antic.h < 44+176) { // TODO: const
@@ -535,6 +537,7 @@ const _Atari8Platform = function(mainElement) {
let bkcol = gtia.regs[COLBK];
$(video.canvas).css('background-color', COLORS_WEB[bkcol]);
}
+ return totalClocks;
}
loadROM(title, data) {
diff --git a/src/platform/galaxian.ts b/src/platform/galaxian.ts
index f072adbe..556c9bef 100644
--- a/src/platform/galaxian.ts
+++ b/src/platform/galaxian.ts
@@ -318,13 +318,14 @@ const _GalaxianPlatform = function(mainElement, options) {
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
}
- advance(novideo: boolean) {
+ advance(novideo: boolean) : number {
+ var steps = 0;
for (var sl = 0; sl < scanlinesPerFrame; sl++) {
this.scanline = sl;
if (!novideo) {
gfx.drawScanline(pixels, sl);
}
- this.runCPU(cpu, cpuCyclesPerLine);
+ steps += this.runCPU(cpu, cpuCyclesPerLine);
}
// visible area is 256x224 (before rotation)
if (!novideo) {
@@ -337,6 +338,7 @@ const _GalaxianPlatform = function(mainElement, options) {
}
// NMI interrupt @ 0x66
if (interruptEnabled) { cpu.NMI(); }
+ return steps;
}
getRasterScanline() { return this.scanline; }
diff --git a/src/platform/nes.ts b/src/platform/nes.ts
index b9c24c4c..738b9a8b 100644
--- a/src/platform/nes.ts
+++ b/src/platform/nes.ts
@@ -156,8 +156,9 @@ class JSNESPlatform extends Base6502Platform implements Platform, Probeable {
pollControls() { this.poller.poll(); }
- advance(novideo : boolean) {
+ advance(novideo : boolean) : number {
this.nes.frame();
+ return 29780; //TODO
}
updateDebugViews() {
@@ -479,7 +480,7 @@ class JSNESPlatform extends Base6502Platform implements Platform, Probeable {
] } };
showHelp(tool:string, ident:string) {
- window.open("https://8bitworkshop.com/blog/platforms/nintendo-nes.md.html", "_help"); // TODO
+ window.open("https://8bitworkshop.com/blog/platforms/nes/", "_help"); // TODO
}
}
diff --git a/src/platform/vcs.ts b/src/platform/vcs.ts
index c96e8625..162da99f 100644
--- a/src/platform/vcs.ts
+++ b/src/platform/vcs.ts
@@ -135,8 +135,9 @@ class VCSPlatform extends BasePlatform {
Javatari.room.speaker.powerOff();
Javatari.room.speaker.powerOn();
}
- advance() {
+ advance() : number {
Javatari.room.console.clockPulse();
+ return 0; //TODO
}
// for unit test
nextFrame() {
@@ -303,7 +304,7 @@ class VCSPlatform extends BasePlatform {
if (tool == 'bataribasic')
window.open("help/bataribasic/manual.html", "_help");
else
- window.open("https://8bitworkshop.com/blog/platforms/atari-2600-vcs.md.html", "_help"); // TODO
+ window.open("https://8bitworkshop.com/blog/platforms/vcs/", "_help"); // TODO
}
getMemoryMap = function() { return {main:[
diff --git a/src/platform/vectrex.ts b/src/platform/vectrex.ts
index ba225757..6612aea8 100644
--- a/src/platform/vectrex.ts
+++ b/src/platform/vectrex.ts
@@ -838,14 +838,16 @@ class VectrexPlatform extends Base6809Platform {
this.psg.psg.register[14] = ~this.inputs[2];
}
- advance(novideo:boolean) {
+ advance(novideo:boolean) : number {
if (!novideo) this.video.clear();
this.updateControls();
this.probe.logNewFrame();
- var cycles = 1500000 / 60;
- while (cycles > 0) {
- cycles -= this.step();
+ var frameCycles = 1500000 / 60;
+ var cycles = 0;
+ while (cycles < frameCycles) {
+ cycles += this.step();
}
+ return cycles;
}
step() {
diff --git a/src/platform/verilog.ts b/src/platform/verilog.ts
index f16936a0..fad258b8 100644
--- a/src/platform/verilog.ts
+++ b/src/platform/verilog.ts
@@ -460,13 +460,14 @@ var VerilogPlatform = function(mainElement, options) {
}
// TODO: merge with prev func
- advance(novideo : boolean) {
+ advance(novideo : boolean) : number {
this.setGenInputs();
this.updateVideoFrameCycles(cyclesPerFrame, true, false);
gen.__unreset();
if (!novideo) {
this.refreshVideoFrame();
}
+ return cyclesPerFrame; //TODO?
}
refreshVideoFrame() {