apple2js/js/apple2.ts
Ian Flanigan b80436d99c
More typescript conversion (#46)
* Convert js/ram to a class

* Convert js/mmu to Typescript

* Convert js/apple2io to Typescript

* Convert js/canvas to Typescript

* Use new types in js/mmu

* Rename js/symbols.js to js/symbols.ts

* Remove the difference between readPages and writePages

As @whscullin said in PR #38, there's no need to have both readable
and writable pages since all implementations are currently both. This
change combines them into `Page`. Likewise, `PageHandler` now extends
`Page`.

`Apple2IO` now implements `PageHandler`. This caught a bug where `end`
had been renamed `endend` by mistake.

There are a few other formatting changes as well.

* Convert js/apple2 to Typescript

* Convert js/prefs to Typescript

* Convert all of the ROMs in js/roms to Typescript

Now all of the ROMs are classes that extend the ROM class. There is
some rudamentary checking to make sure that the length of the ROM
matches the declared start and end pages. (This caught what looks to
be an error in roms/apple2e, but it's hard for me to tell.)

The typing also caught an error where the character ROM was being
used for the main ROM for the apple2j version.

* Convert js/roms/cards/* to Typescript

* Convert js/formats/format_utils to Typescript

This change also seems to fix a bug with `.po` image files that
weren't being read correctly.
2020-11-24 08:48:14 -08:00

199 lines
5.4 KiB
TypeScript

import Apple2IO from './apple2io';
import { HiresPage, LoresPage, VideoModes } from './canvas';
import CPU6502, { PageHandler, CpuState } from './cpu6502';
import MMU from './mmu';
import RAM from './ram';
import { debug } from './util';
import SYMBOLS from './symbols';
import { Restorable } from './types';
interface Options {
characterRom: any,
enhanced: boolean,
e: boolean,
multiScreen: boolean,
rom: PageHandler,
screen: any[],
tick: () => void,
}
interface State {
cpu: CpuState,
}
export class Apple2 implements Restorable<State> {
private paused = false;
private DEBUG = false;
private TRACE = false;
private MAX_TRACE = 256;
private trace: string[] = [];
private runTimer: number | null = null;
private runAnimationFrame: number | null = null;
private cpu: CPU6502;
private gr: LoresPage;
private gr2: LoresPage;
private hgr: HiresPage;
private hgr2: HiresPage;
private vm: VideoModes;
private io: Apple2IO;
private mmu: MMU;
private multiScreen: boolean;
private tick: () => void;
private stats = {
frames: 0,
renderedFrames: 0
};
constructor(options: Options) {
this.cpu = new CPU6502({ '65C02': options.enhanced })
this.gr = new LoresPage(1, options.characterRom, options.e, options.screen[0]);
this.gr2 = new LoresPage(2, options.characterRom, options.e, options.screen[1]);
this.hgr = new HiresPage(1, options.screen[2]);
this.hgr2 = new HiresPage(2, options.screen[3]);
this.vm = new VideoModes(this.gr, this.hgr, this.gr2, this.hgr2, options.e);
this.vm.multiScreen(options.multiScreen);
this.vm.enhanced(options.enhanced);
this.io = new Apple2IO(this.cpu, this.vm);
this.multiScreen = options.multiScreen;
this.tick = options.tick;
if (options.e) {
this.mmu = new MMU(this.cpu, this.vm, this.gr, this.gr2, this.hgr, this.hgr2, this.io, options.rom);
this.cpu.addPageHandler(this.mmu);
} else {
let ram1 = new RAM(0x00, 0x03);
let ram2 = new RAM(0x0C, 0x1F);
let ram3 = new RAM(0x60, 0xBF);
this.cpu.addPageHandler(ram1);
this.cpu.addPageHandler(this.gr);
this.cpu.addPageHandler(this.gr2);
this.cpu.addPageHandler(ram2);
this.cpu.addPageHandler(this.hgr);
this.cpu.addPageHandler(this.hgr2);
this.cpu.addPageHandler(ram3);
this.cpu.addPageHandler(this.io);
this.cpu.addPageHandler(options.rom);
}
}
/**
* Runs the emulator. If the emulator is already running, this does
* nothing. When this function exits either `runTimer` or
* `runAnimationFrame` will be non-null.
*/
run() {
if (this.runTimer || this.runAnimationFrame) {
return; // already running
}
let interval = 30;
let now, last = Date.now();
let runFn = () => {
let kHz = this.io.getKHz();
now = Date.now();
let step = (now - last) * kHz, stepMax = kHz * interval;
last = now;
if (step > stepMax) {
step = stepMax;
}
if (this.DEBUG) {
this.cpu.stepCyclesDebug(this.TRACE ? 1 : step, () => {
let line = this.cpu.dumpRegisters() + ' ' +
this.cpu.dumpPC(undefined, SYMBOLS);
if (this.TRACE) {
debug(line);
} else {
this.trace.push(line);
if (this.trace.length > this.MAX_TRACE) {
this.trace.shift();
}
}
});
} else {
this.cpu.stepCycles(step);
}
if (this.mmu) {
this.mmu.resetVB();
}
if (this.io.annunciator(0)) {
if (this.multiScreen) {
this.vm.blit();
}
if (this.io.blit()) {
this.stats.renderedFrames++;
}
} else {
if (this.vm.blit()) {
this.stats.renderedFrames++;
}
}
this.stats.frames++;
this.io.tick();
this.tick();
if (!this.paused && requestAnimationFrame) {
this.runAnimationFrame = requestAnimationFrame(runFn);
}
};
if (requestAnimationFrame) {
this.runAnimationFrame = requestAnimationFrame(runFn);
} else {
this.runTimer = window.setInterval(runFn, interval);
}
}
stop() {
if (this.runTimer) {
clearInterval(this.runTimer);
}
if (this.runAnimationFrame) {
cancelAnimationFrame(this.runAnimationFrame);
}
this.runTimer = null;
this.runAnimationFrame = null;
}
getState(): State {
let state: State = {
cpu: this.cpu.getState(),
};
return state;
}
setState(state: State) {
this.cpu.setState(state.cpu);
}
reset() {
this.cpu.reset();
}
getStats() {
return this.stats;
}
getCPU() {
return this.cpu;
}
getIO() {
return this.io;
}
getVideoModes() {
return this.vm;
}
}