From e456e24f0698b79b5c5fa1c4d43e1020370b98c9 Mon Sep 17 00:00:00 2001 From: Alex Janka <alex@alexjanka.com> Date: Fri, 24 Nov 2023 17:00:06 +1100 Subject: [PATCH] mac frontend can actually run games now --- Cargo.lock | 13 +- gb-emu/Cargo.toml | 1 + gb-emu/src/audio.rs | 5 +- gb-emu/src/bin/cli.rs | 39 ++- gb-emu/src/bin/macos/cacao_window_manager.rs | 310 ++++++++++++++++++- gb-emu/src/bin/macos/mod.rs | 55 +++- gb-emu/src/lib.rs | 40 +-- gb-emu/src/window.rs | 2 - gb-emu/src/window/winit_manager.rs | 20 +- lib/src/connect/mod.rs | 5 +- lib/src/lib.rs | 34 +- 11 files changed, 429 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce66d3c..e499139 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1440,6 +1440,7 @@ dependencies = [ "send_wrapper", "serde", "twinc_emu_vst", + "uuid 1.6.1", "winit", "winit_input_helper", ] @@ -1511,7 +1512,7 @@ dependencies = [ "fnv", "gilrs-core", "log", - "uuid 1.5.0", + "uuid 1.6.1", "vec_map", ] @@ -1528,7 +1529,7 @@ dependencies = [ "libudev-sys", "log", "nix 0.26.4", - "uuid 1.5.0", + "uuid 1.6.1", "vec_map", "wasm-bindgen", "web-sys", @@ -3846,9 +3847,13 @@ dependencies = [ [[package]] name = "uuid" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", + "rand", +] [[package]] name = "vcpkg" diff --git a/gb-emu/Cargo.toml b/gb-emu/Cargo.toml index 486691f..ce5d7ec 100644 --- a/gb-emu/Cargo.toml +++ b/gb-emu/Cargo.toml @@ -42,6 +42,7 @@ image = { version = "0.24", default-features = false, features = ["png"] } bytemuck = "1.14" chrono = "0.4" twinc_emu_vst = { path = "../gb-vst", default-features = false } +uuid = { version = "1.6", features = ["v4", "fast-rng"] } [target.'cfg(any(target_os = "macos"))'.dependencies] cacao = { git = "https://github.com/italicsjenga/cacao" } diff --git a/gb-emu/src/audio.rs b/gb-emu/src/audio.rs index 98b42e5..50b15da 100644 --- a/gb-emu/src/audio.rs +++ b/gb-emu/src/audio.rs @@ -65,9 +65,8 @@ pub fn create_output(muted: bool) -> (AudioOutput, Stream) { &config.config(), move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| { for v in data.chunks_exact_mut(2) { - match executor::block_on(rx.pop()) { - Some(a) => v.copy_from_slice(&a), - None => panic!("Audio queue disconnected!"), + if let Some(a) = executor::block_on(rx.pop()) { + v.copy_from_slice(&a); } } }, diff --git a/gb-emu/src/bin/cli.rs b/gb-emu/src/bin/cli.rs index d2c0834..8b79814 100644 --- a/gb-emu/src/bin/cli.rs +++ b/gb-emu/src/bin/cli.rs @@ -3,12 +3,17 @@ #[cfg(feature = "camera")] use camera::Webcam; use clap::{ArgGroup, Parser, Subcommand, ValueEnum}; -use gb_emu::{audio, window::winit_manager::WinitWindowManager}; +use gb_emu::{audio, debug::Debugger, window::winit_manager::WinitWindowManager}; use gb_emu_lib::{ config::ConfigManager, - connect::{EmulatorMessage, SerialTarget, SramType, StdoutType}, + connect::{EmulatorCoreTrait, EmulatorMessage, SerialTarget, SramType, StdoutType}, +}; +use std::{ + path::PathBuf, + process::exit, + sync::mpsc::channel, + time::{Duration, Instant}, }; -use std::{path::PathBuf, sync::mpsc::channel}; #[cfg(all(feature = "vulkan", feature = "pixels"))] compile_error!("select only one rendering backend!"); @@ -160,7 +165,6 @@ fn main() { } } } else { - gb_emu::init_configs(); let (sender, receiver) = channel::<EmulatorMessage>(); { @@ -170,11 +174,30 @@ fn main() { }) .unwrap(); } - let record = args.record; - let mute = args.mute; + let (record, mute, debug) = (args.record, args.mute, args.debug); let prepared = gb_emu::prepare(args.into(), receiver); let (output, stream) = audio::create_output(mute); - let window_manager = WinitWindowManager::new(sender, stream, record); - gb_emu::run(prepared, window_manager, output); + let mut window_manager = WinitWindowManager::new(sender, stream, record); + let mut core = gb_emu::run(prepared, &mut window_manager, output); + if debug { + let mut debugger = Debugger::new(Box::new(core)); + let mut since = Instant::now(); + loop { + if since.elapsed() >= UPDATE_INTERVAL { + window_manager.update_events(); + since = Instant::now(); + } + debugger.step(); + } + } else { + std::thread::spawn(move || loop { + if core.run(100) { + exit(0); + } + }); + window_manager.run_events_blocking().unwrap(); + } } } + +const UPDATE_INTERVAL: Duration = Duration::from_millis(1); diff --git a/gb-emu/src/bin/macos/cacao_window_manager.rs b/gb-emu/src/bin/macos/cacao_window_manager.rs index 58a5664..482691c 100644 --- a/gb-emu/src/bin/macos/cacao_window_manager.rs +++ b/gb-emu/src/bin/macos/cacao_window_manager.rs @@ -1,6 +1,118 @@ -use gb_emu::window::{RendererChannel, WindowManager}; +use std::{ + collections::HashMap, + path::PathBuf, + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, + }, + thread::JoinHandle, +}; -pub struct CacaoWindowManager {} +use cacao::{ + appkit::window::{Window, WindowConfig, WindowDelegate, WindowStyle}, + core_foundation::base::TCFTypeRef, +}; +use cpal::Stream; +use gb_emu::window::{RendererChannel, WindowManager}; +use gb_emu_lib::{ + connect::{EmulatorMessage, RendererMessage, ResolutionData}, + renderer::{RendererBackend, RendererBackendManager, WindowOptions}, +}; +use raw_window_handle::{ + AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle, + RawDisplayHandle, RawWindowHandle, +}; +use uuid::Uuid; + +use super::{dispatch, AppMessage}; + +pub enum EmuWindowMessage { + Closing, + Renderer(RendererMessage<[u8; 4]>), +} + +pub struct EmulatorHandles { + sender: Sender<EmulatorMessage>, + emulator_thread: Option<JoinHandle<()>>, + _stream: Stream, +} + +impl EmulatorHandles { + fn new(sender: Sender<EmulatorMessage>, stream: Stream) -> Self { + Self { + sender, + emulator_thread: None, + _stream: stream, + } + } + + pub fn set_thread_handle(&mut self, handle: JoinHandle<()>) { + self.emulator_thread = Some(handle); + } +} + +impl Drop for EmulatorHandles { + fn drop(&mut self) { + if let Some(handle) = self.emulator_thread.take() { + self.sender.send(EmulatorMessage::Exit).unwrap(); + handle.join().unwrap(); + } + } +} + +pub struct CacaoWindowManager { + backend_manager: Arc<RendererBackendManager>, + windows: HashMap<Uuid, Window<CacaoWindow>>, + handles: Option<EmulatorHandles>, +} + +impl CacaoWindowManager { + pub fn new(display_handle: RawDisplayHandle) -> Self { + Self { + backend_manager: Arc::new(RendererBackendManager::new(display_handle)), + windows: HashMap::new(), + handles: None, + } + } + + pub fn is_emulator_running(&self) -> bool { + self.handles.is_some() + } + + pub fn update_handles(&mut self, sender: Sender<EmulatorMessage>, stream: Stream) { + self.handles = Some(EmulatorHandles::new(sender, stream)); + } + + pub fn set_thread_handle(&mut self, handle: JoinHandle<()>) { + if let Some(ref mut handles) = self.handles { + handles.set_thread_handle(handle); + } + } + + pub fn message(&mut self, id: Uuid, message: EmuWindowMessage) { + match message { + EmuWindowMessage::Closing => { + self.windows.remove(&id); + if self.windows.is_empty() { + let _ = self.handles.take(); + } + } + EmuWindowMessage::Renderer(message) => { + if let Some(window) = self.windows.get_mut(&id) { + if let Some(delegate) = window.delegate.as_mut() { + delegate.message( + message, + Window { + delegate: None, + objc: window.objc.clone(), + }, + ); + } + } + } + } + } +} impl WindowManager for CacaoWindowManager { fn add_main( @@ -9,7 +121,7 @@ impl WindowManager for CacaoWindowManager { shader_path: Option<std::path::PathBuf>, resizable: bool, ) -> RendererChannel { - todo!() + self.add(factor, shader_path, resizable) } fn add( @@ -18,14 +130,192 @@ impl WindowManager for CacaoWindowManager { shader_path: Option<std::path::PathBuf>, resizable: bool, ) -> RendererChannel { - todo!() - } + let (w, receiver) = CacaoWindow::new(factor, shader_path, self.backend_manager.clone()); + let window = Window::with( + { + let mut config = WindowConfig::default(); + config.set_initial_dimensions(0., 0., 800., 800.); - fn update_events(&mut self) { - todo!() - } + let mut styles = vec![ + WindowStyle::Miniaturizable, + WindowStyle::Closable, + WindowStyle::Titled, + ]; + if resizable { + styles.push(WindowStyle::Resizable); + } - fn run_events_blocking(self) -> Result<(), winit::error::EventLoopError> { - todo!() + config.set_styles(&styles); + config + }, + w, + ); + window.show(); + self.windows + .insert(window.delegate.as_ref().unwrap().id, window); + receiver + } +} + +pub struct MonitorThread { + state: MonitorThreadState, +} + +enum MonitorThreadState { + Live { + thread: JoinHandle<()>, + destroy: Sender<bool>, + }, + Destroyed, +} + +impl MonitorThread { + fn new(receiver: Receiver<RendererMessage<[u8; 4]>>, id: Uuid) -> Self { + let (destroy, destroy_recv) = mpsc::channel(); + let thread = std::thread::spawn(move || loop { + if let Ok(message) = receiver.recv() { + dispatch(AppMessage::EmuWindow { + id, + message: EmuWindowMessage::Renderer(message), + }) + } + if let Ok(true) = destroy_recv.try_recv() { + return; + } + }); + Self { + state: MonitorThreadState::Live { thread, destroy }, + } + } +} + +impl MonitorThreadState { + fn destroy(&mut self) { + if let Self::Live { thread, destroy } = std::mem::replace(self, Self::Destroyed) { + destroy.send(true).expect("Failed to kill monitor thread"); + thread.join().unwrap(); + } + } +} + +impl Drop for MonitorThread { + fn drop(&mut self) { + self.state.destroy(); + } +} + +struct CacaoWindow { + id: Uuid, + backend_manager: Arc<RendererBackendManager>, + backend: Option<RendererBackend>, + scale_factor: usize, + shader_path: Option<PathBuf>, + resolutions: ResolutionData, + _channel_thread: MonitorThread, +} + +impl CacaoWindow { + fn new( + scale_factor: usize, + shader_path: Option<std::path::PathBuf>, + backend_manager: Arc<RendererBackendManager>, + ) -> (Self, RendererChannel) { + let id = Uuid::new_v4(); + let (sender, receiver) = mpsc::channel(); + let channel_thread = MonitorThread::new(receiver, id); + ( + Self { + id, + backend_manager, + backend: None, + scale_factor, + shader_path, + resolutions: ResolutionData { + real_width: 1, + real_height: 1, + scaled_width: 1, + scaled_height: 1, + }, + _channel_thread: channel_thread, + }, + sender, + ) + } + + fn message(&mut self, message: RendererMessage<[u8; 4]>, window: Window) { + match message { + RendererMessage::Prepare { width, height } => self.resize(width, height, window), + RendererMessage::Resize { width, height } => self.resize(width, height, window), + RendererMessage::Display { buffer } => self.display(buffer), + RendererMessage::SetTitle { title } => window.set_title(&title), + RendererMessage::Rumble { rumble: _ } => todo!(), + } + } + + fn resize(&mut self, width: usize, height: usize, window: Window) { + window.set_content_size( + (width * self.scale_factor) as f64, + (height * self.scale_factor) as f64, + ); + let real_factor = (window.backing_scale_factor() * self.scale_factor as f64) as u32; + let (width, height) = (width as u32, height as u32); + self.resolutions = ResolutionData { + real_width: width * real_factor, + real_height: height * real_factor, + scaled_width: width, + scaled_height: height, + } + } + + fn display(&mut self, buffer: Vec<[u8; 4]>) { + if let Some(backend) = self.backend.as_mut() { + backend.render(self.resolutions, &self.backend_manager); + backend.new_frame(&buffer); + } + } +} + +impl WindowDelegate for CacaoWindow { + const NAME: &'static str = "EmulatorWindow"; + + fn did_load(&mut self, window: Window) { + #[cfg(feature = "vulkan")] + let options = WindowOptions { + shader_path: self.shader_path.clone(), + }; + #[cfg(feature = "pixels")] + let options = WindowOptions {}; + + self.backend = Some(RendererBackend::new( + self.resolutions, + &WindowHandleWrapper(&window), + options, + self.backend_manager.clone(), + )); + } + + fn will_close(&self) { + dispatch(AppMessage::EmuWindow { + id: self.id, + message: EmuWindowMessage::Closing, + }) + } +} + +struct WindowHandleWrapper<'a, T>(&'a Window<T>); + +unsafe impl<'a, T> HasRawDisplayHandle for WindowHandleWrapper<'a, T> { + fn raw_display_handle(&self) -> RawDisplayHandle { + RawDisplayHandle::AppKit(AppKitDisplayHandle::empty()) + } +} + +unsafe impl<'a, T> HasRawWindowHandle for WindowHandleWrapper<'a, T> { + fn raw_window_handle(&self) -> RawWindowHandle { + let Self(w) = self; + let mut handle = AppKitWindowHandle::empty(); + handle.ns_window = objc::rc::Id::as_ptr(&w.objc).as_void_ptr().cast_mut(); + + RawWindowHandle::AppKit(handle) } } diff --git a/gb-emu/src/bin/macos/mod.rs b/gb-emu/src/bin/macos/mod.rs index 7b851dc..d9cea9f 100644 --- a/gb-emu/src/bin/macos/mod.rs +++ b/gb-emu/src/bin/macos/mod.rs @@ -8,9 +8,11 @@ use cacao::appkit::{App, AppDelegate}; use cacao::filesystem::FileSelectPanel; use cacao::notification_center::Dispatcher; use gb_emu::audio; -use gb_emu_lib::connect::EmulatorMessage; +use gb_emu_lib::connect::{EmulatorCoreTrait, EmulatorMessage}; +use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle}; +use uuid::Uuid; -use self::cacao_window_manager::CacaoWindowManager; +use self::cacao_window_manager::{CacaoWindowManager, EmuWindowMessage}; use self::preferences::{PreferencesMessage, PreferencesUi}; mod cacao_window_manager; @@ -19,39 +21,38 @@ mod preferences; pub(crate) enum AppMessage { Core(CoreMessage), Preferences(PreferencesMessage), + EmuWindow { id: Uuid, message: EmuWindowMessage }, } pub(crate) enum CoreMessage { - Open, + ShowOpenDialog, OpenPreferences, + OpenRom(PathBuf), } pub(crate) struct TwincUiApp { preferences: RwLock<Window<PreferencesUi>>, + current_game: RwLock<CacaoWindowManager>, } impl TwincUiApp { fn open_dialog(&self) { + if self.current_game.read().unwrap().is_emulator_running() { + return; + } + let mut file_select_panel = FileSelectPanel::new(); file_select_panel.set_can_choose_directories(false); file_select_panel.set_can_choose_files(true); file_select_panel.set_allows_multiple_selection(false); file_select_panel.show(move |v| { if let Some(path) = v.first() { - open_file(path.pathbuf()); + dispatch(AppMessage::Core(CoreMessage::OpenRom(path.pathbuf()))); } }); } } -fn open_file(path: PathBuf) { - let (sender, receiver) = channel::<EmulatorMessage>(); - let prepared = gb_emu::prepare(gb_emu::RunOptions::new(path), receiver); - let (output, stream) = audio::create_output(false); - let window_manager = CacaoWindowManager {}; - gb_emu::run(prepared, window_manager, output); -} - impl Default for TwincUiApp { fn default() -> Self { Self { @@ -72,6 +73,9 @@ impl Default for TwincUiApp { }, PreferencesUi::new(), )), + current_game: RwLock::new(CacaoWindowManager::new(RawDisplayHandle::AppKit( + AppKitDisplayHandle::empty(), + ))), } } } @@ -88,10 +92,28 @@ impl Dispatcher for TwincUiApp { fn on_ui_message(&self, message: Self::Message) { match message { - AppMessage::Core(CoreMessage::Open) => self.open_dialog(), + AppMessage::Core(CoreMessage::ShowOpenDialog) => self.open_dialog(), AppMessage::Core(CoreMessage::OpenPreferences) => { self.preferences.read().unwrap().show(); } + AppMessage::Core(CoreMessage::OpenRom(path)) => { + let (sender, receiver) = channel::<EmulatorMessage>(); + sender.send(EmulatorMessage::Start).unwrap(); + let prepared = gb_emu::prepare(gb_emu::RunOptions::new(path), receiver); + let (output, stream) = audio::create_output(false); + let mut window_manager = self.current_game.write().unwrap(); + window_manager.update_handles(sender, stream); + let mut core = gb_emu::run(prepared, &mut *window_manager, output); + let handle = std::thread::Builder::new() + .name(String::from("EmuCore")) + .spawn(move || loop { + if core.run(100) { + break; + } + }) + .unwrap(); + window_manager.set_thread_handle(handle); + } AppMessage::Preferences(prefs_message) => { if let Ok(mut prefs) = self.preferences.write() { if let Some(ref mut delegate) = prefs.delegate { @@ -99,6 +121,11 @@ impl Dispatcher for TwincUiApp { } } } + AppMessage::EmuWindow { id, message } => { + if let Ok(mut window_manager) = self.current_game.write() { + window_manager.message(id, message); + } + } } } } @@ -127,7 +154,7 @@ fn menu() -> Vec<Menu> { "File", vec![MenuItem::new("Open") .key("o") - .action(|| dispatch(AppMessage::Core(CoreMessage::Open)))], + .action(|| dispatch(AppMessage::Core(CoreMessage::ShowOpenDialog)))], ), Menu::new( "Window", diff --git a/gb-emu/src/lib.rs b/gb-emu/src/lib.rs index 45c7e33..f04e592 100644 --- a/gb-emu/src/lib.rs +++ b/gb-emu/src/lib.rs @@ -2,12 +2,12 @@ #[cfg(feature = "camera")] use camera::Webcam; -use debug::Debugger; + use gb_emu_lib::{ config::{ConfigManager, NamedConfig}, connect::{ - AudioOutput, CameraWrapper, CgbRomType, EmulatorCoreTrait, EmulatorMessage, - EmulatorOptions, NoCamera, Rom, RomFile, SerialTarget, SramType, + AudioOutput, CameraWrapper, CgbRomType, EmulatorMessage, EmulatorOptions, NoCamera, Rom, + RomFile, SerialTarget, SramType, }, EmulatorCore, }; @@ -15,7 +15,6 @@ use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, sync::{mpsc::Receiver, Arc, Mutex, OnceLock}, - time::{Duration, Instant}, }; use window::WindowManager; @@ -109,11 +108,8 @@ where serial: SerialTarget, tile_window: bool, layer_window: bool, - debug: bool, } -pub fn init_configs() {} - pub fn prepare( options: RunOptions, receiver: Receiver<EmulatorMessage>, @@ -166,17 +162,19 @@ pub fn prepare( shader_path, resizable, rom, - receiver, camera, serial: options.serial, tile_window: options.tile_window, layer_window: options.layer_window, - debug: options.debug, } } -pub fn run<W>(prepared: PreparedEmulator<NoCamera>, mut window_manager: W, output: AudioOutput) +pub fn run<W>( + prepared: PreparedEmulator<NoCamera>, + window_manager: &mut W, + output: AudioOutput, +) -> EmulatorCore<[u8; 4], NoCamera> where W: WindowManager, { @@ -229,25 +227,5 @@ where // EmulatorTypes::Normal(core) // }; - let mut core = EmulatorCore::init(true, prepared.receiver, emulator_options, prepared.camera); - - if prepared.debug { - let mut debugger = Debugger::new(Box::new(core)); - let mut since = Instant::now(); - loop { - if since.elapsed() >= UPDATE_INTERVAL { - window_manager.update_events(); - since = Instant::now(); - } - debugger.step(); - } - } else { - std::thread::spawn(move || loop { - core.run(100); - }); - - window_manager.run_events_blocking().unwrap(); - } + EmulatorCore::init(true, prepared.receiver, emulator_options, prepared.camera) } - -const UPDATE_INTERVAL: Duration = Duration::from_millis(1); diff --git a/gb-emu/src/window.rs b/gb-emu/src/window.rs index f4cd22a..53458d6 100644 --- a/gb-emu/src/window.rs +++ b/gb-emu/src/window.rs @@ -19,6 +19,4 @@ pub trait WindowManager { shader_path: Option<PathBuf>, resizable: bool, ) -> RendererChannel; - fn update_events(&mut self); - fn run_events_blocking(self) -> Result<(), winit::error::EventLoopError>; } diff --git a/gb-emu/src/window/winit_manager.rs b/gb-emu/src/window/winit_manager.rs index 57a70df..f18179d 100644 --- a/gb-emu/src/window/winit_manager.rs +++ b/gb-emu/src/window/winit_manager.rs @@ -70,6 +70,16 @@ impl WinitWindowManager { record_main, } } + + pub fn update_events(&mut self) { + self.event_loop + .pump_events(None, |event, target| self.data.handler(true, event, target)); + } + + pub fn run_events_blocking(mut self) -> Result<(), winit::error::EventLoopError> { + self.event_loop + .run(move |event, target| self.data.handler(false, event, target)) + } } impl WindowManager for WinitWindowManager { @@ -104,16 +114,6 @@ impl WindowManager for WinitWindowManager { false, ) } - - fn update_events(&mut self) { - self.event_loop - .pump_events(None, |event, target| self.data.handler(true, event, target)); - } - - fn run_events_blocking(mut self) -> Result<(), winit::error::EventLoopError> { - self.event_loop - .run(move |event, target| self.data.handler(false, event, target)) - } } impl WinitWindowManagerData { diff --git a/lib/src/connect/mod.rs b/lib/src/connect/mod.rs index 428ebdb..4c5ae54 100644 --- a/lib/src/connect/mod.rs +++ b/lib/src/connect/mod.rs @@ -76,6 +76,7 @@ impl RomFile { } } +#[derive(Debug)] pub enum RendererMessage<Format: From<Colour>> { Prepare { width: usize, height: usize }, Resize { width: usize, height: usize }, @@ -354,7 +355,7 @@ pub trait EmulatorCoreTrait { fn pc(&self) -> u16; fn print_reg(&self) -> String; fn get_memory(&self, address: u16) -> u8; - fn run(&mut self, cycles: usize); + fn run(&mut self, cycles: usize) -> bool; fn run_until_buffer_full(&mut self); - fn process_messages(&mut self); + fn process_messages(&mut self) -> bool; } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fd1a082..3569471 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -11,7 +11,6 @@ use processor::{ }; use std::{ marker::PhantomData, - process::exit, sync::{mpsc::Receiver, Arc, Mutex}, }; @@ -151,23 +150,30 @@ where self.cpu.exec_next(); } - fn process_messages(&mut self) { + fn process_messages(&mut self) -> bool { while let Ok(msg) = self.receiver.try_recv() { - self.process_message(msg); + if self.process_message(msg) { + return true; + } } while self.paused { match self.receiver.recv() { - Ok(msg) => self.process_message(msg), + Ok(msg) => { + if self.process_message(msg) { + return true; + } + } Err(e) => panic!("no message sender! error {e:#?}"), } } + false } - fn process_message(&mut self, msg: EmulatorMessage) { + fn process_message(&mut self, msg: EmulatorMessage) -> bool { match msg { EmulatorMessage::Exit => { self.cpu.memory.flush_rom(); - exit(0); + return true; } EmulatorMessage::Start => self.paused = false, EmulatorMessage::Pause => self.paused = true, @@ -175,6 +181,7 @@ where self.cpu.next_joypad_state = Some(new_state) } } + false } } @@ -214,23 +221,28 @@ where self.cpu.memory.get(address) } - fn run(&mut self, cycles: usize) { - self.process_messages(); + fn run(&mut self, cycles: usize) -> bool { + if self.process_messages() { + return true; + } if !self.paused { for _ in 0..cycles { self.run_cycle(); } } + false } fn run_until_buffer_full(&mut self) { - self.process_messages(); + if self.process_messages() { + return; + } while !self.cpu.memory.is_audio_buffer_full() { self.run_cycle(); } } - fn process_messages(&mut self) { - self.process_messages(); + fn process_messages(&mut self) -> bool { + self.process_messages() } }