use std::{ collections::HashMap, fs, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, Ordering}, mpsc::{self, RecvError, TryRecvError}, Arc, }, }; use anyhow::Result; use crate::{audio::Audio, graphics::TextureSink}; use shrooms_vb_core::Sim; pub use shrooms_vb_core::VBKey; mod shrooms_vb_core; pub struct EmulatorBuilder { rom: Option, commands: mpsc::Receiver, running: Arc, has_game: Arc, } #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum SimId { Player1, Player2, } impl SimId { pub const fn values() -> [Self; 2] { [Self::Player1, Self::Player2] } pub const fn to_index(self) -> usize { match self { Self::Player1 => 0, Self::Player2 => 1, } } } 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_from_file(SimId::Player1, &path)?; } Ok(emulator) } } pub struct Emulator { sims: Vec, audio: Audio, commands: mpsc::Receiver, renderers: HashMap, running: Arc, has_game: Arc, } impl Emulator { fn new( commands: mpsc::Receiver, running: Arc, has_game: Arc, ) -> Result { Ok(Self { sims: vec![], audio: Audio::init()?, commands, renderers: HashMap::new(), running, has_game, }) } pub fn load_rom_from_file(&mut self, sim_id: SimId, path: &Path) -> Result<()> { let bytes = fs::read(path)?; self.load_rom(sim_id, bytes)?; Ok(()) } pub fn start_second_sim(&mut self, rom: Option) -> Result<()> { let bytes = if let Some(path) = rom { fs::read(path)? } else if let Some(rom) = self.sims.first().and_then(|s| s.clone_rom()) { rom } else { return Ok(()); }; self.load_rom(SimId::Player2, bytes)?; Ok(()) } fn load_rom(&mut self, sim_id: SimId, bytes: Vec) -> Result<()> { while self.sims.len() <= sim_id.to_index() { self.sims.push(Sim::new()); } let sim = &mut self.sims[sim_id.to_index()]; sim.reset(); sim.load_rom(bytes)?; self.has_game.store(true, Ordering::Release); self.running.store(true, Ordering::Release); Ok(()) } pub fn stop_second_sim(&mut self) { self.renderers.remove(&SimId::Player2); self.sims.truncate(1); } pub fn run(&mut self) { let mut eye_contents = vec![0u8; 384 * 224 * 2]; let mut audio_samples = vec![]; loop { let mut idle = true; if self.running.load(Ordering::Acquire) { idle = false; Sim::emulate_many(&mut self.sims); } for sim_id in SimId::values() { let Some(renderer) = self.renderers.get_mut(&sim_id) else { continue; }; let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { continue; }; if sim.read_pixels(&mut eye_contents) { idle = false; if renderer.queue_render(&eye_contents).is_err() { self.renderers.remove(&sim_id); } } } let weight = 1.0 / self.sims.len() as f32; for sim in self.sims.iter_mut() { sim.read_samples(&mut audio_samples, weight); } if !audio_samples.is_empty() { idle = false; self.audio.update(&audio_samples); audio_samples.clear(); } if idle { // The game is paused, and we have output all the video/audio we have. // Block the thread until a new command comes in. match self.commands.recv() { Ok(command) => self.handle_command(command), Err(RecvError) => { return; } } } 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(sim_id, renderer) => { self.renderers.insert(sim_id, renderer); } EmulatorCommand::LoadGame(sim_id, path) => { if let Err(error) = self.load_rom_from_file(sim_id, &path) { eprintln!("error loading rom: {}", error); } } EmulatorCommand::StartSecondSim(path) => { if let Err(error) = self.start_second_sim(path) { eprintln!("error starting second sim: {}", error); } } EmulatorCommand::StopSecondSim => { self.stop_second_sim(); } 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 => { for sim in self.sims.iter_mut() { sim.reset(); } if !self.sims.is_empty() { self.running.store(true, Ordering::Release); } } EmulatorCommand::SetKeys(sim_id, keys) => { if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { sim.set_keys(keys); } } } } } #[derive(Debug)] pub enum EmulatorCommand { SetRenderer(SimId, TextureSink), LoadGame(SimId, PathBuf), StartSecondSim(Option), StopSecondSim, Pause, Resume, Reset, SetKeys(SimId, 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 ); } } }