use std::{ fs, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, mpsc::{self, TryRecvError}, Arc, }, }; use anyhow::Result; use crate::{ audio::Audio, renderer::GameRenderer, shrooms_vb_core::{CoreVB, VBKey}, }; pub struct EmulatorBuilder { rom: Option, commands: mpsc::Receiver, running: Arc, has_game: Arc, } impl EmulatorBuilder { pub fn new() -> (Self, EmulatorClient) { let (queue, commands) = mpsc::channel(); let builder = Self { rom: None, commands, running: Arc::new(AtomicBool::new(false)), has_game: Arc::new(AtomicBool::new(false)), }; let client = EmulatorClient { queue, running: builder.running.clone(), has_game: builder.has_game.clone(), }; (builder, client) } pub fn with_rom(self, path: &Path) -> Self { Self { rom: Some(path.into()), ..self } } pub fn build(self) -> Result { let mut emulator = Emulator::new(self.commands, self.running, self.has_game)?; if let Some(path) = self.rom { emulator.load_rom(&path)?; } Ok(emulator) } } pub struct Emulator { sim: CoreVB, audio: Audio, commands: mpsc::Receiver, renderer: Option, running: Arc, has_game: Arc, } impl Emulator { fn new( commands: mpsc::Receiver, running: Arc, has_game: Arc, ) -> Result { Ok(Self { sim: CoreVB::new(), audio: Audio::init()?, commands, renderer: None, running, has_game, }) } pub fn load_rom(&mut self, path: &Path) -> Result<()> { let bytes = fs::read(path)?; self.sim.reset(); self.sim.load_rom(bytes)?; self.has_game.store(true, Ordering::Release); self.running.store(true, Ordering::Release); Ok(()) } pub fn run(&mut self) { let mut eye_contents = vec![0u8; 384 * 224 * 2]; let mut audio_samples = vec![]; loop { if self.running.load(Ordering::Acquire) { self.sim.emulate_frame(); } if let Some(renderer) = &mut self.renderer { if self.sim.read_pixels(&mut eye_contents) { renderer.render(&eye_contents); } } self.sim.read_samples(&mut audio_samples); if !audio_samples.is_empty() { self.audio.update(&audio_samples); audio_samples.clear(); } loop { match self.commands.try_recv() { Ok(command) => self.handle_command(command), Err(TryRecvError::Empty) => { break; } Err(TryRecvError::Disconnected) => { return; } } } } } fn handle_command(&mut self, command: EmulatorCommand) { match command { EmulatorCommand::SetRenderer(renderer) => { self.renderer = Some(renderer); } EmulatorCommand::LoadGame(path) => { if let Err(error) = self.load_rom(&path) { eprintln!("error loading rom: {}", error); } } EmulatorCommand::Pause => { self.running.store(false, Ordering::Release); } EmulatorCommand::Resume => { if self.has_game.load(Ordering::Acquire) { self.running.store(true, Ordering::Relaxed); } } EmulatorCommand::Reset => { self.sim.reset(); self.running.store(true, Ordering::Release); } EmulatorCommand::SetKeys(keys) => { self.sim.set_keys(keys); } } } } #[derive(Debug)] pub enum EmulatorCommand { SetRenderer(GameRenderer), LoadGame(PathBuf), Pause, Resume, Reset, SetKeys(VBKey), } #[derive(Clone)] pub struct EmulatorClient { queue: mpsc::Sender, running: Arc, has_game: Arc, } impl EmulatorClient { pub fn is_running(&self) -> bool { self.running.load(Ordering::Acquire) } pub fn has_game(&self) -> bool { self.has_game.load(Ordering::Acquire) } pub fn send_command(&self, command: EmulatorCommand) { if let Err(err) = self.queue.send(command) { eprintln!( "could not send command {:?} as emulator is shut down", err.0 ); } } }