diff --git a/Cargo.lock b/Cargo.lock index 4b3eb56..8a1298f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -406,6 +406,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1752,6 +1761,7 @@ version = "0.2.5" dependencies = [ "anyhow", "atoi", + "atomic", "bitflags 2.6.0", "bytemuck", "cc", diff --git a/Cargo.toml b/Cargo.toml index 0e1b3fb..c757995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [dependencies] anyhow = "1" atoi = "2" +atomic = "0.6" bitflags = { version = "2", features = ["serde"] } bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } diff --git a/src/emulator.rs b/src/emulator.rs index 0250cf4..2cc7b61 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -6,13 +6,15 @@ use std::{ ops::Range, path::{Path, PathBuf}, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, + atomic::{AtomicBool, Ordering}, mpsc::{self, RecvError, TryRecvError}, Arc, }, }; use anyhow::Result; +use atomic::Atomic; +use bytemuck::NoUninit; use egui_toast::{Toast, ToastKind, ToastOptions}; use crate::{audio::Audio, graphics::TextureSink}; @@ -21,16 +23,6 @@ pub use shrooms_vb_core::{VBKey, VBRegister}; 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, @@ -93,23 +85,33 @@ fn sram_path(rom_path: &Path, sim_id: SimId) -> PathBuf { } } +pub struct EmulatorBuilder { + rom: Option, + commands: mpsc::Receiver, + sim_state: Arc<[Atomic; 2]>, + state: Arc>, + audio_on: Arc<[AtomicBool; 2]>, + linked: Arc, +} + 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)]), + sim_state: Arc::new([ + Atomic::new(SimState::Uninitialized), + Atomic::new(SimState::Uninitialized), + ]), + state: Arc::new(Atomic::new(EmulatorState::Paused)), 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(), + sim_state: builder.sim_state.clone(), + state: builder.state.clone(), audio_on: builder.audio_on.clone(), linked: builder.linked.clone(), }; @@ -126,9 +128,8 @@ impl EmulatorBuilder { pub fn build(self) -> Result { let mut emulator = Emulator::new( self.commands, - self.sim_count, - self.running, - self.has_game, + self.sim_state, + self.state, self.audio_on, self.linked, )?; @@ -144,9 +145,8 @@ pub struct Emulator { carts: [Option; 2], audio: Audio, commands: mpsc::Receiver, - sim_count: Arc, - running: Arc<[AtomicBool; 2]>, - has_game: Arc<[AtomicBool; 2]>, + sim_state: Arc<[Atomic; 2]>, + state: Arc>, audio_on: Arc<[AtomicBool; 2]>, linked: Arc, renderers: HashMap, @@ -158,9 +158,8 @@ pub struct Emulator { impl Emulator { fn new( commands: mpsc::Receiver, - sim_count: Arc, - running: Arc<[AtomicBool; 2]>, - has_game: Arc<[AtomicBool; 2]>, + sim_state: Arc<[Atomic; 2]>, + state: Arc>, audio_on: Arc<[AtomicBool; 2]>, linked: Arc, ) -> Result { @@ -169,9 +168,8 @@ impl Emulator { carts: [None, None], audio: Audio::init()?, commands, - sim_count, - running, - has_game, + sim_state, + state, audio_on, linked, renderers: HashMap::new(), @@ -208,17 +206,17 @@ impl Emulator { let index = sim_id.to_index(); while self.sims.len() <= index { self.sims.push(Sim::new()); + self.sim_state[index].store(SimState::NoGame, Ordering::Release); } - self.sim_count.store(self.sims.len(), Ordering::Relaxed); let sim = &mut self.sims[index]; sim.reset(); if let Some(cart) = new_cart { sim.load_cart(cart.rom.clone(), cart.sram.clone())?; self.carts[index] = Some(cart); - self.has_game[index].store(true, Ordering::Release); + self.sim_state[index].store(SimState::Ready, Ordering::Release); } - if self.has_game[index].load(Ordering::Acquire) { - self.running[index].store(true, Ordering::Release); + if self.sim_state[index].load(Ordering::Acquire) == SimState::Ready { + self.resume_sims(); } Ok(()) } @@ -243,9 +241,31 @@ impl Emulator { self.linked.store(false, Ordering::Release); } - pub fn pause_sim(&mut self, sim_id: SimId) -> Result<()> { - self.running[sim_id.to_index()].store(false, Ordering::Release); - self.save_sram(sim_id) + fn pause_sims(&mut self) -> Result<()> { + if self + .state + .compare_exchange( + EmulatorState::Running, + EmulatorState::Paused, + Ordering::AcqRel, + Ordering::Relaxed, + ) + .is_ok() + { + for sim_id in SimId::values() { + self.save_sram(sim_id)?; + } + } + Ok(()) + } + + fn resume_sims(&mut self) { + let _ = self.state.compare_exchange( + EmulatorState::Paused, + EmulatorState::Running, + Ordering::AcqRel, + Ordering::Relaxed, + ); } fn save_sram(&mut self, sim_id: SimId) -> Result<()> { @@ -263,9 +283,7 @@ impl Emulator { self.save_sram(SimId::Player2)?; 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.sim_state[SimId::Player2.to_index()].store(SimState::Uninitialized, Ordering::Release); self.linked.store(false, Ordering::Release); Ok(()) } @@ -299,9 +317,12 @@ impl Emulator { // returns true if the emulator is "idle" (i.e. this didn't output anything) pub fn tick(&mut self) -> bool { - 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; + let p1_state = self.sim_state[SimId::Player1.to_index()].load(Ordering::Acquire); + let p2_state = self.sim_state[SimId::Player2.to_index()].load(Ordering::Acquire); + let state = self.state.load(Ordering::Acquire); + let p1_running = state == EmulatorState::Running && p1_state == SimState::Ready; + let p2_running = state == EmulatorState::Running && p2_state == SimState::Ready; + let mut idle = !p1_running && !p2_running; if p1_running && p2_running { Sim::emulate_many(&mut self.sims); } else if p1_running { @@ -377,19 +398,12 @@ impl Emulator { } } EmulatorCommand::Pause => { - for sim_id in SimId::values() { - if let Err(error) = self.pause_sim(sim_id) { - self.report_error(sim_id, format!("Error pausing: {error}")); - } + if let Err(error) = self.pause_sims() { + self.report_error(SimId::Player1, format!("Error pausing: {error}")); } } 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); - } - } + self.resume_sims(); } EmulatorCommand::ReadRegister(sim_id, register, done) => { let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { @@ -472,32 +486,43 @@ pub enum EmulatorCommand { Exit(oneshot::Sender<()>), } +#[derive(Clone, Copy, Debug, PartialEq, Eq, NoUninit)] +#[repr(usize)] +pub enum SimState { + Uninitialized, + NoGame, + Ready, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, NoUninit)] +#[repr(usize)] +pub enum EmulatorState { + Paused, + Running, +} + #[derive(Clone)] pub struct EmulatorClient { queue: mpsc::Sender, - sim_count: Arc, - running: Arc<[AtomicBool; 2]>, - has_game: Arc<[AtomicBool; 2]>, + sim_state: Arc<[Atomic; 2]>, + state: Arc>, 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 sim_state(&self, sim_id: SimId) -> SimState { + self.sim_state[sim_id.to_index()].load(Ordering::Acquire) } - 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 emulator_state(&self) -> EmulatorState { + self.state.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 are_sims_linked(&self) -> bool { + self.linked.load(Ordering::Acquire) + } pub fn send_command(&self, command: EmulatorCommand) -> bool { match self.queue.send(command) { Ok(()) => true, diff --git a/src/window/game.rs b/src/window/game.rs index ced5d1d..df3cdd6 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -2,7 +2,7 @@ use std::sync::mpsc; use crate::{ app::UserEvent, - emulator::{EmulatorClient, EmulatorCommand, SimId}, + emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState}, persistence::Persistence, }; use egui::{ @@ -73,7 +73,7 @@ impl GameWindow { .pick_file(); if let Some(path) = rom { self.client - .send_command(EmulatorCommand::LoadGame(SimId::Player1, path)); + .send_command(EmulatorCommand::LoadGame(self.sim_id, path)); } ui.close_menu(); } @@ -82,17 +82,21 @@ impl GameWindow { } }); ui.menu_button("Emulation", |ui| { - let has_game = self.client.has_game(self.sim_id); - if self.client.is_running(self.sim_id) { - if ui.add_enabled(has_game, Button::new("Pause")).clicked() { + let state = self.client.emulator_state(); + let can_interact = self.client.sim_state(self.sim_id) == SimState::Ready; + if state == EmulatorState::Running { + if ui.add_enabled(can_interact, Button::new("Pause")).clicked() { self.client.send_command(EmulatorCommand::Pause); ui.close_menu(); } - } else if ui.add_enabled(has_game, Button::new("Resume")).clicked() { + } else if ui + .add_enabled(can_interact, Button::new("Resume")) + .clicked() + { self.client.send_command(EmulatorCommand::Resume); ui.close_menu(); } - if ui.add_enabled(has_game, Button::new("Reset")).clicked() { + if ui.add_enabled(can_interact, Button::new("Reset")).clicked() { self.client .send_command(EmulatorCommand::Reset(self.sim_id)); ui.close_menu(); @@ -100,8 +104,9 @@ impl GameWindow { }); ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui)); ui.menu_button("Multiplayer", |ui| { + let has_player_2 = self.client.sim_state(SimId::Player2) != SimState::Uninitialized; if self.sim_id == SimId::Player1 - && !self.client.has_player_2() + && !has_player_2 && ui.button("Open Player 2").clicked() { self.client @@ -109,7 +114,7 @@ impl GameWindow { self.proxy.send_event(UserEvent::OpenPlayer2).unwrap(); ui.close_menu(); } - if self.client.has_player_2() { + if has_player_2 { let linked = self.client.are_sims_linked(); if linked && ui.button("Unlink").clicked() { self.client.send_command(EmulatorCommand::Unlink); @@ -207,7 +212,7 @@ impl GameWindow { let color_str = |color: Color32| { format!("{:02x}{:02x}{:02x}", color.r(), color.g(), color.b()) }; - let is_running = self.client.is_running(self.sim_id); + let is_running = self.client.emulator_state() == EmulatorState::Running; if is_running { self.client.send_command(EmulatorCommand::Pause); }