From 31fb280fc67ef26cc129a3037640e682f09cc3c0 Mon Sep 17 00:00:00 2001 From: Atreya Bain Date: Sun, 10 Mar 2024 14:26:42 +0530 Subject: [PATCH] [cln] Code cleanup Cleanup code Add a offset delay timer --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/device/device.rs | 43 +++++------ src/device/keyboard.rs | 12 ++-- src/main.rs | 91 ++++++++++++------------ src/rom.rs | 12 ++++ src/sdl_adapters/sdl_audio_adapter.rs | 1 + src/sdl_adapters/sdl_graphics_adapter.rs | 5 +- 8 files changed, 92 insertions(+), 76 deletions(-) create mode 100644 src/rom.rs diff --git a/Cargo.lock b/Cargo.lock index d3c0d07..57b7bc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,7 +191,7 @@ dependencies = [ [[package]] name = "porcel8" -version = "0.1.0" +version = "0.1.1" dependencies = [ "byteorder", "clap", diff --git a/Cargo.toml b/Cargo.toml index 112c3ec..dbc737e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "porcel8" -version = "0.1.0" +version = "0.1.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/device/device.rs b/src/device/device.rs index 7baca1c..5badf4f 100644 --- a/src/device/device.rs +++ b/src/device/device.rs @@ -37,28 +37,13 @@ impl Device { } impl Device { - const DEFAULT_FONT: [u8; 5 * 16] = [ - 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 - 0x20, 0x60, 0x20, 0x20, 0x70, // 1 - 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 - 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 - 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 - 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 - 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 - 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 - 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 - 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 - 0xF0, 0x90, 0xF0, 0x90, 0x90, // A - 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B - 0xF0, 0x80, 0x80, 0x80, 0xF0, // C - 0xE0, 0x90, 0x90, 0x90, 0xE0, // D - 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E - 0xF0, 0x80, 0xF0, 0x80, 0x80 // F - ]; - const FONT_DEFAULT_MEM_LOCATION_START: usize = 0x50; const FONT_HEIGHT: u16 = 5; + const FONT_DEFAULT_MEM_LOCATION_START: usize = 0x50; const FONT_DEFAULT_MEM_LOCATION_END: usize = 0x9F; const ROM_START: usize = 0x200; + + + pub fn cycle(&mut self) -> EmulatorResult<()> { self.device_keyboard.update_keyboard()?; @@ -312,8 +297,26 @@ impl Device { } pub fn set_default_font(&mut self) { + const DEFAULT_FONT: [u8; Device::FONT_HEIGHT as usize * 16] = [ + 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 + 0x20, 0x60, 0x20, 0x20, 0x70, // 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, // A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B + 0xF0, 0x80, 0x80, 0x80, 0xF0, // C + 0xE0, 0x90, 0x90, 0x90, 0xE0, // D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E + 0xF0, 0x80, 0xF0, 0x80, 0x80 // F + ]; log::info!("Loaded default font from memory"); - self.memory[Self::FONT_DEFAULT_MEM_LOCATION_START..=Self::FONT_DEFAULT_MEM_LOCATION_END].copy_from_slice(&Self::DEFAULT_FONT); + self.memory[Self::FONT_DEFAULT_MEM_LOCATION_START..=Self::FONT_DEFAULT_MEM_LOCATION_END].copy_from_slice(&DEFAULT_FONT); } /// load a rom from bytes pub fn load_rom(&mut self, rom: &[u8]) { diff --git a/src/device/keyboard.rs b/src/device/keyboard.rs index 90268bd..45bbc59 100644 --- a/src/device/keyboard.rs +++ b/src/device/keyboard.rs @@ -2,13 +2,11 @@ use std::sync::mpsc::TryRecvError; use crate::util::{EmulatorError, EmulatorResult}; -// display thread sends these to the device thread. - -// device thread updates on key up and down? - - +/// An emulated keyboard that receives keyboard data as input pub struct Keyboard { + /// Current keyboard state bitflags: u16, + /// Receives keyboard events from main thread keyboard_event_receiver: std::sync::mpsc::Receiver, } @@ -26,7 +24,8 @@ impl Keyboard { } } - /// Update keyboard based on pending keyboard events + /// Update keyboard based on pending keyboard events. + /// If no events are presents, it will return without any action. pub fn update_keyboard(&mut self) -> EmulatorResult<()> { loop { let keyboard_event_recv_res = self.keyboard_event_receiver.try_recv(); @@ -45,7 +44,6 @@ impl Keyboard { } } - /// Query if key is down pub fn query_key_down(&self, key_num: u8) -> bool { (self.bitflags | 1 << key_num) == (1 << key_num) diff --git a/src/main.rs b/src/main.rs index 361a20d..7593eb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ -use std::fs::File; -use std::io::Read; use std::sync::{Arc, Mutex}; -use std::sync::mpsc::Receiver; +use std::sync::mpsc::Sender; use std::thread; -use std::time::Duration; +use std::thread::JoinHandle; +use std::time::{Duration}; use clap::Parser; use log::LevelFilter; use sdl2::audio::{AudioQueue, AudioSpecDesired}; @@ -15,10 +14,10 @@ use sdl2::render::BlendMode; use sdl2::render::WindowCanvas; use simple_logger::SimpleLogger; -use device::timer::DeviceTimerManager; + use crate::args::Porcel8ProgramArgs; use crate::device::Device; -use crate::device::keyboard::Keyboard; + use crate::util::EmulatorResult; use crate::kb_map::get_key_index; use crate::sdl_adapters::sdl_audio_adapter::SdlAudioAdapter; @@ -30,26 +29,26 @@ mod device; mod kb_map; mod util; mod sdl_adapters; +mod rom; fn main() -> EmulatorResult<()> { SimpleLogger::new().with_level(LevelFilter::Info).env().init().unwrap(); - let Porcel8ProgramArgs { filename, new_chip8_behaviour,draw_scale } = Porcel8ProgramArgs::parse(); + let Porcel8ProgramArgs { filename, new_chip8_behaviour, draw_scale } = Porcel8ProgramArgs::parse(); + log::info!("Started emulator"); + let (mut canvas, mut event_pump, audio_queue) = try_initiate_sdl(draw_scale)?; - let (mut canvas, mut event_pump,audio_queue) = try_initiate_sdl(draw_scale)?; + let (mut timer, mut sdl_aud_adapter) = SdlAudioAdapter::new_timers(SdlAudioAdapter::AUDIO_FREQUENCY, 0.85, audio_queue); - let (mut timer,mut sdl_aud_adapter) = SdlAudioAdapter::new_timers(440.0,0.85,audio_queue); - let (frame_buffer_for_display, frame_buffer_for_device) = get_frame_buffer_references(); let (sdl_kb_adapter, device_keyboard) = SdlKeyboardAdapter::new_keyboard(); - let (device_termination_signal_sender, device_termination_signal_sender_receiver) = std::sync::mpsc::channel(); - timer.start(); - let compute_handle = thread::Builder::new().name("Compute".to_string()).spawn(move || { - do_device_loop(timer, frame_buffer_for_device, device_termination_signal_sender_receiver, device_keyboard, filename, new_chip8_behaviour); - })?; + + let device = Device::new(timer, frame_buffer_for_device, device_keyboard, new_chip8_behaviour); + + let (device_termination_signal_sender, compute_handle) = start_compute_thread(filename, device)?; let mut sdl_graphics_adapter = SdlGraphicsAdapter::new(); @@ -57,7 +56,11 @@ fn main() -> EmulatorResult<()> { canvas.set_draw_color(Color::BLACK); canvas.clear(); + // Compute a frame time offset + // Thread will sleep for 60fps - (time spent computing) + let mut frame_timer = std::time::Instant::now(); 'running: loop { + let last_time = frame_timer.elapsed(); for event in event_pump.poll_iter() { match event { Event::Quit { .. } | @@ -79,43 +82,46 @@ fn main() -> EmulatorResult<()> { } } - - // The rest of the game loop goes here... + // lock and draw framebuffer { let lock = frame_buffer_for_display.lock()?; sdl_graphics_adapter.draw_screen(lock, &mut canvas)?; } canvas.present(); sdl_aud_adapter.process_push_audio()?; - // 60fps - small offset to consider for cpu cycle time - thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); + let sleep_duration = SdlGraphicsAdapter::FRAME_RATE_TIMING - last_time; + thread::sleep(sleep_duration); + frame_timer = std::time::Instant::now(); } compute_handle.join().expect("Failed to close compute thread"); Ok(()) } -fn do_device_loop(timer: DeviceTimerManager, frame_buffer: Arc>>, receiver: Receiver<()>, device_keyboard: Keyboard, rom_file_location_option: Option, new_chip_behaviour: bool) { - let mut device = Device::new(timer, frame_buffer, device_keyboard, new_chip_behaviour); +fn start_compute_thread(filename: Option, mut device: Device) -> EmulatorResult<(Sender<()>, JoinHandle<()>)> { device.set_default_font(); - if let Some(rom_file_location) = rom_file_location_option { - let rom = load_rom(rom_file_location); + if let Some(rom_file_location) = filename { + let rom = rom::load_rom(rom_file_location)?; device.load_rom(&rom); - } - loop { - let val = receiver.try_recv(); - if let Ok(()) = val { - break; - } else if let Err(std::sync::mpsc::TryRecvError::Disconnected) = val { - panic!("Disconnected"); + let (device_termination_signal_sender, device_termination_signal_sender_receiver) = std::sync::mpsc::channel(); + let compute_handle = thread::Builder::new().name("Compute".to_string()).spawn(move || { + + loop { + let val = device_termination_signal_sender_receiver.try_recv(); + if let Ok(()) = val { + break; + } else if let Err(std::sync::mpsc::TryRecvError::Disconnected) = val { + panic!("Disconnected"); + } + device.cycle().expect("Failed to execute"); + // Put a bit of delay to slow down execution + thread::sleep(Duration::from_millis(2)) } - device.cycle().expect("Failed to execute"); - // Put a bit of delay to slow down execution - thread::sleep(Duration::from_millis(2)) - } + })?; + Ok((device_termination_signal_sender, compute_handle)) } @@ -125,15 +131,10 @@ fn get_frame_buffer_references() -> (Arc>>, Arc [u8; ROM_SIZE] { - let mut rom_slice = [0u8; ROM_SIZE]; - let mut file = File::open(rom_file_location).expect("could not open"); - file.read(&mut rom_slice).expect("Unwrap"); - rom_slice -} - +/// Initiate SDL resources: +/// 1. A window canvas for drawing +/// 2. An event pump for use as an event loop, +/// 3. An Audio queue for sound fn try_initiate_sdl(draw_scale: f32) -> EmulatorResult<(WindowCanvas, EventPump, AudioQueue)> { let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); @@ -144,7 +145,7 @@ fn try_initiate_sdl(draw_scale: f32) -> EmulatorResult<(WindowCanvas, EventPump, freq: Some(SdlAudioAdapter::SAMPLING_FREQ), }; - let audio_queue = audio_subsystem.open_queue::(None, &wanted_spec)?; + let audio_queue = audio_subsystem.open_queue::(None, &wanted_spec)?; let window_width = (Device::FRAME_BUFFER_WIDTH as f32 * draw_scale) as u32; let window_height = (Device::FRAME_BUFFER_HEIGHT as f32 * draw_scale) as u32; @@ -160,7 +161,7 @@ fn try_initiate_sdl(draw_scale: f32) -> EmulatorResult<(WindowCanvas, EventPump, canvas.clear(); canvas.present(); let event_pump = sdl_context.event_pump()?; - Ok((canvas, event_pump,audio_queue)) + Ok((canvas, event_pump, audio_queue)) } diff --git a/src/rom.rs b/src/rom.rs new file mode 100644 index 0000000..c568414 --- /dev/null +++ b/src/rom.rs @@ -0,0 +1,12 @@ +use std::fs::File; +use std::io::Read; +use crate::util::EmulatorResult; + +pub const ROM_SIZE: usize = 4096 - 0x200; + +pub fn load_rom(rom_file_location: String) -> EmulatorResult<[u8; ROM_SIZE]> { + let mut rom_slice = [0u8; ROM_SIZE]; + let mut file = File::open(rom_file_location)?; + file.read(&mut rom_slice)?; + Ok(rom_slice) +} diff --git a/src/sdl_adapters/sdl_audio_adapter.rs b/src/sdl_adapters/sdl_audio_adapter.rs index 83c91d9..aa2d519 100644 --- a/src/sdl_adapters/sdl_audio_adapter.rs +++ b/src/sdl_adapters/sdl_audio_adapter.rs @@ -16,6 +16,7 @@ pub struct SdlAudioAdapter { impl SdlAudioAdapter { /// Number of samples per second pub const SAMPLING_FREQ:i32 = 15360; + pub const AUDIO_FREQUENCY: f32 = 440.0; pub const SAMPLES_PER_FRAME: usize = (Self::SAMPLING_FREQ as usize / 60) * 2; pub fn new_timers(freq: f32, volume: f32, diff --git a/src/sdl_adapters/sdl_graphics_adapter.rs b/src/sdl_adapters/sdl_graphics_adapter.rs index 1fe030d..bd9a9ab 100644 --- a/src/sdl_adapters/sdl_graphics_adapter.rs +++ b/src/sdl_adapters/sdl_graphics_adapter.rs @@ -1,15 +1,16 @@ use std::sync::MutexGuard; +use std::time::Duration; use sdl2::pixels::PixelFormatEnum; use sdl2::render::{TextureAccess, WindowCanvas}; use crate::device::Device; use crate::util::EmulatorResult; -#[derive(Debug)] pub struct SdlGraphicsAdapter { rgb_frame_buffer: Vec, } impl SdlGraphicsAdapter { + pub const FRAME_RATE_TIMING: Duration = Duration::new(0, 1_000_000_000u32 / 60); pub const RGB_COMPONENTS: usize = 3; pub const RGB_FRAMEBUFFER_SIZE: usize = Self::RGB_COMPONENTS * Device::FRAME_BUFFER_SIZE; pub fn new() -> SdlGraphicsAdapter { @@ -18,7 +19,7 @@ impl SdlGraphicsAdapter { rgb_frame_buffer } } - pub fn draw_screen(&mut self, frame_buffer: MutexGuard>, window_canvas: &mut WindowCanvas) ->EmulatorResult<()> { + pub fn draw_screen(&mut self, frame_buffer: MutexGuard>, window_canvas: &mut WindowCanvas) -> EmulatorResult<()> { for (i, pixel) in frame_buffer.iter().enumerate() { let col_component = if *pixel { 0xff } else { 0 }; self.rgb_frame_buffer[3 * i] = col_component;