From 8d297efe866722094bab3fdd134544204827d706 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Thu, 27 Apr 2023 12:15:41 +0100
Subject: [PATCH] chore: much better device initialization

---
 frontends/sdl/src/main.rs | 46 ++++++++++++++++++++++++++-------------
 src/cpu.rs                | 15 +++++++++++++
 src/devices/printer.rs    |  4 ++++
 src/devices/stdout.rs     | 15 ++++++++++++-
 src/gb.rs                 | 16 +++++++++++++-
 src/mmu.rs                | 12 ++++++++++
 src/rom.rs                |  1 -
 src/serial.rs             |  7 ++++++
 8 files changed, 98 insertions(+), 18 deletions(-)

diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs
index 9cdc4888..0e423926 100644
--- a/frontends/sdl/src/main.rs
+++ b/frontends/sdl/src/main.rs
@@ -6,10 +6,11 @@ pub mod graphics;
 
 use audio::Audio;
 use boytacean::{
-    devices::printer::PrinterDevice,
+    devices::{printer::PrinterDevice, stdout::StdoutDevice},
     gb::{AudioProvider, GameBoy, GameBoyMode},
     pad::PadKey,
     ppu::{PaletteInfo, PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH},
+    serial::{NullDevice, SerialDevice},
 };
 use chrono::Utc;
 use clap::Parser;
@@ -461,6 +462,9 @@ struct Args {
     #[arg(short, long, default_value_t = String::from("cgb"))]
     mode: String,
 
+    #[arg(short, long, default_value_t = String::from("printer"))]
+    device: String,
+
     #[arg(short, long, default_value_t = String::from("../../res/roms.prop/tetris_dx.gbc"))]
     rom_path: String,
 }
