Implement GDB/LLDB compatible server #3

Merged
SonicSwordcane merged 33 commits from debugger into main 2025-01-19 00:13:43 +00:00
4 changed files with 116 additions and 75 deletions
Showing only changes of commit a5b5f8e80f - Show all commits

10
Cargo.lock generated
View File

@ -406,6 +406,15 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "atomic"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -1752,6 +1761,7 @@ version = "0.2.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atoi", "atoi",
"atomic",
"bitflags 2.6.0", "bitflags 2.6.0",
"bytemuck", "bytemuck",
"cc", "cc",

View File

@ -10,6 +10,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
atoi = "2" atoi = "2"
atomic = "0.6"
bitflags = { version = "2", features = ["serde"] } bitflags = { version = "2", features = ["serde"] }
bytemuck = { version = "1", features = ["derive"] } bytemuck = { version = "1", features = ["derive"] }
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }

View File

@ -6,13 +6,15 @@ use std::{
ops::Range, ops::Range,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
atomic::{AtomicBool, AtomicUsize, Ordering}, atomic::{AtomicBool, Ordering},
mpsc::{self, RecvError, TryRecvError}, mpsc::{self, RecvError, TryRecvError},
Arc, Arc,
}, },
}; };
use anyhow::Result; use anyhow::Result;
use atomic::Atomic;
use bytemuck::NoUninit;
use egui_toast::{Toast, ToastKind, ToastOptions}; use egui_toast::{Toast, ToastKind, ToastOptions};
use crate::{audio::Audio, graphics::TextureSink}; use crate::{audio::Audio, graphics::TextureSink};
@ -21,16 +23,6 @@ pub use shrooms_vb_core::{VBKey, VBRegister};
mod shrooms_vb_core; 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)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub enum SimId { pub enum SimId {
Player1, 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 { impl EmulatorBuilder {
pub fn new() -> (Self, EmulatorClient) { pub fn new() -> (Self, EmulatorClient) {
let (queue, commands) = mpsc::channel(); let (queue, commands) = mpsc::channel();
let builder = Self { let builder = Self {
rom: None, rom: None,
commands, commands,
sim_count: Arc::new(AtomicUsize::new(0)), sim_state: Arc::new([
running: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), Atomic::new(SimState::Uninitialized),
has_game: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), Atomic::new(SimState::Uninitialized),
]),
state: Arc::new(Atomic::new(EmulatorState::Paused)),
audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]), audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]),
linked: Arc::new(AtomicBool::new(false)), linked: Arc::new(AtomicBool::new(false)),
}; };
let client = EmulatorClient { let client = EmulatorClient {
queue, queue,
sim_count: builder.sim_count.clone(), sim_state: builder.sim_state.clone(),
running: builder.running.clone(), state: builder.state.clone(),
has_game: builder.has_game.clone(),
audio_on: builder.audio_on.clone(), audio_on: builder.audio_on.clone(),
linked: builder.linked.clone(), linked: builder.linked.clone(),
}; };
@ -126,9 +128,8 @@ impl EmulatorBuilder {
pub fn build(self) -> Result<Emulator> { pub fn build(self) -> Result<Emulator> {
let mut emulator = Emulator::new( let mut emulator = Emulator::new(
self.commands, self.commands,
self.sim_count, self.sim_state,
self.running, self.state,
self.has_game,
self.audio_on, self.audio_on,
self.linked, self.linked,
)?; )?;
@ -144,9 +145,8 @@ pub struct Emulator {
carts: [Option<Cart>; 2], carts: [Option<Cart>; 2],
audio: Audio, audio: Audio,
commands: mpsc::Receiver<EmulatorCommand>, commands: mpsc::Receiver<EmulatorCommand>,
sim_count: Arc<AtomicUsize>, sim_state: Arc<[Atomic<SimState>; 2]>,
running: Arc<[AtomicBool; 2]>, state: Arc<Atomic<EmulatorState>>,
has_game: Arc<[AtomicBool; 2]>,
audio_on: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>, linked: Arc<AtomicBool>,
renderers: HashMap<SimId, TextureSink>, renderers: HashMap<SimId, TextureSink>,
@ -158,9 +158,8 @@ pub struct Emulator {
impl Emulator { impl Emulator {
fn new( fn new(
commands: mpsc::Receiver<EmulatorCommand>, commands: mpsc::Receiver<EmulatorCommand>,
sim_count: Arc<AtomicUsize>, sim_state: Arc<[Atomic<SimState>; 2]>,
running: Arc<[AtomicBool; 2]>, state: Arc<Atomic<EmulatorState>>,
has_game: Arc<[AtomicBool; 2]>,
audio_on: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>, linked: Arc<AtomicBool>,
) -> Result<Self> { ) -> Result<Self> {
@ -169,9 +168,8 @@ impl Emulator {
carts: [None, None], carts: [None, None],
audio: Audio::init()?, audio: Audio::init()?,
commands, commands,
sim_count, sim_state,
running, state,
has_game,
audio_on, audio_on,
linked, linked,
renderers: HashMap::new(), renderers: HashMap::new(),
@ -208,17 +206,17 @@ impl Emulator {
let index = sim_id.to_index(); let index = sim_id.to_index();
while self.sims.len() <= index { while self.sims.len() <= index {
self.sims.push(Sim::new()); 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]; let sim = &mut self.sims[index];
sim.reset(); sim.reset();
if let Some(cart) = new_cart { if let Some(cart) = new_cart {
sim.load_cart(cart.rom.clone(), cart.sram.clone())?; sim.load_cart(cart.rom.clone(), cart.sram.clone())?;
self.carts[index] = Some(cart); 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) { if self.sim_state[index].load(Ordering::Acquire) == SimState::Ready {
self.running[index].store(true, Ordering::Release); self.resume_sims();
} }
Ok(()) Ok(())
} }
@ -243,9 +241,31 @@ impl Emulator {
self.linked.store(false, Ordering::Release); self.linked.store(false, Ordering::Release);
} }
pub fn pause_sim(&mut self, sim_id: SimId) -> Result<()> { fn pause_sims(&mut self) -> Result<()> {
self.running[sim_id.to_index()].store(false, Ordering::Release); if self
self.save_sram(sim_id) .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<()> { fn save_sram(&mut self, sim_id: SimId) -> Result<()> {
@ -263,9 +283,7 @@ impl Emulator {
self.save_sram(SimId::Player2)?; self.save_sram(SimId::Player2)?;
self.renderers.remove(&SimId::Player2); self.renderers.remove(&SimId::Player2);
self.sims.truncate(1); self.sims.truncate(1);
self.sim_count.store(self.sims.len(), Ordering::Relaxed); self.sim_state[SimId::Player2.to_index()].store(SimState::Uninitialized, Ordering::Release);
self.running[SimId::Player2.to_index()].store(false, Ordering::Release);
self.has_game[SimId::Player2.to_index()].store(false, Ordering::Release);
self.linked.store(false, Ordering::Release); self.linked.store(false, Ordering::Release);
Ok(()) Ok(())
} }
@ -299,9 +317,12 @@ impl Emulator {
// returns true if the emulator is "idle" (i.e. this didn't output anything) // returns true if the emulator is "idle" (i.e. this didn't output anything)
pub fn tick(&mut self) -> bool { pub fn tick(&mut self) -> bool {
let p1_running = self.running[SimId::Player1.to_index()].load(Ordering::Acquire); let p1_state = self.sim_state[SimId::Player1.to_index()].load(Ordering::Acquire);
let p2_running = self.running[SimId::Player2.to_index()].load(Ordering::Acquire); let p2_state = self.sim_state[SimId::Player2.to_index()].load(Ordering::Acquire);
let mut idle = p1_running || p2_running; 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 { if p1_running && p2_running {
Sim::emulate_many(&mut self.sims); Sim::emulate_many(&mut self.sims);
} else if p1_running { } else if p1_running {
@ -377,19 +398,12 @@ impl Emulator {
} }
} }
EmulatorCommand::Pause => { EmulatorCommand::Pause => {
for sim_id in SimId::values() { if let Err(error) = self.pause_sims() {
if let Err(error) = self.pause_sim(sim_id) { self.report_error(SimId::Player1, format!("Error pausing: {error}"));
self.report_error(sim_id, format!("Error pausing: {error}"));
}
} }
} }
EmulatorCommand::Resume => { EmulatorCommand::Resume => {
for sim_id in SimId::values() { self.resume_sims();
let index = sim_id.to_index();
if self.has_game[index].load(Ordering::Acquire) {
self.running[index].store(true, Ordering::Relaxed);
}
}
} }
EmulatorCommand::ReadRegister(sim_id, register, done) => { EmulatorCommand::ReadRegister(sim_id, register, done) => {
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
@ -472,32 +486,43 @@ pub enum EmulatorCommand {
Exit(oneshot::Sender<()>), 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)] #[derive(Clone)]
pub struct EmulatorClient { pub struct EmulatorClient {
queue: mpsc::Sender<EmulatorCommand>, queue: mpsc::Sender<EmulatorCommand>,
sim_count: Arc<AtomicUsize>, sim_state: Arc<[Atomic<SimState>; 2]>,
running: Arc<[AtomicBool; 2]>, state: Arc<Atomic<EmulatorState>>,
has_game: Arc<[AtomicBool; 2]>,
audio_on: Arc<[AtomicBool; 2]>, audio_on: Arc<[AtomicBool; 2]>,
linked: Arc<AtomicBool>, linked: Arc<AtomicBool>,
} }
impl EmulatorClient { impl EmulatorClient {
pub fn has_player_2(&self) -> bool { pub fn sim_state(&self, sim_id: SimId) -> SimState {
self.sim_count.load(Ordering::Acquire) == 2 self.sim_state[sim_id.to_index()].load(Ordering::Acquire)
} }
pub fn is_running(&self, sim_id: SimId) -> bool { pub fn emulator_state(&self) -> EmulatorState {
self.running[sim_id.to_index()].load(Ordering::Acquire) self.state.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 is_audio_enabled(&self, sim_id: SimId) -> bool { pub fn is_audio_enabled(&self, sim_id: SimId) -> bool {
self.audio_on[sim_id.to_index()].load(Ordering::Acquire) 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 { pub fn send_command(&self, command: EmulatorCommand) -> bool {
match self.queue.send(command) { match self.queue.send(command) {
Ok(()) => true, Ok(()) => true,

View File

@ -2,7 +2,7 @@ use std::sync::mpsc;
use crate::{ use crate::{
app::UserEvent, app::UserEvent,
emulator::{EmulatorClient, EmulatorCommand, SimId}, emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
persistence::Persistence, persistence::Persistence,
}; };
use egui::{ use egui::{
@ -73,7 +73,7 @@ impl GameWindow {
.pick_file(); .pick_file();
if let Some(path) = rom { if let Some(path) = rom {
self.client self.client
.send_command(EmulatorCommand::LoadGame(SimId::Player1, path)); .send_command(EmulatorCommand::LoadGame(self.sim_id, path));
} }
ui.close_menu(); ui.close_menu();
} }
@ -82,17 +82,21 @@ impl GameWindow {
} }
}); });
ui.menu_button("Emulation", |ui| { ui.menu_button("Emulation", |ui| {
let has_game = self.client.has_game(self.sim_id); let state = self.client.emulator_state();
if self.client.is_running(self.sim_id) { let can_interact = self.client.sim_state(self.sim_id) == SimState::Ready;
if ui.add_enabled(has_game, Button::new("Pause")).clicked() { if state == EmulatorState::Running {
if ui.add_enabled(can_interact, Button::new("Pause")).clicked() {
self.client.send_command(EmulatorCommand::Pause); self.client.send_command(EmulatorCommand::Pause);
ui.close_menu(); 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); self.client.send_command(EmulatorCommand::Resume);
ui.close_menu(); 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 self.client
.send_command(EmulatorCommand::Reset(self.sim_id)); .send_command(EmulatorCommand::Reset(self.sim_id));
ui.close_menu(); ui.close_menu();
@ -100,8 +104,9 @@ impl GameWindow {
}); });
ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui)); ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui));
ui.menu_button("Multiplayer", |ui| { ui.menu_button("Multiplayer", |ui| {
let has_player_2 = self.client.sim_state(SimId::Player2) != SimState::Uninitialized;
if self.sim_id == SimId::Player1 if self.sim_id == SimId::Player1
&& !self.client.has_player_2() && !has_player_2
&& ui.button("Open Player 2").clicked() && ui.button("Open Player 2").clicked()
{ {
self.client self.client
@ -109,7 +114,7 @@ impl GameWindow {
self.proxy.send_event(UserEvent::OpenPlayer2).unwrap(); self.proxy.send_event(UserEvent::OpenPlayer2).unwrap();
ui.close_menu(); ui.close_menu();
} }
if self.client.has_player_2() { if has_player_2 {
let linked = self.client.are_sims_linked(); let linked = self.client.are_sims_linked();
if linked && ui.button("Unlink").clicked() { if linked && ui.button("Unlink").clicked() {
self.client.send_command(EmulatorCommand::Unlink); self.client.send_command(EmulatorCommand::Unlink);
@ -207,7 +212,7 @@ impl GameWindow {
let color_str = |color: Color32| { let color_str = |color: Color32| {
format!("{:02x}{:02x}{:02x}", color.r(), color.g(), color.b()) 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 { if is_running {
self.client.send_command(EmulatorCommand::Pause); self.client.send_command(EmulatorCommand::Pause);
} }