Use explicit states for sims

This commit is contained in:
Simon Gellis 2025-01-02 22:02:51 -05:00
parent be8cdd958a
commit a5b5f8e80f
4 changed files with 116 additions and 75 deletions

10
Cargo.lock generated
View File

@ -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",

View File

@ -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"] }

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,

View File

@ -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);
}