From 8589697058182df7f377ac66e8eeb2978338778a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Thu, 10 Nov 2022 14:45:55 +0000
Subject: [PATCH] feat: initial support for register printing

---
 examples/web/index.ts                         | 17 +++++++
 examples/web/react/app.css                    |  2 +-
 examples/web/react/app.tsx                    | 37 ++++++++++++--
 .../web/react/components/canvas/canvas.tsx    |  4 +-
 examples/web/react/components/index.ts        |  1 +
 .../keyboard-chip8/keyboard-chip8.tsx         |  5 +-
 .../components/keyboard-gb/keyboard-gb.tsx    |  5 +-
 .../components/registers-gb/registers-gb.css  | 28 ++++++++++
 .../components/registers-gb/registers-gb.tsx  | 51 +++++++++++++++++++
 examples/web/react/components/tiles/tiles.css |  5 +-
 examples/web/react/components/tiles/tiles.tsx | 19 +++++--
 src/cpu.rs                                    |  4 ++
 src/gb.rs                                     | 29 +++++++++++
 13 files changed, 189 insertions(+), 18 deletions(-)
 create mode 100644 examples/web/react/components/registers-gb/registers-gb.css
 create mode 100644 examples/web/react/components/registers-gb/registers-gb.tsx

diff --git a/examples/web/index.ts b/examples/web/index.ts
index 036d5c0d..75de6736 100644
--- a/examples/web/index.ts
+++ b/examples/web/index.ts
@@ -538,6 +538,23 @@ class GameboyEmulator extends EmulatorBase implements Emulator {
             data: romData
         };
     }
