1use crate::DebugRenderContext; 2use jgenesis_common::frontend::{ 3 AudioOutput, EmulatorTrait, InputPoller, PartialClone, Renderer, SaveWriter, TickEffect, 4}; 5use jgenesis_common::sync::{SharedVarReceiver, SharedVarSender}; 6use std::error::Error; 7 8pub type RunTillNextResult<Emulator, RErr, AErr, SErr> = 9 Result<(), <Emulator as EmulatorTrait>::Err<RErr, AErr, SErr>>; 10 11pub trait DebuggerRunnerProcess<Emulator, R, A, I, S>: Send + 'static 12where 13 Emulator: EmulatorTrait, 14 R: Renderer, 15 A: AudioOutput, 16 I: InputPoller<Emulator::Inputs>, 17 S: SaveWriter, 18{ 19 /// Run periodic processing, e.g. copying and sending emulator state to the frontend. This will 20 /// generally get called once per frame while the emulator is running. 21 /// 22 /// # Errors 23 /// 24 /// May propagate any errors encountered during processing. 25 fn run( 26 &mut self, 27 emulator: &mut Emulator, 28 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>>; 29 30 /// Run the emulator until the next frame render. 31 /// 32 /// This exists as a hook so that, if desired, the debugger can call a different emulator entry 33 /// point while the debugger is active. 34 /// 35 /// # Errors 36 /// 37 /// Should propagate any errors encountered while running the emulator. 38 fn run_emulator_till_next_frame( 39 &mut self, 40 emulator: &mut Emulator, 41 renderer: &mut R, 42 audio_output: &mut A, 43 input_poller: &mut I, 44 save_writer: &mut S, 45 ) -> RunTillNextResult<Emulator, R::Err, A::Err, S::Err> { 46 while emulator.tick(renderer, audio_output, input_poller, save_writer)? 47 != TickEffect::FrameRendered 48 {} 49 50 Ok(()) 51 } 52} 53 54pub trait DebuggerMainProcess { 55 /// Render the debugger frontend. 56 /// 57 /// # Errors 58 /// 59 /// May return any errors encountered while processing or rendering. 60 fn run( 61 &mut self, 62 ctx: DebugRenderContext<'_>, 63 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>>; 64} 65 66pub type DebuggerProcesses<Emulator, R, A, I, S> = 67 (Box<dyn DebuggerRunnerProcess<Emulator, R, A, I, S>>, Box<dyn DebuggerMainProcess>); 68 69pub type DebugFn<Emulator, R, A, I, S> = fn() -> DebuggerProcesses<Emulator, R, A, I, S>; 70 71pub struct NullDebugger; 72 73impl<Emulator, R, A, I, S> DebuggerRunnerProcess<Emulator, R, A, I, S> for NullDebugger 74where 75 Emulator: EmulatorTrait, 76 R: Renderer, 77 A: AudioOutput, 78 I: InputPoller<Emulator::Inputs>, 79 S: SaveWriter, 80{ 81 fn run( 82 &mut self, 83 _emulator: &mut Emulator, 84 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 85 Ok(()) 86 } 87} 88 89impl DebuggerMainProcess for NullDebugger { 90 fn run( 91 &mut self, 92 _ctx: DebugRenderContext<'_>, 93 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 94 Ok(()) 95 } 96} 97 98/// Create a dummy debugger implementation that implements the required traits but does not actually 99/// do anything. 100#[must_use] 101pub fn null_debug_fn<Emulator, R, A, I, S>() -> DebuggerProcesses<Emulator, R, A, I, S> 102where 103 Emulator: EmulatorTrait, 104 R: Renderer, 105 A: AudioOutput, 106 I: InputPoller<Emulator::Inputs>, 107 S: SaveWriter, 108{ 109 (Box::new(NullDebugger), Box::new(NullDebugger)) 110} 111 112pub struct PartialCloneRunnerProcess<Emulator> { 113 emulator_sender: SharedVarSender<Emulator>, 114} 115 116impl<Emulator, R, A, I, S> DebuggerRunnerProcess<Emulator, R, A, I, S> 117 for PartialCloneRunnerProcess<Emulator> 118where 119 Emulator: EmulatorTrait + PartialClone + Send + Sync + 'static, 120 R: Renderer, 121 A: AudioOutput, 122 I: InputPoller<Emulator::Inputs>, 123 S: SaveWriter, 124{ 125 fn run( 126 &mut self, 127 emulator: &mut Emulator, 128 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 129 self.emulator_sender.update(emulator.partial_clone()); 130 131 Ok(()) 132 } 133} 134 135pub struct CloneRunnerProcess<Emulator> { 136 emulator_sender: SharedVarSender<Emulator>, 137} 138 139impl<Emulator, R, A, I, S> DebuggerRunnerProcess<Emulator, R, A, I, S> 140 for CloneRunnerProcess<Emulator> 141where 142 Emulator: EmulatorTrait + Clone + Send + Sync + 'static, 143 R: Renderer, 144 A: AudioOutput, 145 I: InputPoller<Emulator::Inputs>, 146 S: SaveWriter, 147{ 148 fn run( 149 &mut self, 150 emulator: &mut Emulator, 151 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 152 self.emulator_sender.update(emulator.clone()); 153 154 Ok(()) 155 } 156} 157 158pub type DebugRenderFn<Emulator> = dyn FnMut(DebugRenderContext<'_>, &mut Emulator); 159 160pub struct CloneMainProcess<Emulator> { 161 emulator_receiver: SharedVarReceiver<Emulator>, 162 render_fn: Box<DebugRenderFn<Emulator>>, 163} 164 165impl<Emulator: Send + Sync + 'static> DebuggerMainProcess for CloneMainProcess<Emulator> { 166 fn run( 167 &mut self, 168 ctx: DebugRenderContext<'_>, 169 ) -> Result<(), Box<dyn Error + Send + Sync + 'static>> { 170 if let Some(emulator) = self.emulator_receiver.get() { 171 (self.render_fn)(ctx, emulator); 172 } 173 174 Ok(()) 175 } 176} 177 178/// Create a debugger implementation that sends the emulator state to the debugger frontend once 179/// per frame via [`jgenesis_common::frontend::PartialClone::partial_clone`]. 180/// 181/// This implementation does not support mutating emulator state or sending commands to the emulator 182/// runner thread. `render_fn` is passed a copy of the current emulator state, not the emulator itself. 183#[must_use] 184pub fn partial_clone_debug_fn<Emulator, R, A, I, S>( 185 render_fn: Box<DebugRenderFn<Emulator>>, 186) -> DebuggerProcesses<Emulator, R, A, I, S> 187where 188 Emulator: EmulatorTrait + PartialClone + Send + Sync + 'static, 189 R: Renderer, 190 A: AudioOutput, 191 I: InputPoller<Emulator::Inputs>, 192 S: SaveWriter, 193{ 194 let (emulator_sender, emulator_receiver) = jgenesis_common::sync::new_shared_var(); 195 196 let runner_process = PartialCloneRunnerProcess { emulator_sender }; 197 let main_process = CloneMainProcess { emulator_receiver, render_fn }; 198 199 (Box::new(runner_process), Box::new(main_process)) 200} 201 202/// Similar to [`partial_clone_debug_fn`] but invokes [`Clone::clone`] instead of 203/// [`jgenesis_common::frontend::PartialClone::partial_clone`]. 204/// 205/// Useful when the debug view needs information that is not included in a partial clone, e.g. 206/// cartridge ROM. 207#[must_use] 208pub fn clone_debug_fn<Emulator, R, A, I, S>( 209 render_fn: Box<DebugRenderFn<Emulator>>, 210) -> DebuggerProcesses<Emulator, R, A, I, S> 211where 212 Emulator: EmulatorTrait + Clone + Send + Sync + 'static, 213 R: Renderer, 214 A: AudioOutput, 215 I: InputPoller<Emulator::Inputs>, 216 S: SaveWriter, 217{ 218 let (emulator_sender, emulator_receiver) = jgenesis_common::sync::new_shared_var(); 219 220 let runner_process = CloneRunnerProcess { emulator_sender }; 221 let main_process = CloneMainProcess { emulator_receiver, render_fn }; 222 223 (Box::new(runner_process), Box::new(main_process)) 224}