diff --git a/examples/web/index.ts b/examples/web/index.ts index f1e48f98e1d7ed0f4e670155cbbaed4839e04ed5..f7951ea9aeeef2b1d63ba8c2ed9d72de0d179cfe 100644 --- a/examples/web/index.ts +++ b/examples/web/index.ts @@ -1,4 +1,4 @@ -import { Emulator, PixelFormat, startApp } from "./react/app"; +import { Emulator, Observable, PixelFormat, startApp } from "./react/app"; import { default as _wasm, GameBoy, PadKey, PpuMode } from "./lib/boytacean.js"; import info from "./package.json"; @@ -48,7 +48,7 @@ const ROM_PATH = require("../../res/roms/20y.gb"); * and "joins" all the elements together to bring input/output * of the associated machine. */ -class GameboyEmulator implements Emulator { +class GameboyEmulator extends Observable implements Emulator { /** * The Game Boy engine (probably coming from WASM) that * is going to be used for the emulation. @@ -218,6 +218,7 @@ class GameboyEmulator implements Emulator { this.gameBoy!.frame_buffer_eager(), PixelFormat.RGB ); + this.trigger("frame"); lastFrame = this.gameBoy!.ppu_frame(); } } diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx index a70e8960e819fb25b9ed5363a54d798c340a95fc..ab92d0f9dd57079f8f66960ccd0e2439f428a020 100644 --- a/examples/web/react/app.tsx +++ b/examples/web/react/app.tsx @@ -22,12 +22,36 @@ import { import "./app.css"; +export type Callback<T> = (owner: T) => void; + +/** + * Abstract class that implements the basic functionality + * part of the definition of the observable 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; + } + + trigger(event: string) { + const callbacks = this.events[event] ?? []; + callbacks.forEach((c) => c(this)); + } +} + /** * 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 { +export interface Emulator extends Observable { getName(): string; getVersion(): string; getVersionUrl(): string; @@ -80,9 +104,10 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => { }; const onDrawHandler = (handler: DrawHandler) => { if (intervalRef.current) return; - intervalRef.current = setInterval(() => { + intervalRef.current = 1; + emulator.bind("frame", () => { handler(emulator.getImageBuffer(), PixelFormat.RGB); - }, 1000); + }); }; useEffect(() => { document.body.style.backgroundColor = `#${getBackground()}`;