1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2025-02-27 13:29:32 +00:00

load/saveControlsState() support in recorder

This commit is contained in:
Steven Hugg 2018-08-22 14:50:10 -04:00
parent d9c41ca9d7
commit 86823c2c21
5 changed files with 109 additions and 6 deletions

View File

@ -54,6 +54,7 @@ TODO:
- make sure we don't store files in local storage unnecc. - make sure we don't store files in local storage unnecc.
- state buffer/replay - state buffer/replay
- intro/help text for each platform - intro/help text for each platform
- make sure controls work with replay feature (we'll have to save control state every frame)
FYI: Image links for the books on http://8bitworkshop.com/ are broken FYI: Image links for the books on http://8bitworkshop.com/ are broken
On the website the additional grey spacing next to the program line numbers is not dynamically resized when the web browser window size is changed. Intentional? On the website the additional grey spacing next to the program line numbers is not dynamically resized when the web browser window size is changed. Intentional?

View File

@ -19,8 +19,16 @@ export interface CpuState {
A:number, X:number, Y:number, SP:number, R:boolean, A:number, X:number, Y:number, SP:number, R:boolean,
N,V,D,Z,C:boolean*/ N,V,D,Z,C:boolean*/
}; };
export interface EmuState {c:CpuState, b?:number[]}; export interface EmuState {
export type DisasmLine = {line:string, nbytes:number}; c:CpuState, // CPU state
b?:number[] // RAM
};
export interface EmuControlsState {
}
export type DisasmLine = {
line:string,
nbytes:number
};
export interface Platform { export interface Platform {
start() : void; start() : void;
@ -60,6 +68,8 @@ export interface Platform {
setRecorder?(recorder : EmuRecorder) : void; setRecorder?(recorder : EmuRecorder) : void;
advance?(novideo? : boolean) : void; advance?(novideo? : boolean) : void;
loadControlsState?(state : EmuControlsState) : void;
saveControlsState?() : EmuControlsState;
} }
export interface Preset { export interface Preset {
@ -80,7 +90,7 @@ type BreakpointCallback = (EmuState) => void;
export interface EmuRecorder { export interface EmuRecorder {
frameRequested() : boolean; frameRequested() : boolean;
recordFrame(platform : Platform, state : EmuState); recordFrame(state : EmuState);
} }
///// /////
@ -132,7 +142,7 @@ abstract class BaseDebugPlatform {
updateRecorder() { updateRecorder() {
// are we recording and do we need to save a frame? // are we recording and do we need to save a frame?
if (this.recorder && (<Platform><any>this).isRunning() && this.recorder.frameRequested()) { if (this.recorder && (<Platform><any>this).isRunning() && this.recorder.frameRequested()) {
this.recorder.recordFrame(<Platform><any>this, this.saveState()); this.recorder.recordFrame(this.saveState());
} }
} }
} }

View File

@ -335,6 +335,14 @@ var Apple2Platform = function(mainElement) {
lc:{s:auxRAMselected,b:auxRAMbank,w:writeinhibit}, lc:{s:auxRAMselected,b:auxRAMbank,w:writeinhibit},
}; };
} }
this.loadControlsState = function(state) {
kbdlatch = state.kbd;
}
this.saveControlsState = function() {
return {
kbd:kbdlatch,
};
}
this.getCPUState = function() { this.getCPUState = function() {
return cpu.saveState(); return cpu.saveState();
} }

83
src/recorder.ts Normal file
View File

@ -0,0 +1,83 @@
import { Platform, EmuState, EmuControlsState, EmuRecorder } from "./baseplatform";
export class StateRecorderImpl implements EmuRecorder {
checkpointInterval : number = 60;
callbackStateChanged : () => void;
maxCheckpoints : number = 120;
platform : Platform;
buffer : EmuState[];
controls : EmuControlsState[];
frameCount : number;
lastSeekFrame : number = -1;
constructor(platform : Platform) {
this.reset();
this.platform = platform;
}
reset() {
this.buffer = [];
this.controls = [];
this.frameCount = 0;
this.lastSeekFrame = -1;
}
frameRequested() : boolean {
// buffer full?
if (this.buffer.length >= this.maxCheckpoints) {
return false;
}
// record the control state, if available
if (this.platform.saveControlsState) {
this.controls.push(this.platform.saveControlsState());
}
// 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.buffer = this.buffer.slice(0, Math.floor((this.frameCount + this.checkpointInterval - 1) / this.checkpointInterval));
this.controls = this.controls.slice(0, this.frameCount);
}
// time to save next frame?
this.frameCount++;
if (this.callbackStateChanged) {
this.callbackStateChanged();
}
return (this.frameCount % this.checkpointInterval) == 0;
}
numFrames() : number {
return this.frameCount;
}
recordFrame(state : EmuState) {
this.buffer.push(state);
}
getStateAtOrBefore(frame : number) : {frame : number, state : EmuState} {
var bufidx = Math.floor(frame / this.checkpointInterval);
var foundidx = bufidx < this.buffer.length ? bufidx : this.buffer.length-1;
var foundframe = foundidx * this.checkpointInterval;
return {frame:foundframe, state:this.buffer[foundidx]};
}
loadFrame(seekframe : number) {
let {frame,state} = this.getStateAtOrBefore(seekframe);
if (state) {
this.platform.pause();
this.platform.loadState(state);
while (frame < seekframe) {
if (frame < this.controls.length) {
this.platform.loadControlsState(this.controls[frame]);
}
this.platform.advance(true); // TODO: infinite loop?
frame++;
}
this.platform.advance();
this.lastSeekFrame = seekframe;
}
}
}

View File

@ -30,7 +30,7 @@ var current_project : CodeProject; // current CodeProject object
var projectWindows : ProjectWindows; // window manager var projectWindows : ProjectWindows; // window manager
var stateRecorder : StateRecorderImpl = new StateRecorderImpl(); var stateRecorder : StateRecorderImpl;
// TODO: codemirror multiplex support? // TODO: codemirror multiplex support?
var TOOL_TO_SOURCE_STYLE = { var TOOL_TO_SOURCE_STYLE = {
@ -771,7 +771,7 @@ function setupDebugControls(){
}; };
replayslider.on('input', function(e) { replayslider.on('input', function(e) {
_pause(); _pause();
stateRecorder.loadFrame(platform, (<any>e.target).value); stateRecorder.loadFrame((<any>e.target).value);
}); });
$("#replay_bar").show(); $("#replay_bar").show();
} }
@ -908,6 +908,7 @@ function addPageFocusHandlers() {
function startPlatform() { function startPlatform() {
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'."); if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
platform = new PLATFORMS[platform_id]($("#emulator")[0]); platform = new PLATFORMS[platform_id]($("#emulator")[0]);
stateRecorder = new StateRecorderImpl(platform);
PRESETS = platform.getPresets(); PRESETS = platform.getPresets();
if (qs['file']) { if (qs['file']) {
// start platform and load file // start platform and load file