Use explicit states for sims

This commit is contained in:
2025-01-02 23:06:33 -05:00
parent be8cdd958a
commit a5b5f8e80f
4 changed files with 116 additions and 75 deletions
+90 -65
View File
@@ -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<PathBuf>,
commands: mpsc::Receiver<EmulatorCommand>,
sim_count: Arc<AtomicUsize>,
running: Arc<[AtomicBool; 2]>,
has_game: Arc<[AtomicBool; 2]>,
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
}
#[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<PathBuf>,
commands: mpsc::Receiver<EmulatorCommand>,
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
}
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<Emulator> {
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<Cart>; 2],
audio: Audio,
commands: mpsc::Receiver<EmulatorCommand>,
sim_count: Arc<AtomicUsize>,
running: Arc<[AtomicBool; 2]>,
has_game: Arc<[AtomicBool; 2]>,
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
renderers: HashMap<SimId, TextureSink>,
@@ -158,9 +158,8 @@ pub struct Emulator {
impl Emulator {
fn new(
commands: mpsc::Receiver<EmulatorCommand>,
sim_count: Arc<AtomicUsize>,
running: Arc<[AtomicBool; 2]>,
has_game: Arc<[AtomicBool; 2]>,
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
) -> Result<Self> {
@@ -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<EmulatorCommand>,
sim_count: Arc<AtomicUsize>,
running: Arc<[AtomicBool; 2]>,
has_game: Arc<[AtomicBool; 2]>,
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
}
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,