mac frontend can actually run games now
This commit is contained in:
parent
0610b8166d
commit
e456e24f06
11 changed files with 429 additions and 95 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue