Support manual/automatic ROM reloading
This commit is contained in:
+98
-15
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user