@@ -469,24 +473,13 @@ fn main() {
     // parses the provided command line arguments and uses them to
     // obtain structured values
     let args = Args::parse();
-    let mode = GameBoyMode::from_str(&args.mode);
+    let mode: GameBoyMode = GameBoyMode::from_str(&args.mode);
 
     // creates a new Game Boy instance and loads both the boot ROM
     // and the initial game ROM to "start the engine"
     let mut game_boy = GameBoy::new(mode);
-    let mut printer = Box::<PrinterDevice>::default();
-    printer.set_callback(|image_buffer| {
-        let file_name = format!("printer-{}.png", Utc::now().format("%Y%m%d-%H%M%S"));
-        image::save_buffer(
-            Path::new(&file_name),
-            image_buffer,
-            160,
-            (image_buffer.len() / 4 / 160) as u32,
-            ColorType::Rgba8,
-        )
-        .unwrap();
-    });
-    game_boy.attach_serial(printer);
+    let device = build_device(&args.device);
+    game_boy.attach_serial(device);
     game_boy.load(true);
 
     // prints the current version of the emulator (informational message)
@@ -503,6 +496,29 @@ fn main() {
     emulator.run();
 }
 
+fn build_device(device: &str) -> Box<dyn SerialDevice> {
+    match device {
+        "null" => Box::<NullDevice>::default(),
+        "stdout" => Box::<StdoutDevice>::default(),
+        "printer" => {
+            let mut printer = Box::<PrinterDevice>::default();
+            printer.set_callback(|image_buffer| {
+                let file_name = format!("printer-{}.png", Utc::now().format("%Y%m%d-%H%M%S"));
+                image::save_buffer(
+                    Path::new(&file_name),
+                    image_buffer,
+                    160,
+                    (image_buffer.len() / 4 / 160) as u32,
+                    ColorType::Rgba8,
+                )
+                .unwrap();
+            });
+            printer
+        }
+        _ => panic!("Unsupported device: {}", device),
+    }
+}
+
 fn key_to_pad(keycode: Keycode) -> Option<PadKey> {
     match keycode {
         Keycode::Up => Some(PadKey::Up),
diff --git a/src/cpu.rs b/src/cpu.rs
index 2f765e4b..1fb00036 100644
--- a/src/cpu.rs
+++ b/src/cpu.rs
@@ -340,16 +340,31 @@ impl Cpu {
         self.mmu().pad()
     }
 
+    #[inline(always)]
+    pub fn pad_i(&self) -> &Pad {
+        self.mmu_i().pad_i()
+    }
+
     #[inline(always)]
     pub fn timer(&mut self) -> &mut Timer {
         self.mmu().timer()
     }
 
+    #[inline(always)]
+    pub fn timer_i(&self) -> &Timer {
+        self.mmu_i().timer_i()
+    }
+
     #[inline(always)]
     pub fn serial(&mut self) -> &mut Serial {
         self.mmu().serial()
     }
 
+    #[inline(always)]
+    pub fn serial_i(&self) -> &Serial {
+        self.mmu_i().serial_i()
+    }
+
     #[inline(always)]
     pub fn halted(&self) -> bool {
         self.halted
diff --git a/src/devices/printer.rs b/src/devices/printer.rs
index 23c46b4e..cf0161a8 100644
--- a/src/devices/printer.rs
+++ b/src/devices/printer.rs
@@ -312,6 +312,10 @@ impl SerialDevice for PrinterDevice {
     fn allow_slave(&self) -> bool {
         false
     }
+
+    fn description(&self) -> String {
+        format!("Printer [{}]", self.command)
+    }
 }
 
 impl Default for PrinterDevice {
diff --git a/src/devices/stdout.rs b/src/devices/stdout.rs
index 048a83f7..cdaefb2c 100644
--- a/src/devices/stdout.rs
+++ b/src/devices/stdout.rs
@@ -1,4 +1,7 @@
-use std::io::{stdout, Write};
+use std::{
+    fmt::{self, Display, Formatter},
+    io::{stdout, Write},
+};
 
 use crate::serial::SerialDevice;
 
@@ -37,6 +40,10 @@ impl SerialDevice for StdoutDevice {
     fn allow_slave(&self) -> bool {
         false
     }
+
+    fn description(&self) -> String {
+        String::from("Stdout")
+    }
 }
 
 impl Default for StdoutDevice {
@@ -44,3 +51,9 @@ impl Default for StdoutDevice {
         Self::new(true)
     }
 }
+
+impl Display for StdoutDevice {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "Stdout")
+    }
+}
diff --git a/src/gb.rs b/src/gb.rs
index 74b8dc27..5088e6f1 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -610,7 +610,7 @@ impl GameBoy {
 
     pub fn description(&self, column_length: usize) -> String {
         format!(
-            "{}  {}\n{}  {}\n{}  {}\n{}  {}",
+            "{}  {}\n{}  {}\n{}  {}\n{}  {}\n{}  {}",
             format!("{:width$}", "Version", width = column_length),
             VERSION,
             format!("{:width$}", "Mode", width = column_length),
@@ -619,6 +619,8 @@ impl GameBoy {
             self.ram_size(),
             format!("{:width$}", "VRAM Size", width = column_length),
             self.vram_size(),
+            format!("{:width$}", "Serial", width = column_length),
+            self.serial_i().device().description(),
         )
     }
 }
@@ -662,14 +664,26 @@ impl GameBoy {
         self.cpu.pad()
     }
 
+    pub fn pad_i(&self) -> &Pad {
+        self.cpu.pad_i()
+    }
+
     pub fn timer(&mut self) -> &mut Timer {
         self.cpu.timer()
     }
 
+    pub fn timer_i(&self) -> &Timer {
+        self.cpu.timer_i()
+    }
+
     pub fn serial(&mut self) -> &mut Serial {
         self.cpu.serial()
     }
 
+    pub fn serial_i(&self) -> &Serial {
+        self.cpu.serial_i()
+    }
+
     pub fn frame_buffer(&mut self) -> &[u8; FRAME_BUFFER_SIZE] {
         &(self.ppu().frame_buffer)
     }
diff --git a/src/mmu.rs b/src/mmu.rs
index 301d3298..983dcbdd 100644
--- a/src/mmu.rs
+++ b/src/mmu.rs
@@ -144,14 +144,26 @@ impl Mmu {
         &mut self.pad
     }
 
+    pub fn pad_i(&self) -> &Pad {
+        &self.pad
+    }
+
     pub fn timer(&mut self) -> &mut Timer {
         &mut self.timer
     }
 
+    pub fn timer_i(&self) -> &Timer {
+        &self.timer
+    }
+
     pub fn serial(&mut self) -> &mut Serial {
         &mut self.serial
     }
 
+    pub fn serial_i(&self) -> &Serial {
+        &self.serial
+    }
+
     pub fn boot_active(&self) -> bool {
         self.boot_active
     }
diff --git a/src/rom.rs b/src/rom.rs
index 8d18fab5..c9acccf5 100644
--- a/src/rom.rs
+++ b/src/rom.rs
@@ -1,6 +1,5 @@
 use core::fmt;
 use std::{
-    any::Any,
     cmp::max,
     fmt::{Display, Formatter},
 };
diff --git a/src/serial.rs b/src/serial.rs
index dd6388a3..551a375b 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -8,6 +8,9 @@ pub trait SerialDevice {
     /// simulating an external clock source. Or if instead the
     /// clock should always be generated by the running device.
     fn allow_slave(&self) -> bool;
+
+    /// Returns a short description of the serial device.
+    fn description(&self) -> String;
 }
 
 pub struct Serial {
@@ -202,6 +205,10 @@ impl SerialDevice for NullDevice {
     fn allow_slave(&self) -> bool {
         false
     }
+
+    fn description(&self) -> String {
+        String::from("Null")
+    }
 }
 
 impl Default for NullDevice {
-- 
GitLab