2019-12-27 23:04:07 +00:00
|
|
|
import Apple2IO from './apple2io';
|
|
|
|
import { HiresPage, LoresPage, VideoModes } from './canvas';
|
|
|
|
import CPU6502 from './cpu6502';
|
|
|
|
import MMU from './mmu';
|
|
|
|
import RAM from './ram';
|
|
|
|
import { debug } from './util';
|
|
|
|
|
|
|
|
import SYMBOLS from './symbols';
|
|
|
|
|
|
|
|
export function Apple2(options) {
|
|
|
|
var stats = {
|
2019-12-30 19:13:35 +00:00
|
|
|
frames: 0,
|
2019-12-27 23:04:07 +00:00
|
|
|
renderedFrames: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
var paused = false;
|
|
|
|
|
|
|
|
var DEBUG = false;
|
|
|
|
var TRACE = false;
|
|
|
|
var MAX_TRACE = 256;
|
|
|
|
var trace = [];
|
|
|
|
|
|
|
|
var runTimer = null;
|
2019-12-28 02:14:33 +00:00
|
|
|
var runAnimationFrame = null;
|
2019-12-27 23:04:07 +00:00
|
|
|
var cpu = new CPU6502({ '65C02': options.enhanced });
|
|
|
|
|
|
|
|
var gr = new LoresPage(1, options.characterRom, options.e, options.screen[0]);
|
|
|
|
var gr2 = new LoresPage(2, options.characterRom, options.e, options.screen[1]);
|
|
|
|
var hgr = new HiresPage(1, options.screen[2]);
|
|
|
|
var hgr2 = new HiresPage(2, options.screen[3]);
|
|
|
|
|
|
|
|
var vm = new VideoModes(gr, hgr, gr2, hgr2, options.e);
|
|
|
|
vm.multiScreen(options.multiScreen);
|
2019-12-27 23:50:40 +00:00
|
|
|
vm.enhanced(options.enhanced);
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
var io = new Apple2IO(cpu, vm);
|
2019-12-29 21:35:00 +00:00
|
|
|
var mmu = null;
|
2019-12-27 23:04:07 +00:00
|
|
|
|
|
|
|
if (options.e) {
|
2019-12-29 21:35:00 +00:00
|
|
|
mmu = new MMU(cpu, vm, gr, gr2, hgr, hgr2, io, options.rom);
|
2019-12-27 23:04:07 +00:00
|
|
|
cpu.addPageHandler(mmu);
|
|
|
|
} else {
|
|
|
|
var ram1 = new RAM(0x00, 0x03),
|
|
|
|
ram2 = new RAM(0x0C, 0x1F),
|
|
|
|
ram3 = new RAM(0x60, 0xBF);
|
|
|
|
|
|
|
|
cpu.addPageHandler(ram1);
|
|
|
|
cpu.addPageHandler(gr);
|
|
|
|
cpu.addPageHandler(gr2);
|
|
|
|
cpu.addPageHandler(ram2);
|
|
|
|
cpu.addPageHandler(hgr);
|
|
|
|
cpu.addPageHandler(hgr2);
|
|
|
|
cpu.addPageHandler(ram3);
|
|
|
|
cpu.addPageHandler(io);
|
|
|
|
cpu.addPageHandler(options.rom);
|
|
|
|
}
|
|
|
|
|
|
|
|
var _requestAnimationFrame =
|
|
|
|
window.requestAnimationFrame ||
|
|
|
|
window.mozRequestAnimationFrame ||
|
|
|
|
window.webkitRequestAnimationFrame ||
|
|
|
|
window.msRequestAnimationFrame;
|
|
|
|
|
2019-12-28 02:14:33 +00:00
|
|
|
var _cancelAnimationFrame =
|
|
|
|
window.cancelAnimationFrame ||
|
|
|
|
window.mozCancelAnimationFrame ||
|
|
|
|
window.webkitCancelAnimationFrame ||
|
|
|
|
window.msCancelAnimationFrame;
|
|
|
|
|
2019-12-27 23:04:07 +00:00
|
|
|
function run() {
|
2019-12-28 02:14:33 +00:00
|
|
|
if (runTimer || runAnimationFrame) {
|
|
|
|
return; // already running
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var interval = 30;
|
|
|
|
|
|
|
|
var now, last = Date.now();
|
|
|
|
var runFn = function() {
|
2019-12-30 03:13:59 +00:00
|
|
|
var kHz = io.getKHz();
|
2019-12-27 23:04:07 +00:00
|
|
|
now = Date.now();
|
|
|
|
|
|
|
|
var step = (now - last) * kHz, stepMax = kHz * interval;
|
|
|
|
last = now;
|
|
|
|
if (step > stepMax) {
|
|
|
|
step = stepMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DEBUG) {
|
|
|
|
cpu.stepCyclesDebug(TRACE ? 1 : step, function() {
|
|
|
|
var line = cpu.dumpRegisters() + ' ' +
|
|
|
|
cpu.dumpPC(undefined, SYMBOLS);
|
|
|
|
if (TRACE) {
|
|
|
|
debug(line);
|
|
|
|
} else {
|
|
|
|
trace.push(line);
|
|
|
|
if (trace.length > MAX_TRACE) {
|
|
|
|
trace.shift();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cpu.stepCycles(step);
|
|
|
|
}
|
2019-12-29 21:35:00 +00:00
|
|
|
if (mmu) {
|
|
|
|
mmu.resetVB();
|
|
|
|
}
|
2019-12-27 23:04:07 +00:00
|
|
|
if (io.annunciator(0)) {
|
|
|
|
if (options.multiScreen) {
|
|
|
|
vm.blit();
|
|
|
|
}
|
|
|
|
if (io.blit()) {
|
|
|
|
stats.renderedFrames++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (vm.blit()) {
|
|
|
|
stats.renderedFrames++;
|
|
|
|
}
|
|
|
|
}
|
2019-12-30 19:13:35 +00:00
|
|
|
stats.frames++;
|
2019-12-27 23:04:07 +00:00
|
|
|
io.tick();
|
|
|
|
options.tick();
|
|
|
|
|
|
|
|
if (!paused && _requestAnimationFrame) {
|
2019-12-28 02:14:33 +00:00
|
|
|
runAnimationFrame = _requestAnimationFrame(runFn);
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if (_requestAnimationFrame) {
|
|
|
|
_requestAnimationFrame(runFn);
|
|
|
|
} else {
|
|
|
|
runTimer = setInterval(runFn, interval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function stop() {
|
|
|
|
if (runTimer) {
|
|
|
|
clearInterval(runTimer);
|
|
|
|
}
|
2019-12-28 02:14:33 +00:00
|
|
|
if (runAnimationFrame) {
|
|
|
|
_cancelAnimationFrame(runAnimationFrame);
|
|
|
|
}
|
2019-12-27 23:04:07 +00:00
|
|
|
runTimer = null;
|
2019-12-28 02:14:33 +00:00
|
|
|
runAnimationFrame = null;
|
2019-12-27 23:04:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function saveState() {
|
|
|
|
var state = {
|
|
|
|
cpu: cpu.getState(),
|
|
|
|
};
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
function restoreState(state) {
|
|
|
|
cpu.setState(state.cpu);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
reset: function () {
|
|
|
|
cpu.reset();
|
|
|
|
},
|
|
|
|
|
|
|
|
run: function () {
|
|
|
|
run();
|
|
|
|
},
|
|
|
|
|
|
|
|
stop: function () {
|
|
|
|
stop();
|
|
|
|
},
|
|
|
|
|
|
|
|
saveState: function () {
|
|
|
|
saveState();
|
|
|
|
},
|
|
|
|
|
|
|
|
restoreState: function () {
|
|
|
|
restoreState();
|
|
|
|
},
|
|
|
|
|
|
|
|
getStats: function () {
|
|
|
|
return stats;
|
|
|
|
},
|
|
|
|
|
|
|
|
getCPU: function () {
|
|
|
|
return cpu;
|
|
|
|
},
|
|
|
|
|
|
|
|
getIO: function () {
|
|
|
|
return io;
|
|
|
|
},
|
|
|
|
|
|
|
|
getVideoModes: function () {
|
|
|
|
return vm;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|