+
+    // @todo move this out of here
+    get registers(): Record<string, string | number> {
+        const registers = this.gameBoy?.registers();
+        if (!registers) return {};
+        return {
+            pc: registers.pc,
+            sp: registers.sp,
+            a: registers.a,
+            b: registers.b,
+            c: registers.c,
+            d: registers.d,
+            e: registers.e,
+            h: registers.h,
+            l: registers.l
+        };
+    }
 }
 
 declare global {
diff --git a/examples/web/react/app.css b/examples/web/react/app.css
index d44f28a6..067b2015 100644
--- a/examples/web/react/app.css
+++ b/examples/web/react/app.css
@@ -5,7 +5,7 @@
 }
 
 .app h3 {
-    margin: 10px 0px 10px 0px;
+    margin: 0px 0px 16px 0px;
 }
 
 .app .display-container {
diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx
index ad01897b..ae936530 100644
--- a/examples/web/react/app.tsx
+++ b/examples/web/react/app.tsx
@@ -20,6 +20,7 @@ import {
     Pair,
     PanelSplit,
     Paragraph,
+    RegistersGB,
     Section,
     Tiles,
     Title,
@@ -97,6 +98,10 @@ export interface Emulator extends ObservableI {
      */
     get device(): string;
 
+    /**
+     * A URL to a website that describes the device that is
+     * being emulated by the emulator (eg: Wikipedia link).
+     */
     get deviceUrl(): string | undefined;
 
     /**
@@ -159,6 +164,8 @@ export interface Emulator extends ObservableI {
      */
     get framerate(): number;
 
+    get registers(): Record<string, string | number>;
+
     getTile(index: number): Uint8Array;
 
     /**
@@ -536,11 +543,31 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                 </Section>
                 {debugVisible && (
                     <Section>
-                        <h3>VRAM Tiles</h3>
-                        <Tiles
-                            getTile={(index) => emulator.getTile(index)}
-                            tileCount={384}
-                        />
+                        <div
+                            style={{
+                                display: "inline-block",
+                                verticalAlign: "top",
+                                marginRight: 32,
+                                width: 256
+                            }}
+                        >
+                            <h3>VRAM Tiles</h3>
+                            <Tiles
+                                getTile={(index) => emulator.getTile(index)}
+                                tileCount={384}
+                                width={"100%"}
+                                contentBox={false}
+                            />
+                        </div>
+                        <div
+                            style={{
+                                display: "inline-block",
+                                verticalAlign: "top"
+                            }}
+                        >
+                            <h3>Registers</h3>
+                            <RegistersGB registers={emulator.registers} />
+                        </div>
                     </Section>
                 )}
                 {infoVisible && (
diff --git a/examples/web/react/components/canvas/canvas.tsx b/examples/web/react/components/canvas/canvas.tsx
index 24f7544a..5cad1d5c 100644
--- a/examples/web/react/components/canvas/canvas.tsx
+++ b/examples/web/react/components/canvas/canvas.tsx
@@ -12,6 +12,7 @@ export type CanvasStructure = {
 type CanvasProps = {
     width: number;
     height: number;
+    scaledWidth?: number | string;
     scale?: number;
     style?: string[];
     onCanvas?: (structure: CanvasStructure) => void;
@@ -20,6 +21,7 @@ type CanvasProps = {
 export const Canvas: FC<CanvasProps> = ({
     width,
     height,
+    scaledWidth,
     scale = 1,
     style = [],
     onCanvas
@@ -41,7 +43,7 @@ export const Canvas: FC<CanvasProps> = ({
         <canvas
             ref={canvasRef}
             className={classes()}
-            style={{ width: width * scale }}
+            style={{ width: scaledWidth ?? width * scale }}
             width={width}
             height={height}
         />
diff --git a/examples/web/react/components/index.ts b/examples/web/react/components/index.ts
index 838636b9..a2980dab 100644
--- a/examples/web/react/components/index.ts
+++ b/examples/web/react/components/index.ts
@@ -14,6 +14,7 @@ export * from "./overlay/overlay";
 export * from "./pair/pair";
 export * from "./panel-split/panel-split";
 export * from "./paragraph/paragraph";
+export * from "./registers-gb/registers-gb";
 export * from "./section/section";
 export * from "./tiles/tiles";
 export * from "./title/title";
diff --git a/examples/web/react/components/keyboard-chip8/keyboard-chip8.tsx b/examples/web/react/components/keyboard-chip8/keyboard-chip8.tsx
index 99ddd4f8..2208c1c1 100644
--- a/examples/web/react/components/keyboard-chip8/keyboard-chip8.tsx
+++ b/examples/web/react/components/keyboard-chip8/keyboard-chip8.tsx
@@ -18,11 +18,10 @@ export const KeyboardChip8: FC<KeyboardChip8Props> = ({
     const classes = () => ["keyboard", "keyboard-chip8", ...style].join(" ");
     const renderKey = (key: string, styles: string[] = []) => {
         const [pressed, setPressed] = useState(false);
+        const classes = ["key", pressed ? "pressed" : "", ...styles].join(" ");
         return (
             <span
-                className={["key", pressed ? "pressed" : "", ...styles].join(
-                    " "
-                )}
+                className={classes}
                 key={key}
                 tabIndex={focusable ? 0 : undefined}
                 onKeyDown={(event) => {
diff --git a/examples/web/react/components/keyboard-gb/keyboard-gb.tsx b/examples/web/react/components/keyboard-gb/keyboard-gb.tsx
index 18672a13..2d7f6065 100644
--- a/examples/web/react/components/keyboard-gb/keyboard-gb.tsx
+++ b/examples/web/react/components/keyboard-gb/keyboard-gb.tsx
@@ -24,11 +24,10 @@ export const KeyboardGB: FC<KeyboardGBProps> = ({
         styles: string[] = []
     ) => {
         const [pressed, setPressed] = useState(false);
+        const classes = ["key", pressed ? "pressed" : "", ...styles].join(" ");
         return (
             <span
-                className={["key", pressed ? "pressed" : "", ...styles].join(
-                    " "
-                )}
+                className={classes}
                 key={keyName ?? key}
                 tabIndex={focusable ? 0 : undefined}
                 onKeyDown={(event) => {
diff --git a/examples/web/react/components/registers-gb/registers-gb.css b/examples/web/react/components/registers-gb/registers-gb.css
new file mode 100644
index 00000000..26765c11
--- /dev/null
+++ b/examples/web/react/components/registers-gb/registers-gb.css
@@ -0,0 +1,28 @@
+.registers-gb > .section {
+    display: inline-block;
+    margin-right: 32px;
+    vertical-align: top;
+}
+
+.registers-gb > .section:last-child {
+    margin-right: 0px;
+}
+
+.registers-gb > .section > h4 {
+    font-size: 22px;
+    margin: 0px 0px 8px 0px;
+}
+
+.registers-gb > .section > .register {
+    font-size: 20px;
+    line-height: 24px;
+}
+
+.registers-gb > .section > .register > .register-key {
+    display: inline-block;
+    width: 70px;
+}
+
+.registers-gb > .section > .register > .register-value {
+    display: inline-block;
+}
diff --git a/examples/web/react/components/registers-gb/registers-gb.tsx b/examples/web/react/components/registers-gb/registers-gb.tsx
new file mode 100644
index 00000000..02186799
--- /dev/null
+++ b/examples/web/react/components/registers-gb/registers-gb.tsx
@@ -0,0 +1,51 @@
+import React, { FC } from "react";
+
+import "./registers-gb.css";
+
+type RegistersGBProps = {
+    registers: Record<string, string | number>;
+    style?: string[];
+};
+
+export const RegistersGB: FC<RegistersGBProps> = ({
+    registers,
+    style = []
+}) => {
+    const classes = () => ["registers-gb", ...style].join(" ");
+    const renderRegister = (
+        key: string,
+        value: number,
+        styles: string[] = []
+    ) => {
+        const classes = ["register", ...styles].join(" ");
+        return (
+            <div className={classes}>
+                <span className="register-key">{key}</span>
+                <span className="register-value">0x{value.toString(16)}</span>
+            </div>
+        );
+    };
+    return (
+        <div className={classes()}>
+            <div className="section">
+                <h4>CPU</h4>
+                {renderRegister("PC", registers.pc as number)}
+                {renderRegister("SP", registers.sp as number)}
+                {renderRegister("A", registers.a as number)}
+                {renderRegister("B", registers.b as number)}
+                {renderRegister("C", registers.c as number)}
+                {renderRegister("D", registers.d as number)}
+                {renderRegister("E", registers.e as number)}
+                {renderRegister("H", registers.h as number)}
+                {renderRegister("L", registers.l as number)}
+            </div>
+            <div className="section">
+                <h4>PPU</h4>
+                {renderRegister("LY", registers.l as number)}
+                {renderRegister("LYC", registers.l as number)}
+            </div>
+        </div>
+    );
+};
+
+export default RegistersGB;
diff --git a/examples/web/react/components/tiles/tiles.css b/examples/web/react/components/tiles/tiles.css
index 59b7d182..030a9575 100644
--- a/examples/web/react/components/tiles/tiles.css
+++ b/examples/web/react/components/tiles/tiles.css
@@ -1,11 +1,14 @@
 .tiles > .canvas {
     background-color: #1b1a17;
     border: 2px solid #50cb93;
+    padding: 8px 8px 8px 8px;
+}
+
+.tiles > .canvas.content-box {
     box-sizing: content-box;
     -o-box-sizing: content-box;
     -ms-box-sizing: content-box;
     -moz-box-sizing: content-box;
     -khtml-box-sizing: content-box;
     -webkit-box-sizing: content-box;
-    padding: 8px 8px 8px 8px;
 }
diff --git a/examples/web/react/components/tiles/tiles.tsx b/examples/web/react/components/tiles/tiles.tsx
index e879f383..483b6cf0 100644
--- a/examples/web/react/components/tiles/tiles.tsx
+++ b/examples/web/react/components/tiles/tiles.tsx
@@ -5,19 +5,24 @@ import Canvas, { CanvasStructure } from "../canvas/canvas";
 import "./tiles.css";
 
 type TilesProps = {
-    tileCount: number;
     getTile: (index: number) => Uint8Array;
+    tileCount: number;
+    width?: number | string;
+    contentBox?: boolean;
     interval?: number;
     style?: string[];
 };
 
 export const Tiles: FC<TilesProps> = ({
-    tileCount,
     getTile,
+    tileCount,
+    width,
+    contentBox = true,
     interval = 500,
     style = []
 }) => {
-    const classes = () => ["tiles", ...style].join(" ");
+    const classes = () =>
+        ["tiles", contentBox ? "content-box" : "", ...style].join(" ");
     const intervalsRef = useRef<number>();
     useEffect(() => {
         return () => {
@@ -38,7 +43,13 @@ export const Tiles: FC<TilesProps> = ({
     };
     return (
         <div className={classes()}>
-            <Canvas width={128} height={192} scale={2} onCanvas={onCanvas} />
+            <Canvas
+                width={128}
+                height={192}
+                scale={2}
+                scaledWidth={width}
+                onCanvas={onCanvas}
+            />
         </div>
     );
 };
diff --git a/src/cpu.rs b/src/cpu.rs
index e253dba6..f4693aba 100644
--- a/src/cpu.rs
+++ b/src/cpu.rs
@@ -21,12 +21,16 @@ pub struct Cpu {
     pub e: u8,
     pub h: u8,
     pub l: u8,
+
     ime: bool,
     zero: bool,
     sub: bool,
     half_carry: bool,
     carry: bool,
     halted: bool,
+
+    /// Reference to the MMU (Memory Management Unit) to be used
+    /// for memory bus access operations.
     pub mmu: Mmu,
 
     /// Temporary counter used to control the number of cycles
diff --git a/src/gb.rs b/src/gb.rs
index 3a72d3ff..1680c971 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -23,6 +23,19 @@ pub struct GameBoy {
     cpu: Cpu,
 }
 
+#[cfg_attr(feature = "wasm", wasm_bindgen)]
+pub struct Registers {
+    pub pc: u16,
+    pub sp: u16,
+    pub a: u8,
+    pub b: u8,
+    pub c: u8,
+    pub d: u8,
+    pub e: u8,
+    pub h: u8,
+    pub l: u8,
+}
+
 #[cfg_attr(feature = "wasm", wasm_bindgen)]
 impl GameBoy {
     #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
@@ -113,6 +126,20 @@ impl GameBoy {
         self.frame_buffer().to_vec()
     }
 
+    pub fn registers(&mut self) -> Registers {
+        Registers {
+            pc: self.cpu.pc,
+            sp: self.cpu.sp,
+            a: self.cpu.a,
+            b: self.cpu.b,
+            c: self.cpu.c,
+            d: self.cpu.d,
+            e: self.cpu.e,
+            h: self.cpu.h,
+            l: self.cpu.l
+        }
+    }
+
     /// Obtains the tile structure for the tile at the
     /// given index, no conversion in the pixel buffer
     /// is done so that the color reference is the GB one.
@@ -129,6 +156,8 @@ impl GameBoy {
     }
 }
 
+/// Gameboy implementations that are meant with performance
+/// in mind and that do not support WASM interface of copy.
 impl GameBoy {
     /// The logical frequency of the Game Boy
     /// CPU in hz.
-- 
GitLab