potatis/nes-wasm/www/index.ts
2023-04-23 13:02:58 +02:00

124 lines
No EOL
2.9 KiB
TypeScript

import wasmInit, { NesWasm, KeyState } from "nes-wasm";
const keymap: { [key: string]: number } = {
['l']: 7,
['k']: 6,
[' ']: 5,
['Enter']: 4,
['w']: 3,
['s']: 2,
['a']: 1,
['d']: 0,
};
export class BrowserNes {
nes: NesWasm;
mem: WebAssembly.Memory;
ctx: CanvasRenderingContext2D;
frame_ready: boolean;
keyboard: KeyState[];
killed: boolean;
req_frame: number;
delay_ms: number;
constructor(ctx: CanvasRenderingContext2D, mem: WebAssembly.Memory, bincart: Uint8Array) {
this.nes = NesWasm.new(this, bincart);
this.ctx = ctx;
this.mem = mem;
this.frame_ready = false;
this.keyboard = new Array(8).fill(KeyState.None);
document.addEventListener('keydown', (ev) => {
let btn = keymap[ev.key];
if (btn != null) {
this.keyboard[btn] = KeyState.Pressed;
ev.preventDefault();
}
})
document.addEventListener('keyup', (ev) => {
let btn = keymap[ev.key];
if (btn != null) {
this.keyboard[btn] = KeyState.Released;
// LOL
setTimeout(() => {
this.keyboard[btn] = KeyState.None;
}, 20);
ev.preventDefault();
}
})
}
poll_keyboard(ptr: number) {
let state = new Uint8ClampedArray(this.mem.buffer, ptr, 8);
state.set(this.keyboard)
}
on_frame_ready(frame_ptr: number, len: number) {
const buf = new Uint8ClampedArray(this.mem.buffer, frame_ptr, len);
this.ctx.putImageData(new ImageData(buf, 240, 224), 0, 0);
this.frame_ready = true;
}
delay(millis: number) {
this.delay_ms = millis;
}
loop() {
const inner = () => {
this.frame_ready = false;
while (!this.frame_ready) {
this.nes.tick();
}
if (!this.killed) {
if (this.delay_ms != 0) {
setTimeout(() => {
this.delay_ms = 0;
this.req_frame = requestAnimationFrame(inner);
}, this.delay_ms);
}
else {
this.req_frame = requestAnimationFrame(inner);
}
}
}
this.req_frame = requestAnimationFrame(inner);
}
kill() {
cancelAnimationFrame(this.req_frame);
this.killed = true;
}
}
const c = document.getElementById('c');
const ctx = c.getContext('2d');
const wasm = await wasmInit();
let instance = new BrowserNes(ctx, wasm.memory, []); // empty bin == nestest
instance.loop();
c.focus();
document.getElementById('file_field').addEventListener('change', (input: any) => {
const file: File = input.target.files[0]
const reader = new FileReader()
reader.onload = async () => {
if (instance != null) {
instance.kill();
instance = null;
}
const bincart = new Uint8Array(reader.result)
instance = new BrowserNes(ctx, wasm.memory, bincart);
instance.loop();
c.focus();
}
reader.onerror = () => {
alert(reader.error)
}
if (file) {
reader.readAsArrayBuffer(file)
}
})