1use crate::app::ActiveCheats;
2use genesis_config::cheats::GenesisCheats;
3use genesis_core::api::GenesisHardware;
4use jgenesis_native_config::AppConfig;
5use jgenesis_native_driver::config::AppConfigExt;
6use jgenesis_native_driver::extensions::Console;
7use jgenesis_native_driver::{
8    NativeEmulatorError, NativeEmulatorResult, NativeGameBoyEmulator, NativeGbaEmulator,
9    NativeGenesisEmulator, NativeNesEmulator, NativeSmsGgEmulator, NativeSnesEmulator,
10    NativeTickEffect, SaveStateMetadata,
11};
12use jgenesis_native_driver::{NativePcEngineEmulator, SdlSubsystems};
13use jgenesis_proc_macros::MatchEachVariantMacro;
14use smsgg_config::cheats::SmsGgCheats;
15use smsgg_core::SmsGgHardware;
16use std::cell::{Cell, Ref, RefCell};
17use std::path::PathBuf;
18use std::rc::Rc;
19use std::sync::Arc;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(test, derive(jgenesis_proc_macros::EnumAll))]
23pub enum EmuRunnerStatus {
24    Idle,
25    WaitingForFirstCommand,
26    RunningSms,
27    RunningGameGear,
28    RunningGenesis,
29    RunningSegaCd,
30    Running32X,
31    RunningSegaCd32X,
32    RunningNes,
33    RunningSnes,
34    RunningGameBoy,
35    RunningGba,
36    RunningPcEngine,
37}
38
39impl EmuRunnerStatus {
40    pub fn is_running(self) -> bool {
41        matches!(
42            self,
43            Self::RunningSms
44                | Self::RunningGenesis
45                | Self::RunningSegaCd
46                | Self::Running32X
47                | Self::RunningSegaCd32X
48                | Self::RunningNes
49                | Self::RunningSnes
50                | Self::RunningGameBoy
51                | Self::RunningGba
52                | Self::RunningGameGear
53                | Self::RunningPcEngine
54        )
55    }
56
57    pub fn is_running_genesis(self) -> bool {
58        matches!(
59            self,
60            Self::RunningGenesis | Self::RunningSegaCd | Self::Running32X | Self::RunningSegaCd32X
61        )
62    }
63
64    pub fn is_running_smsgg(self) -> bool {
65        matches!(self, Self::RunningSms | Self::RunningGameGear)
66    }
67
68    pub fn is_running_handheld(self) -> bool {
69        matches!(self, Self::RunningGameGear | Self::RunningGameBoy | Self::RunningGba)
70    }
71
72    pub fn is_running_disc_based(self) -> bool {
73        matches!(self, Self::RunningSegaCd | Self::RunningSegaCd32X)
74    }
75
76    pub fn running_console(self) -> Option<Console> {
77        match self {
78            Self::RunningSms => Some(Console::MasterSystem),
79            Self::RunningGameGear => Some(Console::GameGear),
80            Self::RunningGenesis => Some(Console::Genesis),
81            Self::RunningSegaCd => Some(Console::SegaCd),
82            Self::Running32X => Some(Console::Sega32X),
83            Self::RunningSegaCd32X => Some(Console::SegaCd32X),
84            Self::RunningNes => Some(Console::Nes),
85            Self::RunningSnes => Some(Console::Snes),
86            Self::RunningGameBoy => Some(Console::GameBoy),
87            Self::RunningGba => Some(Console::GameBoyAdvance),
88            Self::RunningPcEngine => Some(Console::PcEngine),
89            EmuRunnerStatus::Idle | EmuRunnerStatus::WaitingForFirstCommand => None,
90        }
91    }
92}
93
94trait ConsoleExt {
95    fn running_status(self) -> EmuRunnerStatus;
96}
97
98impl ConsoleExt for Console {
99    fn running_status(self) -> EmuRunnerStatus {
100        match self {
101            Self::MasterSystem | Self::Sg1000 => EmuRunnerStatus::RunningSms,
102            Self::GameGear => EmuRunnerStatus::RunningGameGear,
103            Self::Genesis => EmuRunnerStatus::RunningGenesis,
104            Self::SegaCd | Self::SegaCd32X => EmuRunnerStatus::RunningSegaCd,
105            Self::Sega32X => EmuRunnerStatus::Running32X,
106            Self::Nes => EmuRunnerStatus::RunningNes,
107            Self::Snes => EmuRunnerStatus::RunningSnes,
108            Self::GameBoy | Self::GameBoyColor => EmuRunnerStatus::RunningGameBoy,
109            Self::GameBoyAdvance => EmuRunnerStatus::RunningGba,
110            Self::PcEngine => EmuRunnerStatus::RunningPcEngine,
111        }
112    }
113}
114
115#[derive(Debug, Clone)]
116pub enum EmulatorRunInput {
117    OpenFile { file_path: PathBuf, secondary_paths: Vec<PathBuf> },
118    RunBios,
119}
120
121#[derive(Debug, Clone)]
122pub enum EmuRunnerCommand {
123    Run {
124        console: Console,
125        config: Box<AppConfig>,
126        cheats: Arc<ActiveCheats>,
127        input: EmulatorRunInput,
128    },
129    ReloadConfig(Box<AppConfig>, Arc<ActiveCheats>),
130    StopEmulator,
131    SoftReset,
132    HardReset,
133    OpenMemoryViewer,
134    SaveState {
135        slot: usize,
136    },
137    LoadState {
138        slot: usize,
139    },
140    SegaCdRemoveDisc,
141    SegaCdChangeDisc(PathBuf),
142}
143
144pub struct GuiEmulatorRunner {
145    emulator: Option<GenericEmulator>,
146    emulator_error: Rc<RefCell<Option<NativeEmulatorError>>>,
147    sdl: SdlSubsystems,
148    egui_ctx: egui::Context,
149    status: Rc<Cell<EmuRunnerStatus>>,
150    exit_signal: Rc<Cell<bool>>,
151    gui_focused: Rc<Cell<bool>>,
152    save_state_metadata: Rc<RefCell<SaveStateMetadata>>,
153    queued_commands: Rc<RefCell<Vec<EmuRunnerCommand>>>,
154}
155
156pub struct GuiEmulatorRunnerHandle {
157    emulator_error: Rc<RefCell<Option<NativeEmulatorError>>>,
158    status: Rc<Cell<EmuRunnerStatus>>,
159    exit_signal: Rc<Cell<bool>>,
160    gui_focused: Rc<Cell<bool>>,
161    save_state_metadata: Rc<RefCell<SaveStateMetadata>>,
162    queued_commands: Rc<RefCell<Vec<EmuRunnerCommand>>>,
163}
164
165impl GuiEmulatorRunner {
166    #[must_use]
167    pub fn new(sdl: SdlSubsystems, egui_ctx: egui::Context) -> (Self, GuiEmulatorRunnerHandle) {
168        let runner = Self {
169            emulator: None,
170            emulator_error: Rc::new(RefCell::new(None)),
171            sdl,
172            egui_ctx,
173            status: Rc::new(Cell::new(EmuRunnerStatus::WaitingForFirstCommand)),
174            exit_signal: Rc::new(Cell::new(false)),
175            gui_focused: Rc::new(Cell::new(true)),
176            save_state_metadata: Rc::new(RefCell::new(SaveStateMetadata::default())),
177            queued_commands: Rc::new(RefCell::new(Vec::new())),
178        };
179
180        let handle = GuiEmulatorRunnerHandle {
181            emulator_error: Rc::clone(&runner.emulator_error),
182            status: Rc::clone(&runner.status),
183            exit_signal: Rc::clone(&runner.exit_signal),
184            gui_focused: Rc::clone(&runner.gui_focused),
185            save_state_metadata: Rc::clone(&runner.save_state_metadata),
186            queued_commands: Rc::clone(&runner.queued_commands),
187        };
188
189        (runner, handle)
190    }
191
192    pub fn run(&mut self, sdl_event_handler: &mut dyn FnMut(&sdl3::event::Event)) {
193        self.process_commands();
194
195        let Some(emulator) = &mut self.emulator else {
196            self.drain_sdl_events(sdl_event_handler);
197            return;
198        };
199
200        emulator.update_gui_focused(self.gui_focused.get());
201        *self.save_state_metadata.borrow_mut() = emulator.save_state_metadata();
202
203        match emulator.run(sdl_event_handler) {
204            Ok(None) => {}
205            Ok(Some(NativeTickEffect::PowerOff)) => {
206                self.stop_emulator();
207            }
208            Ok(Some(NativeTickEffect::Exit)) => {
209                self.stop_emulator();
210                self.exit_signal.set(true);
211            }
212            Err(err) => {
213                log::error!("Emulator terminated with an error: {err}");
214                self.stop_emulator();
215                *self.emulator_error.borrow_mut() = Some(err);
216            }
217        }
218    }
219
220    fn process_commands(&mut self) {
221        for command in Rc::clone(&self.queued_commands).borrow_mut().drain(..) {
222            match command {
223                EmuRunnerCommand::Run { console, config, cheats, input } => {
224                    self.launch_emulator(console, config, cheats, input);
225                }
226                EmuRunnerCommand::ReloadConfig(config, cheats) => {
227                    self.do_with_emulator(|emulator| emulator.reload_config(config, cheats));
228                }
229                EmuRunnerCommand::StopEmulator => {
230                    self.stop_emulator();
231                }
232                EmuRunnerCommand::SoftReset => {
233                    self.do_with_emulator(GenericEmulator::soft_reset);
234                }
235                EmuRunnerCommand::HardReset => {
236                    self.do_with_emulator(GenericEmulator::hard_reset);
237                }
238                EmuRunnerCommand::OpenMemoryViewer => {
239                    self.do_with_emulator(GenericEmulator::open_memory_viewer);
240                }
241                EmuRunnerCommand::SaveState { slot } => {
242                    self.do_with_emulator(|emulator| emulator.save_state(slot));
243                }
244                EmuRunnerCommand::LoadState { slot } => {
245                    self.do_with_emulator(|emulator| emulator.load_state(slot));
246                }
247                EmuRunnerCommand::SegaCdRemoveDisc => {
248                    self.do_with_emulator(GenericEmulator::remove_disc);
249                }
250                EmuRunnerCommand::SegaCdChangeDisc(path) => {
251                    self.do_with_emulator(|emulator| emulator.change_disc(path));
252                }
253            }
254        }
255    }
256
257    fn drain_sdl_events(&mut self, sdl_event_handler: &mut dyn FnMut(&sdl3::event::Event)) {
258        assert!(
259            self.emulator.is_none(),
260            "drain_sdl_events() should never be called while an emulator is running"
261        );
262
263        let mut event_pump = self.sdl.event_pump.borrow_mut();
264
265        for event in event_pump.poll_iter() {
266            sdl_event_handler(&event);
267
268            if let Err(err) = self.sdl.joysticks.borrow_mut().handle_sdl_event(&event) {
269                log::error!("Error handling joystick event: {err}");
270            }
271        }
272    }
273
274    fn launch_emulator(
275        &mut self,
276        console: Console,
277        config: Box<AppConfig>,
278        cheats: Arc<ActiveCheats>,
279        input: EmulatorRunInput,
280    ) {
281        if self.emulator.is_some() {
282            self.stop_emulator();
283        }
284
285        let emulator = match input {
286            EmulatorRunInput::OpenFile { file_path, secondary_paths } => GenericEmulator::create(
287                self.sdl.clone(),
288                console,
289                config,
290                cheats,
291                file_path,
292                secondary_paths,
293            )
294            .map(Some),
295            EmulatorRunInput::RunBios => {
296                GenericEmulator::create_run_bios(self.sdl.clone(), console, config)
297            }
298        };
299
300        let emulator = match emulator {
301            Ok(Some(emulator)) => emulator,
302            Ok(None) => return,
303            Err(err) => {
304                log::error!("Error initializing emulator: {err}");
305                self.status.set(EmuRunnerStatus::Idle);
306                *self.emulator_error.borrow_mut() = Some(err);
307                self.egui_ctx.request_repaint();
308                return;
309            }
310        };
311
312        self.emulator = Some(emulator);
313        self.status.set(console.running_status());
314        self.egui_ctx.request_repaint();
315    }
316
317    fn stop_emulator(&mut self) {
318        self.emulator = None;
319        self.status.set(EmuRunnerStatus::Idle);
320
321        // Force a repaint after an emulator exits. This will immediately display the error window
322        // if there was an error, and it will also force quit immediately if auto-close is enabled
323        self.egui_ctx.request_repaint();
324    }
325
326    fn do_with_emulator(
327        &mut self,
328        action: impl FnOnce(&mut GenericEmulator) -> NativeEmulatorResult<()>,
329    ) {
330        let Some(emulator) = &mut self.emulator else { return };
331
332        if let Err(err) = action(emulator) {
333            self.stop_emulator();
334            *self.emulator_error.borrow_mut() = Some(err);
335        }
336    }
337}
338
339impl GuiEmulatorRunnerHandle {
340    #[must_use]
341    pub fn status(&self) -> EmuRunnerStatus {
342        self.status.get()
343    }
344
345    #[must_use]
346    pub fn emulator_error(&self) -> Rc<RefCell<Option<NativeEmulatorError>>> {
347        Rc::clone(&self.emulator_error)
348    }
349
350    pub fn update_gui_focused(&self, gui_focused: bool) {
351        self.gui_focused.set(gui_focused);
352    }
353
354    #[must_use]
355    pub fn save_state_metadata(&self) -> Ref<'_, SaveStateMetadata> {
356        self.save_state_metadata.borrow()
357    }
358
359    #[must_use]
360    pub fn exit_signal(&self) -> bool {
361        self.exit_signal.get()
362    }
363
364    pub fn push_command(&self, command: EmuRunnerCommand) {
365        self.queued_commands.borrow_mut().push(command);
366    }
367
368    pub fn clear_waiting_for_first_command(&mut self) {
369        if self.status.get() == EmuRunnerStatus::WaitingForFirstCommand {
370            self.status.set(EmuRunnerStatus::Idle);
371        }
372    }
373}
374
375#[derive(MatchEachVariantMacro)]
376enum GenericEmulator {
377    SmsGg(Box<NativeSmsGgEmulator>),
378    Genesis(Box<NativeGenesisEmulator>),
379    Nes(Box<NativeNesEmulator>),
380    Snes(Box<NativeSnesEmulator>),
381    GameBoy(Box<NativeGameBoyEmulator>),
382    GameBoyAdvance(Box<NativeGbaEmulator>),
383    PcEngine(Box<NativePcEngineEmulator>),
384}
385
386impl GenericEmulator {
387    fn create(
388        sdl: SdlSubsystems,
389        console: Console,
390        config: Box<AppConfig>,
391        cheats: Arc<ActiveCheats>,
392        path: PathBuf,
393        secondary_paths: Vec<PathBuf>,
394    ) -> NativeEmulatorResult<Self> {
395        let emulator = match console {
396            Console::MasterSystem | Console::GameGear | Console::Sg1000 => {
397                Self::SmsGg(Box::new(NativeSmsGgEmulator::create(
398                    sdl,
399                    config.smsgg_config(
400                        path,
401                        console.to_smsgg_hardware(),
402                        cheats.smsgg_or_default(),
403                    ),
404                )?))
405            }
406            Console::Genesis | Console::SegaCd | Console::Sega32X | Console::SegaCd32X => {
407                Self::Genesis(Box::new(NativeGenesisEmulator::create(
408                    sdl,
409                    config.genesis_config(
410                        path,
411                        secondary_paths.first().cloned(),
412                        console.to_genesis_hardware(),
413                        cheats.genesis_or_default(),
414                    ),
415                )?))
416            }
417            Console::Nes => {
418                Self::Nes(Box::new(NativeNesEmulator::create(sdl, config.nes_config(path))?))
419            }
420            Console::Snes => {
421                Self::Snes(Box::new(NativeSnesEmulator::create(sdl, config.snes_config(path))?))
422            }
423            Console::GameBoy | Console::GameBoyColor => {
424                Self::GameBoy(Box::new(NativeGameBoyEmulator::create(sdl, config.gb_config(path))?))
425            }
426            Console::GameBoyAdvance => Self::GameBoyAdvance(Box::new(NativeGbaEmulator::create(
427                sdl,
428                config.gba_config(path),
429            )?)),
430            Console::PcEngine => Self::PcEngine(Box::new(NativePcEngineEmulator::create(
431                sdl,
432                config.pce_config(path),
433            )?)),
434        };
435
436        Ok(emulator)
437    }
438
439    fn create_run_bios(
440        sdl: SdlSubsystems,
441        console: Console,
442        config: Box<AppConfig>,
443    ) -> NativeEmulatorResult<Option<Self>> {
444        let emulator = match console {
445            Console::MasterSystem => {
446                let mut sms_config = config.smsgg_config(
447                    PathBuf::new(),
448                    Some(SmsGgHardware::MasterSystem),
449                    &SmsGgCheats::default(),
450                );
451                sms_config.sms_boot_from_bios = true;
452                sms_config.run_without_cartridge = true;
453
454                Self::SmsGg(Box::new(NativeSmsGgEmulator::create(sdl, sms_config)?))
455            }
456            Console::GameGear => {
457                let mut gg_config = config.smsgg_config(
458                    PathBuf::new(),
459                    Some(SmsGgHardware::GameGear),
460                    &SmsGgCheats::default(),
461                );
462                gg_config.gg_boot_from_bios = true;
463                gg_config.run_without_cartridge = true;
464
465                Self::SmsGg(Box::new(NativeSmsGgEmulator::create(sdl, gg_config)?))
466            }
467            Console::SegaCd | Console::SegaCd32X => {
468                let hardware = match console {
469                    Console::SegaCd32X => GenesisHardware::SegaCd32X,
470                    _ => GenesisHardware::SegaCd,
471                };
472                let mut genesis_config = config.genesis_config(
473                    PathBuf::new(),
474                    None,
475                    Some(hardware),
476                    &GenesisCheats::default(),
477                );
478                genesis_config.scd_run_without_disc = true;
479
480                Self::Genesis(Box::new(NativeGenesisEmulator::create(sdl, genesis_config)?))
481            }
482            _ => return Ok(None),
483        };
484
485        Ok(Some(emulator))
486    }
487
488    fn reload_config(
489        &mut self,
490        config: Box<AppConfig>,
491        cheats: Arc<ActiveCheats>,
492    ) -> NativeEmulatorResult<()> {
493        let path = PathBuf::new();
494
495        match self {
496            Self::SmsGg(emulator) => {
497                emulator.reload_config(config.smsgg_config(path, None, cheats.smsgg_or_default()))
498            }
499            Self::Genesis(emulator) => emulator.reload_config(config.genesis_config(
500                path,
501                None,
502                None,
503                cheats.genesis_or_default(),
504            )),
505            Self::Nes(emulator) => emulator.reload_config(config.nes_config(path)),
506            Self::Snes(emulator) => emulator.reload_config(config.snes_config(path)),
507            Self::GameBoy(emulator) => emulator.reload_config(config.gb_config(path)),
508            Self::GameBoyAdvance(emulator) => emulator.reload_config(config.gba_config(path)),
509
510            Self::PcEngine(emulator) => emulator.reload_config(config.pce_config(path)),
511        }
512    }
513
514    fn remove_disc(&mut self) -> NativeEmulatorResult<()> {
515        match_each_variant!(self, emulator => emulator.remove_disc())
516    }
517
518    fn change_disc(&mut self, path: PathBuf) -> NativeEmulatorResult<()> {
519        match_each_variant!(self, emulator => emulator.change_disc(path))
520    }
521
522    fn run(
523        &mut self,
524        sdl_event_handler: &mut dyn FnMut(&sdl3::event::Event),
525    ) -> NativeEmulatorResult<Option<NativeTickEffect>> {
526        match_each_variant!(self, emulator => emulator.run(sdl_event_handler))
527    }
528
529    fn soft_reset(&mut self) -> NativeEmulatorResult<()> {
530        match_each_variant!(self, emulator => emulator.soft_reset())
531    }
532
533    fn hard_reset(&mut self) -> NativeEmulatorResult<()> {
534        match_each_variant!(self, emulator => emulator.hard_reset())
535    }
536
537    fn open_memory_viewer(&mut self) -> NativeEmulatorResult<()> {
538        match_each_variant!(self, emulator => emulator.open_memory_viewer())
539    }
540
541    fn save_state(&mut self, slot: usize) -> NativeEmulatorResult<()> {
542        match_each_variant!(self, emulator => emulator.save_state(slot))
543    }
544
545    fn load_state(&mut self, slot: usize) -> NativeEmulatorResult<()> {
546        match_each_variant!(self, emulator => emulator.load_state(slot))
547    }
548
549    fn save_state_metadata(&self) -> SaveStateMetadata {
550        match_each_variant!(self, emulator => emulator.save_state_metadata())
551    }
552
553    fn update_gui_focused(&mut self, gui_focused: bool) {
554        match_each_variant!(self, emulator => emulator.update_gui_focused(gui_focused));
555    }
556}