rbgb 0.1.2

A basic and rough gameboy emulator
Documentation
use std::{
    sync::{Arc, Mutex},
    time::{Duration, Instant},
};

use crate::types::{CURRENT_SCANLINE, GameInput, INPUT_REGISTER, KeyState, LCD_CONTROL};
use debug_print::debug_println;

mod cpu;
mod graphics;
mod joypad;
mod mem;
mod sound;

pub struct Emulator {
    screen: graphics::Screen,
    cpu: cpu::CPU,
    joypad: joypad::Joypad,
    memory: mem::SharedMemory,
    paused: bool,
}

impl Default for Emulator {
    fn default() -> Self {
        Self::new()
    }
}

impl Emulator {
    /// Calls the needed functions once a frame
    const MAXCYCLES: u32 = 69905;
    const FRAME_DURATION: Duration = Duration::from_nanos(16_741_000);

    pub fn new() -> Self {
        let mem = Arc::new(Mutex::new(mem::Memory::new()));
        mem.lock().unwrap().ram_startup();
        Emulator {
            screen: graphics::Screen::new(Arc::clone(&mem)),
            cpu: cpu::CPU::new(Arc::clone(&mem)),
            joypad: joypad::Joypad::new(Arc::clone(&mem)),
            memory: mem,
            paused: true,
        }
    }

    pub fn update(&mut self) {
        if self.paused {
            return;
        }
        let frame_start = Instant::now();
        // debug_println!("Main Loop");
        let mut num_cycles: u32 = 0;
        while num_cycles < Self::MAXCYCLES {
            //debug_println!("Program Counter: 0x{:X}", self.cpu.registers.val_pc());
            let cycles = self.cpu.execute_next_opcode(false);
            num_cycles += cycles as u32;
            self.cpu.timers.update_timers(cycles as i32);
            self.screen.update_screen(cycles as i32);
            self.cpu.handle_interrupts();
        }
        if let Some(remaining) = Self::FRAME_DURATION.checked_sub(frame_start.elapsed()) {
            std::thread::sleep(remaining);
        }
    }

    pub fn toggle_pause(&mut self) {
        self.paused = !self.paused;
    }

    pub fn is_paused(&self) -> bool {
        self.paused
    }

    pub fn load_rom(&mut self, path: &str) -> Result<(), String> {
        let data = std::fs::read(path).map_err(|e| e.to_string())?;
        let mut mem = self.memory.lock().unwrap();
        mem.load_rom_data(&data);
        mem.ram_startup();
        self.cpu.reset();

        self.paused = false;
        Ok(())
    }

    pub fn get_display_buffer(&self) -> &[u8] {
        &self.screen.buffer
    }

    pub fn dump_lcd_mem(&self) {
        let mem = self.memory.lock().unwrap();

        debug_println!("IDK: {:X}", mem.read_byte_forced(0xFF26));
        debug_println!("LCD Control: {:X}", mem.read_byte_forced(LCD_CONTROL));
        debug_println!("Scroll Y: {:X}", mem.read_byte_forced(0xFF42));
        debug_println!("Scroll X: {:X}", mem.read_byte_forced(0xFF43));
        debug_println!("BG Palette: {:X}", mem.read_byte_forced(0xFF47));
        debug_println!("OBJ palette: {:X}", mem.read_byte_forced(0xFF48));
        debug_println!(
            "Current Scanline: {:X}",
            mem.read_byte_forced(CURRENT_SCANLINE)
        );
        debug_println!("LCD Control: {:X}", mem.read_byte_forced(LCD_CONTROL));
        debug_println!(
            "Joystick Register: 0x{:X}",
            mem.read_byte_forced(INPUT_REGISTER)
        );
    }

    /// Handle input to the emulator
    pub fn game_input(&mut self, input: GameInput, val: KeyState) {
        self.joypad.log_input(input, val)
    }
}