export const FREQUENCY_DELTA = 100000; export type Callback<T> = (owner: T, params?: any) => void; export type RomInfo = { name?: string; data?: Uint8Array; size?: number; extra?: Record<string, string | undefined>; }; export type BenchmarkResult = { delta: number; count: number; cycles: number; frequency_mhz: number; }; export type Entry = { text: string; url?: string; }; /** * Enumeration to be used to describe the set of * features that a certain emulator supports, this * is going to condition its runtime execution. */ export enum Feature { Debug = 1, Palettes, Benchmark, Keyboard, KeyboardChip8, KeyboardGB } /** * Enumeration that describes the multiple pixel * formats and the associated size in bytes. */ export enum PixelFormat { RGB = 3, RGBA = 4 } export interface ObservableI { bind(event: string, callback: Callback<this>): void; unbind(event: string, callback: Callback<this>): void; trigger(event: string): void; } /** * Top level interface that declares the main abstract * interface of an emulator structured entity. * Should allow typical hardware operations to be performed. */ export interface Emulator extends ObservableI { /** * The descriptive name of the emulator. */ get name(): string; /** * The information on the hardware that is being emulated * by the emulator (eg: Super Nintendo), can contain a URL * that describes the device that is being emulated by * the emulator (eg: Wikipedia link). */ get device(): Entry; /** * A semantic version string for the current version * of the emulator, can include a URL pointing to a * changelog or equivalent document. * * @see {@link https://semver.org} */ get version(): Entry | undefined; /** * Information about the source code repository where * the emulator source code is being stored. */ get repository(): Entry | undefined; /** * The features available and compatible with the emulator, * these values will influence the associated GUIs. */ get features(): Feature[]; /** * The complete set of engine names that can be used * in the re-boot operation. */ get engines(): string[]; /** * The name of the current execution engine being used * by the emulator. */ get engine(): string | null; /** * The complete set of file extensions that this emulator * supports. */ get romExts(): string[]; /** * The pixel format of the emulator's display * image buffer (eg: RGB). */ get pixelFormat(): PixelFormat; /** * Gets the complete image buffer as a sequence of * bytes that respects the current pixel format from * `getPixelFormat()`. This method returns an in memory * pointer to the heap and not a copy. */ get imageBuffer(): Uint8Array; /** * Gets information about the ROM that is currently * loaded in the emulator, using a structure containing * the information about the ROM that is currently * loaded in the emulator. */ get romInfo(): RomInfo; /** * The current CPU frequency (logic) of the emulator, * should impact other elements of the emulator. */ get frequency(): number; set frequency(value: number); /** * The recommended frequency delta in hertz for scale up * and scale down operations in the CPU frequency. */ get frequencyDelta(): number | null; /** * The current logic framerate of the running emulator. */ get framerate(): number; /** * A dictionary that contains the register names associated * with their value either as strings or numbers. */ get registers(): Record<string, string | number>; /** * The palette as a string name that is currently * set in the emulator for display. */ get palette(): string | undefined; set palette(value: string | undefined); /** * Obtains the pixel buffer for the VRAM tile at the given * index. * * @param index The index of the tile to obtain pixel buffer. * @returns The pixel buffer of the tile at the given index. */ getTile(index: number): Uint8Array; /** * Boot (or reboots) the emulator according to the provided * set of options. * * @param options The options that are going to be used for * the booting operation of the emulator. */ boot(options: any): void; /** * Toggle the running state of the emulator between paused * and running, prevents consumers from the need to access * the current running state of the emulator to implement * a logic toggle. */ toggleRunning(): void; pause(): void; resume(): void; /** * Resets the emulator machine to the start state and * re-loads the ROM that is currently set in the emulator. */ reset(): void; keyPress(key: string): void; keyLift(key: string): void; /** * Changes the palette of the emulator to the "next" one, * the order in which the palette is chosen is defined by * the concrete emulator implementation. * * @returns The name of the palette that has been selected. */ changePalette?: { (): string }; /** * Runs a benchmark operation in the emulator, effectively * measuring the performance of it. * * @param count The number of benchmark iterations to be * run, increasing this value will make the benchmark take * more time to be executed. * @returns The result metrics from the benchmark run. */ benchmark?: { (count?: number): BenchmarkResult }; } /** * Abstract class that implements the basic functionality * part of the definition of the Observer pattern. * * @see {@link https://en.wikipedia.org/wiki/Observer_pattern} */ export class Observable { private events: Record<string, [Callback<this>]> = {}; bind(event: string, callback: Callback<this>) { const callbacks = this.events[event] ?? []; if (callbacks.includes(callback)) return; callbacks.push(callback); this.events[event] = callbacks; } unbind(event: string, callback: Callback<this>) { const callbacks = this.events[event] ?? []; if (!callbacks.includes(callback)) return; const index = callbacks.indexOf(callback); callbacks.splice(index, 1); this.events[event] = callbacks; } trigger(event: string, params?: any) { const callbacks = this.events[event] ?? []; callbacks.forEach((c) => c(this, params)); } } export class EmulatorBase extends Observable { get version(): Entry | undefined { return undefined; } get repository(): Entry | undefined { return undefined; } get features(): Feature[] { return []; } get frequencyDelta(): number | null { return FREQUENCY_DELTA; } get palette(): string | undefined { return undefined; } set palette(value: string | undefined) {} }