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}