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