use std::{ collections::HashMap, fs, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, mpsc::{self, RecvError, TryRecvError}, Arc, }, }; use anyhow::Result; use crate::{audio::Audio, graphics::TextureSink}; pub use shrooms_vb_core::VBKey; use shrooms_vb_core::{Sim, EXPECTED_FRAME_SIZE}; mod shrooms_vb_core; pub struct EmulatorBuilder { rom: Option, commands: mpsc::Receiver, sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>, linked: 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, sim_count: Arc::new(AtomicUsize::new(0)), running: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), has_game: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]), linked: Arc::new(AtomicBool::new(false)), }; let client = EmulatorClient { queue, sim_count: builder.sim_count.clone(), running: builder.running.clone(), has_game: builder.has_game.clone(), audio_on: builder.audio_on.clone(), linked: builder.linked.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.sim_count, self.running, self.has_game, self.audio_on, self.linked, )?; if let Some(path) = self.rom { emulator.load_rom(SimId::Player1, &path)?; } Ok(emulator) } } pub struct Emulator { sims: Vec, audio: Audio, commands: mpsc::Receiver, sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>, linked: Arc, renderers: HashMap, } impl Emulator { fn new( commands: mpsc::Receiver, sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>, linked: Arc, ) -> Result { Ok(Self { sims: vec![], audio: Audio::init()?, commands, sim_count, running, has_game, audio_on, linked, renderers: HashMap::new(), }) } pub fn load_rom(&mut self, sim_id: SimId, path: &Path) -> Result<()> { let bytes = fs::read(path)?; self.reset_sim(sim_id, Some(bytes))?; Ok(()) } pub fn start_second_sim(&mut self, rom: Option) -> Result<()> { let bytes = if let Some(path) = rom { Some(fs::read(path)?) } else { self.sims.first().and_then(|s| s.clone_rom()) }; self.reset_sim(SimId::Player2, bytes)?; self.link_sims(); Ok(()) } fn reset_sim(&mut self, sim_id: SimId, new_rom: Option>) -> Result<()> { let index = sim_id.to_index(); while self.sims.len() <= index { self.sims.push(Sim::new()); } self.sim_count.store(self.sims.len(), Ordering::Relaxed); let sim = &mut self.sims[index]; sim.reset(); if let Some(bytes) = new_rom { sim.load_rom(bytes)?; self.has_game[index].store(true, Ordering::Release); } if self.has_game[index].load(Ordering::Acquire) { self.running[index].store(true, Ordering::Release); } Ok(()) } fn link_sims(&mut self) { let (first, second) = self.sims.split_at_mut(1); let Some(first) = first.first_mut() else { return; }; let Some(second) = second.first_mut() else { return; }; first.link(second); self.linked.store(true, Ordering::Release); } fn unlink_sims(&mut self) { let Some(first) = self.sims.first_mut() else { return; }; first.unlink(); self.linked.store(false, Ordering::Release); } pub fn stop_second_sim(&mut self) { self.renderers.remove(&SimId::Player2); self.sims.truncate(1); self.sim_count.store(self.sims.len(), Ordering::Relaxed); self.running[SimId::Player2.to_index()].store(false, Ordering::Release); self.has_game[SimId::Player2.to_index()].store(false, Ordering::Release); self.linked.store(false, Ordering::Release); } pub fn run(&mut self) { let mut eye_contents = vec![0u8; 384 * 224 * 2]; let mut audio_samples = vec![]; loop { let p1_running = self.running[SimId::Player1.to_index()].load(Ordering::Acquire); let p2_running = self.running[SimId::Player2.to_index()].load(Ordering::Acquire); let mut idle = p1_running || p2_running; if p1_running && p2_running { Sim::emulate_many(&mut self.sims); } else if p1_running { self.sims[SimId::Player1.to_index()].emulate(); } else if p2_running { self.sims[SimId::Player2.to_index()].emulate(); } 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 p1_audio = p1_running && self.audio_on[SimId::Player1.to_index()].load(Ordering::Acquire); let p2_audio = p2_running && self.audio_on[SimId::Player2.to_index()].load(Ordering::Acquire); let weight = if p1_audio && p2_audio { 0.5 } else { 1.0 }; if p1_audio { if let Some(sim) = self.sims.get_mut(SimId::Player1.to_index()) { sim.read_samples(&mut audio_samples, weight); } } if p2_audio { if let Some(sim) = self.sims.get_mut(SimId::Player2.to_index()) { sim.read_samples(&mut audio_samples, weight); } } if audio_samples.is_empty() { audio_samples.resize(EXPECTED_FRAME_SIZE, 0.0); } else { 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(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 => { for sim in SimId::values() { self.running[sim.to_index()].store(false, Ordering::Release); } } EmulatorCommand::Resume => { for sim_id in SimId::values() { let index = sim_id.to_index(); if self.has_game[index].load(Ordering::Acquire) { self.running[index].store(true, Ordering::Relaxed); } } } EmulatorCommand::SetAudioEnabled(p1, p2) => { self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release); self.audio_on[SimId::Player2.to_index()].store(p2, Ordering::Release); } EmulatorCommand::Link => { self.link_sims(); } EmulatorCommand::Unlink => { self.unlink_sims(); } EmulatorCommand::Reset(sim_id) => { if let Err(error) = self.reset_sim(sim_id, None) { eprintln!("error resetting sim: {}", error); } } 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, SetAudioEnabled(bool, bool), Link, Unlink, Reset(SimId), SetKeys(SimId, VBKey), } #[derive(Clone)] pub struct EmulatorClient { queue: mpsc::Sender, sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>, linked: Arc, } impl EmulatorClient { pub fn has_player_2(&self) -> bool { self.sim_count.load(Ordering::Acquire) == 2 } pub fn is_running(&self, sim_id: SimId) -> bool { self.running[sim_id.to_index()].load(Ordering::Acquire) } pub fn has_game(&self, sim_id: SimId) -> bool { self.has_game[sim_id.to_index()].load(Ordering::Acquire) } pub fn are_sims_linked(&self) -> bool { self.linked.load(Ordering::Acquire) } pub fn is_audio_enabled(&self, sim_id: SimId) -> bool { self.audio_on[sim_id.to_index()].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 ); } } }