From 6ffd97ceeca59c88c17ea4a2970660fda111654b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Sat, 29 Oct 2022 01:19:39 +0100
Subject: [PATCH] feat: added frame event trigger

---
 examples/web/index.ts      |  5 +++--
 examples/web/react/app.tsx | 31 ++++++++++++++++++++++++++++---
 2 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/examples/web/index.ts b/examples/web/index.ts
index f1e48f98..f7951ea9 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 a70e8960..ab92d0f9 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()}`;
-- 
GitLab