diff --git a/examples/web/index.ts b/examples/web/index.ts index 036d5c0d635101d3707f1a7c4fef99de1b9393ee..75de6736cc0d727da11779a0d06ea922d77b5890 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 d44f28a65d33844a5029ac0909c352465c2cb860..067b2015ed036a8e715ca512d791690958b42bd5 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 ad01897b2abce06c63cb7597a93fab38b216e681..ae936530d61a3b160dfecfda383d36af52b19aa8 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 24f7544a1c8e7f5de294f02b474cda82f3dda36e..5cad1d5c5d8f82c2073ebb210bce035523ad5d6b 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 838636b94e5c09949d1234242e97bcdccf67ca09..a2980dabb48825fb1403af2a17b42b20a0243bbf 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 99ddd4f8272700828680589bd53f23278e7593d9..2208c1c11b3d4c2a9800a7029a1c080693d1ce53 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 18672a13bc9713059756d6e97d3e5ed43c42d30d..2d7f6065598fa6b42f55fec0997bc2385e774f2f 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 0000000000000000000000000000000000000000..26765c112b50a340d2bc4a46110f20bddd5e333a --- /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 0000000000000000000000000000000000000000..021867997ddc4a4fe99a6b663c241461b9e8d93b --- /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 59b7d18240ad1bc4ea361b700e70c75c911782de..030a9575febd3db6e663c0740256eb48c5a89e88 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 e879f383585faaab2a29889295ec78793182cb5c..483b6cf00566964ea1dfb88717bef6606a4f58d0 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 e253dba68cb622d24f5743b7e8646bdd63589b4e..f4693abad04c75991f748bc02b2b789ae1ffc1df 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 3a72d3ff9efea0b3c76c963e58912eb729fffde7..1680c971a6a465a5840665115fc518a20720dea8 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.