Skip to content

Commit d883a07

Browse files
bfirshclaude
andcommitted
Extract Browser class from React into jsnes package
Adds jsnes.Browser, a framework-agnostic emulator for browsers. Moves canvas rendering, Web Audio, keyboard/gamepad input, and frame timing from web/src/ into src/browser/, with a zero-dependency ring buffer replacing ringbufferjs. The web/ React app now uses Browser as a thin wrapper. Simplifies example/ to use the new class. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 3413b34 commit d883a07

File tree

18 files changed

+417
-410
lines changed

18 files changed

+417
-410
lines changed

eslint.config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ export default [
1414
document: "readonly",
1515
navigator: "readonly",
1616
console: "readonly",
17+
AudioContext: "readonly",
18+
requestAnimationFrame: "readonly",
19+
cancelAnimationFrame: "readonly",
20+
setTimeout: "readonly",
21+
clearTimeout: "readonly",
22+
setInterval: "readonly",
23+
clearInterval: "readonly",
24+
localStorage: "readonly",
25+
Image: "readonly",
26+
XMLHttpRequest: "readonly",
27+
Float32Array: "readonly",
28+
Uint8ClampedArray: "readonly",
29+
Uint32Array: "readonly",
30+
ArrayBuffer: "readonly",
1731
// Node globals
1832
process: "readonly",
1933
Buffer: "readonly",

example/nes-embed.html

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,39 @@
11
<!DOCTYPE html>
2-
32
<html>
4-
<head>
5-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6-
<title>Embedding Example</title>
7-
8-
<script type="text/javascript" src="https://unpkg.com/jsnes/dist/jsnes.min.js"></script>
9-
<script type="text/javascript" src="nes-embed.js"></script>
10-
<script>window.onload = function(){nes_load_url("nes-canvas", "InterglacticTransmissing.nes");}</script>
11-
</head>
12-
<body>
13-
<button id="audio" style="display: none;">Click to enable audio</button>
14-
<div style="margin: auto; width: 75%;">
15-
<canvas id="nes-canvas" width="256" height="240" style="width: 100%"/>
16-
</div>
17-
<p>DPad: Arrow keys<br/>Start: Return, Select: Tab<br/>A Button: A, B Button: S</p>
18-
</body>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5+
<title>Embedding Example</title>
6+
<script
7+
type="text/javascript"
8+
src="https://unpkg.com/jsnes/dist/jsnes.min.js"
9+
></script>
10+
<script>
11+
window.onload = function () {
12+
var container = document.getElementById("nes");
13+
var browser = new jsnes.Browser(container, null, {
14+
onError: function (e) {
15+
console.error(e);
16+
},
17+
});
18+
jsnes.Browser.loadROMFromURL(
19+
"InterglacticTransmissing.nes",
20+
function (err, data) {
21+
if (err) {
22+
console.error(err);
23+
return;
24+
}
25+
browser.loadROM(data);
26+
},
27+
);
28+
};
29+
</script>
30+
</head>
31+
<body>
32+
<div id="nes" style="margin: auto; width: 512px; height: 480px"></div>
33+
<p>
34+
DPad: Arrow keys<br />
35+
Start: Enter, Select: Right Ctrl<br />
36+
A: X, B: Z
37+
</p>
38+
</body>
1939
</html>

example/nes-embed.js

Lines changed: 0 additions & 154 deletions
This file was deleted.

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./src/nes";
22
export * from "./src/controller";
3+
export * from "./src/browser";

src/browser.d.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { NES } from "./nes";
2+
3+
export interface BrowserOptions {
4+
/** Called when the emulator encounters an error during frame execution. */
5+
onError?: (error: Error) => void;
6+
/** Called when battery-backed SRAM is written. */
7+
onBatteryRamWrite?: (address: number, value: number) => void;
8+
}
9+
10+
export class Browser {
11+
/** The underlying NES instance. */
12+
readonly nes: NES;
13+
/** The keyboard controller for configuring key mappings. */
14+
readonly keyboard: {
15+
keys: Record<number, [number, number, string]>;
16+
loadKeys: () => void;
17+
setKeys: (keys: Record<number, [number, number, string]>) => void;
18+
};
19+
/** The gamepad controller for configuring gamepad mappings. */
20+
readonly gamepad: {
21+
gamepadConfig: unknown;
22+
loadGamepadConfig: () => void;
23+
setGamepadConfig: (config: unknown) => void;
24+
promptButton: (callback: ((buttonInfo: unknown) => void) | null) => void;
25+
};
26+
27+
constructor(
28+
container: HTMLElement,
29+
romData?: string | null,
30+
options?: BrowserOptions,
31+
);
32+
33+
/** Start emulation. Called automatically if romData is provided to constructor. */
34+
start(): void;
35+
/** Pause emulation. */
36+
stop(): void;
37+
/** Load a new ROM and start emulation. */
38+
loadROM(data: string): void;
39+
/** Re-layout the canvas to fill its container. */
40+
fitInParent(): void;
41+
/** Get a screenshot as an HTMLImageElement. */
42+
screenshot(): HTMLImageElement;
43+
/** Clean up all resources: stop emulation, remove listeners, remove canvas. */
44+
destroy(): void;
45+
46+
/** Load ROM data from a URL via XHR. */
47+
static loadROMFromURL(
48+
url: string,
49+
callback: (error: Error | null, data?: string) => void,
50+
): XMLHttpRequest;
51+
}
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { debug } from "./debug";
1+
// Debug logging, enabled via localStorage.jsnes_debug = 1
2+
let debugEnabled = false;
3+
try {
4+
debugEnabled = !!localStorage.getItem("jsnes_debug");
5+
} catch {
6+
// localStorage not available
7+
}
28

39
const FPS = 60.098;
410

@@ -55,7 +61,6 @@ export default class FrameTimer {
5561

5662
// This can happen a lot on a 144Hz display
5763
if (numFrames === 0) {
58-
//console.log("WOAH, no frames");
5964
return;
6065
}
6166

@@ -76,6 +81,8 @@ export default class FrameTimer {
7681
(i * timeToNextFrame) / numFrames,
7782
);
7883
}
79-
if (numFrames > 1) debug("SKIP", numFrames - 1, this.lastFrameTime);
84+
if (numFrames > 1 && debugEnabled) {
85+
console.log("SKIP", numFrames - 1, this.lastFrameTime);
86+
}
8087
};
8188
}

0 commit comments

Comments
 (0)