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}