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.
- 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)
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?

View File

@ -19,8 +19,16 @@ export interface CpuState {
A:number, X:number, Y:number, SP:number, R:boolean,
N,V,D,Z,C:boolean*/
};
export interface EmuState {c:CpuState, b?:number[]};
export type DisasmLine = {line:string, nbytes:number};
export interface EmuState {
c:CpuState, // CPU state
b?:number[] // RAM
};
export interface EmuControlsState {
}
export type DisasmLine = {
line:string,
nbytes:number
};
export interface Platform {
start() : void;
@ -60,6 +68,8 @@ export interface Platform {
setRecorder?(recorder : EmuRecorder) : void;
advance?(novideo? : boolean) : void;
loadControlsState?(state : EmuControlsState) : void;
saveControlsState?() : EmuControlsState;
}
export interface Preset {
@ -80,7 +90,7 @@ type BreakpointCallback = (EmuState) => void;
export interface EmuRecorder {
frameRequested() : boolean;
recordFrame(platform : Platform, state : EmuState);
recordFrame(state : EmuState);
}
/////
@ -132,7 +142,7 @@ abstract class BaseDebugPlatform {
updateRecorder() {
// are we recording and do we need to save a frame?
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},
};
}
this.loadControlsState = function(state) {
kbdlatch = state.kbd;
}
this.saveControlsState = function() {
return {
kbd:kbdlatch,
};
}
this.getCPUState = function() {
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 stateRecorder : StateRecorderImpl = new StateRecorderImpl();
var stateRecorder : StateRecorderImpl;
// TODO: codemirror multiplex support?
var TOOL_TO_SOURCE_STYLE = {
@ -771,7 +771,7 @@ function setupDebugControls(){
};
replayslider.on('input', function(e) {
_pause();
stateRecorder.loadFrame(platform, (<any>e.target).value);
stateRecorder.loadFrame((<any>e.target).value);
});
$("#replay_bar").show();
}
@ -908,6 +908,7 @@ function addPageFocusHandlers() {
function startPlatform() {
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
platform = new PLATFORMS[platform_id]($("#emulator")[0]);
stateRecorder = new StateRecorderImpl(platform);
PRESETS = platform.getPresets();
if (qs['file']) {
// start platform and load file