Support manual/automatic ROM reloading

This commit is contained in:
2025-11-23 15:45:57 -05:00
parent 2e474df3e8
commit 2034f7934b
8 changed files with 248 additions and 22 deletions
+98 -15
View File
@@ -5,7 +5,7 @@ use std::{
sync::{
Arc, Weak,
atomic::{AtomicBool, Ordering},
mpsc::{self, RecvError, TryRecvError},
mpsc::{self, RecvTimeoutError, TryRecvError},
},
time::Duration,
};
@@ -75,6 +75,7 @@ pub struct EmulatorBuilder {
audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
start_paused: bool,
watch_rom: Arc<[AtomicBool; 2]>,
}
impl EmulatorBuilder {
@@ -91,11 +92,13 @@ impl EmulatorBuilder {
audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]),
linked: Arc::new(AtomicBool::new(false)),
start_paused: false,
watch_rom: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]),
};
let client = EmulatorClient {
queue,
sim_state: builder.sim_state.clone(),
state: builder.state.clone(),
watch_rom: builder.watch_rom.clone(),
linked: builder.linked.clone(),
};
(builder, client)
@@ -121,12 +124,19 @@ impl EmulatorBuilder {
self
}
pub fn with_watch_rom(self, p1: bool, p2: bool) -> Self {
self.watch_rom[0].store(p1, Ordering::Relaxed);
self.watch_rom[1].store(p2, Ordering::Relaxed);
self
}
pub fn build(self) -> Result<Emulator> {
let mut emulator = Emulator::new(
self.commands,
self.sim_state,
self.state,
self.audio_on,
self.watch_rom,
self.linked,
)?;
if let Some(path) = self.rom {
@@ -147,6 +157,7 @@ pub struct Emulator {
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
watch_rom: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
profilers: [Option<ProfileSender>; 2],
renderers: HashMap<SimId, TextureSink>,
@@ -165,6 +176,7 @@ impl Emulator {
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
audio_on: Arc<[AtomicBool; 2]>,
watch_rom: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>,
) -> Result<Self> {
Ok(Self {
@@ -175,6 +187,7 @@ impl Emulator {
sim_state,
state,
audio_on,
watch_rom,
linked,
profilers: [None, None],
renderers: HashMap::new(),
@@ -188,9 +201,18 @@ impl Emulator {
})
}
pub fn reload_cart(&mut self, sim_id: SimId) -> Result<()> {
let Some(cart) = &self.carts[sim_id.to_index()] else {
return Ok(());
};
let path = cart.file_path.clone();
self.load_cart(sim_id, &path)
}
pub fn load_cart(&mut self, sim_id: SimId, path: &Path) -> Result<()> {
let cart = Cart::load(path, sim_id)?;
self.reset_sim(sim_id, Some(cart))?;
let watch = self.watch_rom[sim_id.to_index()].load(Ordering::Acquire);
let cart = Cart::load(path, sim_id, watch)?;
self.try_reset_sim(sim_id, Some(cart))?;
Ok(())
}
@@ -200,16 +222,26 @@ impl Emulator {
} else {
self.carts[0].as_ref().map(|c| c.file_path.clone())
};
let watch = self.watch_rom[SimId::Player2.to_index()].load(Ordering::Acquire);
let cart = match file_path {
Some(rom_path) => Some(Cart::load(&rom_path, SimId::Player2)?),
Some(rom_path) => Some(Cart::load(&rom_path, SimId::Player2, watch)?),
None => None,
};
self.reset_sim(SimId::Player2, cart)?;
self.try_reset_sim(SimId::Player2, cart)?;
self.link_sims();
Ok(())
}
fn reset_sim(&mut self, sim_id: SimId, new_cart: Option<Cart>) -> Result<()> {
fn reset_sim(&mut self, sim_id: SimId, new_cart: Option<Cart>) -> bool {
if let Err(error) = self.try_reset_sim(sim_id, new_cart) {
self.report_error(sim_id, format!("Error resetting sim: {error}"));
false
} else {
true
}
}
fn try_reset_sim(&mut self, sim_id: SimId, new_cart: Option<Cart>) -> Result<()> {
self.save_sram(sim_id)?;
let index = sim_id.to_index();
@@ -230,6 +262,10 @@ impl Emulator {
sim.load_cart(cart.rom.clone(), cart.sram.clone())?;
self.carts[index] = Some(cart);
self.sim_state[index].store(SimState::Ready, Ordering::Release);
} else if let Some(cart) = self.carts[index].as_mut()
&& cart.is_watching()
{
cart.restart_watching();
}
let mut profiling = false;
if let Some(profiler) = self.profilers[sim_id.to_index()].as_ref()
@@ -346,7 +382,7 @@ impl Emulator {
fn start_profiling(&mut self, sim_id: SimId, sender: ProfileSender) -> Result<()> {
self.profilers[sim_id.to_index()] = Some(sender);
self.reset_sim(sim_id, None)
self.try_reset_sim(sim_id, None)
}
fn start_debugging(&mut self, sim_id: SimId, sender: DebugSender) {
@@ -422,11 +458,30 @@ impl Emulator {
let idle = self.tick();
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;
// Block the thread until a new command comes in, or a ROM changes.
loop {
match self.commands.recv_timeout(Duration::from_millis(250)) {
Ok(command) => {
self.handle_command(command);
break;
}
Err(RecvTimeoutError::Timeout) => {
let mut changed = false;
for sim_id in SimId::values() {
let Some(cart) = self.carts[sim_id.to_index()].as_ref() else {
continue;
};
if cart.changed() {
changed |= self.reset_sim(sim_id, None);
}
}
if changed {
break;
}
}
Err(RecvTimeoutError::Disconnected) => {
return;
}
}
}
}
@@ -441,6 +496,14 @@ impl Emulator {
}
}
}
for sim_id in SimId::values() {
let Some(cart) = self.carts[sim_id.to_index()].as_ref() else {
continue;
};
if cart.changed() {
self.reset_sim(sim_id, None);
}
}
self.watched_regions.retain(|range, region| {
let Some(region) = region.upgrade() else {
return false;
@@ -607,6 +670,22 @@ impl Emulator {
self.report_error(sim_id, format!("Error loading rom: {error}"));
}
}
EmulatorCommand::ReloadRom(sim_id) => {
if let Err(error) = self.reload_cart(sim_id) {
self.report_error(sim_id, format!("Error loading rom: {error}"));
}
}
EmulatorCommand::WatchRom(sim_id, watch) => {
self.watch_rom[sim_id.to_index()].store(watch, Ordering::Release);
let Some(cart) = self.carts[sim_id.to_index()].as_mut() else {
return;
};
if watch {
cart.restart_watching();
} else {
cart.stop_watching();
}
}
EmulatorCommand::StartSecondSim(path) => {
if let Err(error) = self.start_second_sim(path) {
self.report_error(
@@ -730,9 +809,7 @@ impl Emulator {
self.unlink_sims();
}
EmulatorCommand::Reset(sim_id) => {
if let Err(error) = self.reset_sim(sim_id, None) {
self.report_error(sim_id, format!("Error resetting sim: {error}"));
}
self.reset_sim(sim_id, None);
}
EmulatorCommand::SetKeys(sim_id, keys) => {
if let Some(sim) = self.sims.get_mut(sim_id.to_index()) {
@@ -774,6 +851,8 @@ impl Emulator {
pub enum EmulatorCommand {
ConnectToSim(SimId, TextureSink, mpsc::Sender<Toast>),
LoadGame(SimId, PathBuf),
ReloadRom(SimId),
WatchRom(SimId, bool),
StartSecondSim(Option<PathBuf>),
StopSecondSim,
Pause,
@@ -863,6 +942,7 @@ pub struct EmulatorClient {
sim_state: Arc<[Atomic<SimState>; 2]>,
state: Arc<Atomic<EmulatorState>>,
linked: Arc<AtomicBool>,
watch_rom: Arc<[AtomicBool; 2]>,
}
impl EmulatorClient {
@@ -875,6 +955,9 @@ impl EmulatorClient {
pub fn are_sims_linked(&self) -> bool {
self.linked.load(Ordering::Acquire)
}
pub fn is_rom_watched(&self, sim_id: SimId) -> bool {
self.watch_rom[sim_id.to_index()].load(Ordering::Acquire)
}
pub fn send_command(&self, command: EmulatorCommand) -> bool {
match self.queue.send(command) {
Ok(()) => true,