From 0c6c9ec4c3d8a60074a4cdb5c99494e8f9a11549 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 9 Aug 2025 23:45:34 -0400 Subject: [PATCH] Start profiler UI/thread --- src/app.rs | 17 ++++++++- src/emulator.rs | 79 ++++++++++++++++++++++++++++------------ src/emulator/profiler.rs | 23 ------------ src/main.rs | 13 ++++--- src/profiler.rs | 79 ++++++++++++++++++++++++++++++++++++++++ src/window.rs | 2 + src/window/game.rs | 5 +++ src/window/profile.rs | 54 +++++++++++++++++++++++++++ 8 files changed, 218 insertions(+), 54 deletions(-) delete mode 100644 src/emulator/profiler.rs create mode 100644 src/profiler.rs create mode 100644 src/window/profile.rs diff --git a/src/app.rs b/src/app.rs index 6efd9dd..b553226 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,8 +24,8 @@ use crate::{ persistence::Persistence, window::{ AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow, - GdbServerWindow, HotkeysWindow, InputWindow, ObjectWindow, RegisterWindow, TerminalWindow, - WorldWindow, + GdbServerWindow, HotkeysWindow, InputWindow, ObjectWindow, ProfileWindow, RegisterWindow, + TerminalWindow, WorldWindow, }, }; @@ -54,6 +54,7 @@ pub struct Application { viewports: HashMap, focused: Option, init_debug_port: Option, + init_profiling: bool, } impl Application { @@ -61,6 +62,7 @@ impl Application { client: EmulatorClient, proxy: EventLoopProxy, debug_port: Option, + profiling: bool, ) -> Self { let wgpu = WgpuState::new(); let icon = load_icon().ok().map(Arc::new); @@ -89,6 +91,7 @@ impl Application { viewports: HashMap::new(), focused: None, init_debug_port: debug_port, + init_profiling: profiling, } } @@ -113,6 +116,11 @@ impl ApplicationHandler for Application { server.launch(port); self.open(event_loop, Box::new(server)); } + if self.init_profiling { + let mut profiler = ProfileWindow::new(SimId::Player1, self.client.clone()); + profiler.launch(); + self.open(event_loop, Box::new(profiler)); + } let app = GameWindow::new( self.client.clone(), self.proxy.clone(), @@ -248,6 +256,10 @@ impl ApplicationHandler for Application { let terminal = TerminalWindow::new(sim_id, &self.client); self.open(event_loop, Box::new(terminal)); } + UserEvent::OpenProfiler(sim_id) => { + let profile = ProfileWindow::new(sim_id, self.client.clone()); + self.open(event_loop, Box::new(profile)); + } UserEvent::OpenDebugger(sim_id) => { let debugger = GdbServerWindow::new(sim_id, self.client.clone(), self.proxy.clone()); @@ -522,6 +534,7 @@ pub enum UserEvent { OpenFrameBuffers(SimId), OpenRegisters(SimId), OpenTerminal(SimId), + OpenProfiler(SimId), OpenDebugger(SimId), OpenInput, OpenHotkeys, diff --git a/src/emulator.rs b/src/emulator.rs index 4ea5860..af44efa 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -18,7 +18,7 @@ use tracing::{error, warn}; use crate::{ audio::Audio, - emulator::{cart::Cart, profiler::Profiler}, + emulator::{cart::Cart, shrooms_vb_core::SimEvent}, graphics::TextureSink, memory::{MemoryRange, MemoryRegion}, }; @@ -27,7 +27,6 @@ pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType}; mod address_set; mod cart; -mod profiler; mod shrooms_vb_core; #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] @@ -70,7 +69,6 @@ pub struct EmulatorBuilder { audio_on: Arc<[AtomicBool; 2]>, linked: Arc, start_paused: bool, - monitor_events: bool, } impl EmulatorBuilder { @@ -87,7 +85,6 @@ impl EmulatorBuilder { audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]), linked: Arc::new(AtomicBool::new(false)), start_paused: false, - monitor_events: false, }; let client = EmulatorClient { queue, @@ -113,13 +110,6 @@ impl EmulatorBuilder { } } - pub fn monitor_events(self, monitor_events: bool) -> Self { - Self { - monitor_events, - ..self - } - } - pub fn build(self) -> Result { let mut emulator = Emulator::new( self.commands, @@ -134,9 +124,6 @@ impl EmulatorBuilder { if self.start_paused { emulator.pause_sims()?; } - if self.monitor_events { - emulator.monitor_events(SimId::Player1)?; - } Ok(emulator) } } @@ -150,7 +137,7 @@ pub struct Emulator { state: Arc>, audio_on: Arc<[AtomicBool; 2]>, linked: Arc, - profilers: [Profiler; 2], + profilers: [Option; 2], renderers: HashMap, messages: HashMap>, debuggers: HashMap, @@ -178,7 +165,7 @@ impl Emulator { state, audio_on, linked, - profilers: [Profiler::new(), Profiler::new()], + profilers: [None, None], renderers: HashMap::new(), messages: HashMap::new(), debuggers: HashMap::new(), @@ -228,12 +215,29 @@ impl Emulator { } let sim = &mut self.sims[index]; sim.reset(); - sim.monitor_events(self.profilers[sim_id.to_index()].is_enabled()); if let Some(cart) = new_cart { sim.load_cart(cart.rom.clone(), cart.sram.clone())?; self.carts[index] = Some(cart); self.sim_state[index].store(SimState::Ready, Ordering::Release); } + let mut profiling = false; + if let Some(profiler) = self.profilers[sim_id.to_index()].as_ref() { + if let Some(cart) = self.carts[index].as_ref() { + if profiler + .send(ProfileEvent::Start { + file_path: cart.file_path.clone(), + }) + .is_ok() + { + sim.monitor_events(true); + profiling = true; + } + } + } + if !profiling { + sim.monitor_events(false); + } + if self.sim_state[index].load(Ordering::Acquire) == SimState::Ready { self.resume_sims(); } @@ -331,6 +335,11 @@ impl Emulator { Ok(()) } + 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) + } + fn start_debugging(&mut self, sim_id: SimId, sender: DebugSender) { if self.sim_state[sim_id.to_index()].load(Ordering::Acquire) != SimState::Ready { // Can't debug unless a game is connected @@ -399,11 +408,6 @@ impl Emulator { self.watched_regions.insert(range, region); } - fn monitor_events(&mut self, sim_id: SimId) -> Result<()> { - self.profilers[sim_id.to_index()].enable(true); - self.reset_sim(sim_id, None) - } - pub fn run(&mut self) { loop { let idle = self.tick(); @@ -469,10 +473,20 @@ impl Emulator { .zip(self.profilers.iter_mut()) .zip([p1_running, p2_running]) { - if !running || !profiler.is_enabled() { + if !running { continue; } - profiler.track(cycles, sim.take_sim_event()); + if let Some(p) = profiler { + if p.send(ProfileEvent::Update { + cycles, + event: sim.take_sim_event(), + }) + .is_err() + { + sim.monitor_events(false); + *profiler = None; + } + } } if state == EmulatorState::Stepping { @@ -614,6 +628,11 @@ impl Emulator { self.report_error(SimId::Player1, format!("Error setting speed: {error}")); } } + EmulatorCommand::StartProfiling(sim_id, profiler) => { + if let Err(error) = self.start_profiling(sim_id, profiler) { + self.report_error(SimId::Player1, format!("Error enaling profiler: {error}")); + } + } EmulatorCommand::StartDebugging(sim_id, debugger) => { self.start_debugging(sim_id, debugger); } @@ -751,6 +770,7 @@ pub enum EmulatorCommand { Resume, FrameAdvance, SetSpeed(f64), + StartProfiling(SimId, ProfileSender), StartDebugging(SimId, DebugSender), StopDebugging(SimId), DebugInterrupt(SimId), @@ -792,6 +812,7 @@ pub enum EmulatorState { Debugging, } +type ProfileSender = tokio::sync::mpsc::UnboundedSender; type DebugSender = tokio::sync::mpsc::UnboundedSender; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -815,6 +836,16 @@ pub enum DebugEvent { Stopped(DebugStopReason), } +pub enum ProfileEvent { + Start { + file_path: PathBuf, + }, + Update { + cycles: u32, + event: Option, + }, +} + #[derive(Clone)] pub struct EmulatorClient { queue: mpsc::Sender, diff --git a/src/emulator/profiler.rs b/src/emulator/profiler.rs deleted file mode 100644 index 0674d68..0000000 --- a/src/emulator/profiler.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::emulator::shrooms_vb_core::SimEvent; - -pub struct Profiler { - enabled: bool, -} - -impl Profiler { - pub fn new() -> Self { - Self { enabled: false } - } - - pub fn enable(&mut self, enabled: bool) { - self.enabled = enabled; - } - - pub fn is_enabled(&self) -> bool { - self.enabled - } - - pub fn track(&mut self, cycles: u32, event: Option) { - println!("profiler {cycles} {event:0x?}"); - } -} diff --git a/src/main.rs b/src/main.rs index a899c03..04f823c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ mod images; mod input; mod memory; mod persistence; +mod profiler; mod window; #[derive(Parser)] @@ -110,10 +111,7 @@ fn main() -> Result<()> { builder = builder.start_paused(true); } if args.profile { - if args.rom.is_none() { - bail!("to start profiling, please select a game."); - } - builder = builder.monitor_events(true) + builder = builder.start_paused(true) } ThreadBuilder::default() @@ -133,6 +131,11 @@ fn main() -> Result<()> { let event_loop = EventLoop::with_user_event().build().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let proxy = event_loop.create_proxy(); - event_loop.run_app(&mut Application::new(client, proxy, args.debug_port))?; + event_loop.run_app(&mut Application::new( + client, + proxy, + args.debug_port, + args.profile, + ))?; Ok(()) } diff --git a/src/profiler.rs b/src/profiler.rs new file mode 100644 index 0000000..6004f44 --- /dev/null +++ b/src/profiler.rs @@ -0,0 +1,79 @@ +use std::{ + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + thread, +}; + +use tokio::{select, sync::mpsc}; + +use crate::emulator::{EmulatorClient, EmulatorCommand, ProfileEvent, SimId}; + +pub struct Profiler { + sim_id: SimId, + client: EmulatorClient, + running: Arc, + killer: Option>, +} + +impl Profiler { + pub fn new(sim_id: SimId, client: EmulatorClient) -> Self { + Self { + sim_id, + client, + running: Arc::new(AtomicBool::new(false)), + killer: None, + } + } + + pub fn started(&self) -> bool { + self.running.load(Ordering::Relaxed) + } + + pub fn start(&mut self) { + let sim_id = self.sim_id; + let client = self.client.clone(); + let running = self.running.clone(); + let (tx, rx) = oneshot::channel(); + self.killer = Some(tx); + thread::spawn(move || { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async move { + select! { + _ = run_profile(sim_id, client, running.clone()) => {} + _ = rx => { + running.store(false, Ordering::Relaxed); + } + } + }) + }); + } + + pub fn stop(&mut self) { + if let Some(killer) = self.killer.take() { + let _ = killer.send(()); + } + } +} + +async fn run_profile(sim_id: SimId, client: EmulatorClient, running: Arc) { + let (profile_sync, mut profile_source) = mpsc::unbounded_channel(); + client.send_command(EmulatorCommand::StartProfiling(sim_id, profile_sync)); + + running.store(true, Ordering::Relaxed); + while let Some(event) = profile_source.recv().await { + match event { + ProfileEvent::Start { file_path } => { + println!("profiling {}", file_path.display()); + } + ProfileEvent::Update { cycles, event } => { + println!("update {cycles} {event:#x?}"); + } + } + } + running.store(false, Ordering::Release); +} diff --git a/src/window.rs b/src/window.rs index 502fae4..237f262 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,6 +4,7 @@ pub use game::GameWindow; pub use gdb::GdbServerWindow; pub use hotkeys::HotkeysWindow; pub use input::InputWindow; +pub use profile::ProfileWindow; pub use terminal::TerminalWindow; pub use vip::{ BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow, @@ -18,6 +19,7 @@ mod game_screen; mod gdb; mod hotkeys; mod input; +mod profile; mod terminal; mod utils; mod vip; diff --git a/src/window/game.rs b/src/window/game.rs index 51b6d24..3a20992 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -228,6 +228,11 @@ impl GameWindow { .send_event(UserEvent::OpenTerminal(self.sim_id)) .unwrap(); } + if ui.button("Profiler").clicked() { + self.proxy + .send_event(UserEvent::OpenProfiler(self.sim_id)) + .unwrap(); + } if ui.button("GDB Server").clicked() { self.proxy .send_event(UserEvent::OpenDebugger(self.sim_id)) diff --git a/src/window/profile.rs b/src/window/profile.rs new file mode 100644 index 0000000..41de435 --- /dev/null +++ b/src/window/profile.rs @@ -0,0 +1,54 @@ +use egui::{CentralPanel, ViewportBuilder, ViewportId}; + +use crate::{ + emulator::{EmulatorClient, SimId}, + profiler::Profiler, + window::AppWindow, +}; + +pub struct ProfileWindow { + sim_id: SimId, + profiler: Profiler, +} + +impl ProfileWindow { + pub fn new(sim_id: SimId, client: EmulatorClient) -> Self { + Self { + sim_id, + profiler: Profiler::new(sim_id, client), + } + } + + pub fn launch(&mut self) { + self.profiler.start(); + } +} + +impl AppWindow for ProfileWindow { + fn viewport_id(&self) -> ViewportId { + ViewportId::from_hash_of(format!("Profile-{}", self.sim_id)) + } + + fn sim_id(&self) -> SimId { + self.sim_id + } + + fn initial_viewport(&self) -> ViewportBuilder { + ViewportBuilder::default() + .with_title(format!("Profiler ({})", self.sim_id)) + .with_inner_size((300.0, 200.0)) + } + + fn show(&mut self, ctx: &egui::Context) { + CentralPanel::default().show(ctx, |ui| { + let mut started = self.profiler.started(); + if ui.checkbox(&mut started, "Profiling enabled?").changed() { + if started { + self.profiler.start(); + } else { + self.profiler.stop(); + } + } + }); + } +}