use std::{ sync::{Arc, Mutex}, thread, }; use anyhow::Result; use tokio::{select, sync::mpsc}; use crate::emulator::{ EmulatorClient, EmulatorCommand, GameInfo, InlineStack, ProfileEvent, SimEvent, SimId, }; use recording::Recording; use state::ProgramState; mod recording; mod state; pub struct Profiler { sim_id: SimId, client: EmulatorClient, status: Arc>, action: Option>, killer: Option>, } impl Profiler { pub fn new(sim_id: SimId, client: EmulatorClient) -> Self { Self { sim_id, client, status: Arc::new(Mutex::new(ProfilerStatus::Disabled)), action: None, killer: None, } } pub fn status(&self) -> ProfilerStatus { self.status.lock().unwrap().clone() } pub fn enable(&mut self) { let sim_id = self.sim_id; let client = self.client.clone(); let status = self.status.clone(); let (action_tx, action_rx) = mpsc::unbounded_channel(); self.action = Some(action_tx); let (killer_tx, killer_rx) = oneshot::channel(); self.killer = Some(killer_tx); thread::spawn(move || { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async move { select! { _ = run_profile(sim_id, client, status.clone(), action_rx) => {} _ = killer_rx => { *status.lock().unwrap() = ProfilerStatus::Disabled; } } }) }); } pub fn disable(&mut self) { if let Some(killer) = self.killer.take() { let _ = killer.send(()); } } pub fn start_recording(&mut self) { if let Some(action) = &self.action { let _ = action.send(RecordingAction::Start); } } pub fn finish_recording(&mut self) -> oneshot::Receiver> { let (tx, rx) = oneshot::channel(); if let Some(action) = &self.action { let _ = action.send(RecordingAction::Finish(tx)); } rx } pub fn cancel_recording(&mut self) { if let Some(action) = &self.action { let _ = action.send(RecordingAction::Cancel); } } } impl Drop for Profiler { fn drop(&mut self) { self.disable(); } } async fn run_profile( sim_id: SimId, client: EmulatorClient, status: Arc>, mut action_source: mpsc::UnboundedReceiver, ) { let (profile_sync, mut profile_source) = mpsc::unbounded_channel(); client.send_command(EmulatorCommand::StartProfiling(sim_id, profile_sync)); *status.lock().unwrap() = ProfilerStatus::Enabled; let mut session = ProfilerSession::new(); loop { select! { maybe_event = profile_source.recv() => { let Some(event) = maybe_event else { break; // emulator thread disconnected }; if let Err(error) = handle_event(event, &mut session).await { *status.lock().unwrap() = ProfilerStatus::Error(error.to_string()); return; } } maybe_action = action_source.recv() => { let Some(action) = maybe_action else { break; // ui thread disconnected }; handle_action(action, &mut session, &status); } } } *status.lock().unwrap() = ProfilerStatus::Disabled; } async fn handle_event(event: ProfileEvent, session: &mut ProfilerSession) -> Result<()> { match event { ProfileEvent::Start { info } => session.start_profiling(info).await, ProfileEvent::Update { cycles, event, inline_stack, } => { session.track_elapsed_cycles(cycles); if let Some(event) = event { session.track_event(event)?; } if let Some(stack) = inline_stack { session.track_inline_stack(stack); } } } Ok(()) } fn handle_action( action: RecordingAction, session: &mut ProfilerSession, status: &Mutex, ) { match action { RecordingAction::Start => { session.start_recording(); *status.lock().unwrap() = ProfilerStatus::Recording; } RecordingAction::Finish(rx) => { if let Some(bytes) = session.finish_recording() { let _ = rx.send(bytes); } *status.lock().unwrap() = ProfilerStatus::Enabled; } RecordingAction::Cancel => { session.cancel_recording(); *status.lock().unwrap() = ProfilerStatus::Enabled; } } } #[derive(Clone)] pub enum ProfilerStatus { Disabled, Enabled, Recording, Error(String), } impl ProfilerStatus { pub fn enabled(&self) -> bool { matches!(self, Self::Enabled | Self::Recording) } } enum RecordingAction { Start, Finish(oneshot::Sender>), Cancel, } struct ProfilerSession { program: Option, recording: Option, } impl ProfilerSession { fn new() -> Self { Self { program: None, recording: None, } } async fn start_profiling(&mut self, info: Arc) { let program = ProgramState::new(info).await; let recording = if self.recording.is_some() { Some(Recording::new(&program)) } else { None }; self.program = Some(program); self.recording = recording; } fn track_elapsed_cycles(&mut self, cycles: u32) { if let (Some(state), Some(recording)) = (&self.program, &mut self.recording) { recording.track_elapsed_cycles(state, cycles); } } fn track_event(&mut self, event: SimEvent) -> Result<()> { let Some(program) = &mut self.program else { return Ok(()); }; match event { SimEvent::Call(address) => program.track_call(address), SimEvent::Return => program.track_return(), SimEvent::Halt => program.track_halt(), SimEvent::Interrupt(code, address) => program.track_interrupt(code, address), SimEvent::Reti => program.track_reti(), SimEvent::Marker(name) => { if let Some(recording) = &mut self.recording { recording.track_marker(name); }; Ok(()) } } } fn track_inline_stack(&mut self, inline_stack: InlineStack) { if let Some(program) = &mut self.program { program.track_inline_stack(inline_stack); } } fn start_recording(&mut self) { if let Some(program) = &self.program { self.recording = Some(Recording::new(program)); } } fn finish_recording(&mut self) -> Option> { self.recording.take().map(|r| r.finish()) } fn cancel_recording(&mut self) { self.recording.take(); } }