diff --git a/Cargo.toml b/Cargo.toml index 397cb0e3..d3e24bbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ sha2 = "0.10" sdl3-sys = { version = "0.6", features = ["build-from-source-static"] } sdl3-ttf-sys = { version = "0.6", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] } rfd = "0.17" -tokio = {version = "1.46", features = ["rt-multi-thread", "macros"] } +tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs"] } spin_sleep = "1.3" encoding_rs = "0.8" mimalloc = "0.1" diff --git a/src/device.rs b/src/device.rs index b4281262..293a4feb 100644 --- a/src/device.rs +++ b/src/device.rs @@ -208,7 +208,6 @@ pub struct Device { #[serde(skip, default = "set_rng")] pub rng: rand_chacha::ChaCha8Rng, pub vru: controller::vru::Vru, - pub vru_window: controller::vru::VruWindow, pub transferpaks: [controller::transferpak::TransferPak; 4], pub cheats: cheats::Cheats, } @@ -482,10 +481,6 @@ impl Device { limit_freq: 2, limit_freq_check: std::time::Instant::now(), }, - vru_window: controller::vru::VruWindow { - window_notifier: None, - word_receiver: None, - }, vru: controller::vru::Vru { status: 0, voice_state: 0, diff --git a/src/device/cart.rs b/src/device/cart.rs index ba3d23de..b6bb0f20 100644 --- a/src/device/cart.rs +++ b/src/device/cart.rs @@ -147,7 +147,7 @@ fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, device.pif.ram[status] = 0x00; - device.ui.storage.saves.eeprom.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k); } fn time2data(device: &mut device::Device, offset: usize) { diff --git a/src/device/cart/rom.rs b/src/device/cart/rom.rs index 88b5456a..0893ad34 100644 --- a/src/device/cart/rom.rs +++ b/src/device/cart/rom.rs @@ -1,5 +1,6 @@ use crate::device; use crate::netplay; +use crate::ui; use sha2::{Digest, Sha256}; pub const CART_MASK: usize = 0xFFFFFFF; @@ -63,7 +64,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3 .data .insert((masked_address + i) as u32, *item); } - device.ui.storage.saves.romsave.written = true; + ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave); } device.cart.latch = value & mask; @@ -94,7 +95,7 @@ pub fn dma_read( .unwrap_or(&0), ); } - device.ui.storage.saves.romsave.written = true; + ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave); } device::pi::calculate_cycles(device, 1, length) diff --git a/src/device/cart/sc64.rs b/src/device/cart/sc64.rs index ff1a3d38..e19f2c00 100644 --- a/src/device/cart/sc64.rs +++ b/src/device/cart/sc64.rs @@ -208,7 +208,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u .copy_from_slice(&data); i += 4; } - device.ui.storage.saves.sdcard.written = true; + ui::storage::schedule_save(device, ui::storage::SaveTypes::Sdcard); } 'U' => { device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; @@ -405,7 +405,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3 device::memory::masked_write_32(&mut data, value, mask); device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4] .copy_from_slice(&data.to_be_bytes()); - device.ui.storage.saves.eeprom.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k); } else { let masked_address = address as usize & SC64_BUFFER_MASK; let mut data = u32::from_be_bytes( @@ -429,7 +429,7 @@ pub fn dma_read( let buffer = if cart_addr & 0x2000 != 0 { device::cart::format_eeprom(device); cart_addr &= SC64_EEPROM_MASK as u32; - device.ui.storage.saves.eeprom.written = true; + ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k); &mut device.ui.storage.saves.eeprom.data } else { cart_addr &= SC64_BUFFER_MASK as u32; diff --git a/src/device/cart/sram.rs b/src/device/cart/sram.rs index 11ff7e07..ef17f8c7 100644 --- a/src/device/cart/sram.rs +++ b/src/device/cart/sram.rs @@ -107,7 +107,7 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u device.ui.storage.saves.sram.data[masked_address..masked_address + 4] .copy_from_slice(&data.to_be_bytes()); - device.ui.storage.saves.sram.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram); } fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) { @@ -167,7 +167,7 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: j += 1; } - device.ui.storage.saves.sram.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram); } fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) { @@ -331,12 +331,12 @@ fn flashram_command(device: &mut device::Device, command: u32) { for i in 0..128 * 128 { device.ui.storage.saves.flash.data[offset + i] = 0xFF; } - device.ui.storage.saves.flash.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash); } else if device.cart.flashram.mode == FlashramMode::ChipErase { for i in 0..FLASHRAM_SIZE { device.ui.storage.saves.flash.data[i] = 0xFF; } - device.ui.storage.saves.flash.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash); } else { panic!("Unexpected flash erase command") } @@ -356,7 +356,7 @@ fn flashram_command(device: &mut device::Device, command: u32) { for i in 0..128 { device.ui.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i]; } - device.ui.storage.saves.flash.written = true; + ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash); /* clear program busy flag, set program success flag, transition to status mode */ device.cart.flashram.status &= !0x01; diff --git a/src/device/controller/mempak.rs b/src/device/controller/mempak.rs index 54ffa7d9..55eab6da 100644 --- a/src/device/controller/mempak.rs +++ b/src/device/controller/mempak.rs @@ -1,4 +1,5 @@ use crate::device; +use crate::ui; pub const MEMPAK_SIZE: usize = 0x8000; const MPK_PAGE_SIZE: usize = 256; @@ -96,6 +97,6 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us device.ui.storage.saves.mempak.data[offset..offset + size] .copy_from_slice(&device.pif.ram[data..data + size]); - device.ui.storage.saves.mempak.written = true + ui::storage::schedule_save(device, ui::storage::SaveTypes::Mempak); } } diff --git a/src/device/controller/vru.rs b/src/device/controller/vru.rs index 468dc6c8..99058efc 100644 --- a/src/device/controller/vru.rs +++ b/src/device/controller/vru.rs @@ -29,14 +29,6 @@ pub struct Vru { pub word_mappings: HashMap, } -#[derive(serde::Serialize, serde::Deserialize)] -pub struct VruWindow { - #[serde(skip)] - pub window_notifier: Option>>>, - #[serde(skip)] - pub word_receiver: Option>, -} - pub fn init(device: &mut device::Device) { reset_vru(device); create_word_mappings(device); @@ -187,13 +179,8 @@ pub fn process(device: &mut device::Device, channel: usize) { device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0; } JCMD_VRU_READ => { - let index = if let Some(window_notifier) = &device.vru_window.window_notifier { - ui::vru::prompt_for_match( - &device.vru.words, - window_notifier, - device.vru_window.word_receiver.as_mut().unwrap(), - device.vi.frame_time, - ) + let index = if let Some(weak) = &device.ui.weak { + ui::vru::prompt_for_match(&device.vru.words, weak, device.vi.frame_time) } else { 0x7FFF }; diff --git a/src/device/events.rs b/src/device/events.rs index 8d85e157..6dbb9dd4 100644 --- a/src/device/events.rs +++ b/src/device/events.rs @@ -1,4 +1,5 @@ use crate::device; +use crate::ui; pub const EVENT_TYPE_NONE: usize = 0; pub const EVENT_TYPE_AI: usize = 1; @@ -12,7 +13,8 @@ pub const EVENT_TYPE_SPDMA: usize = 8; pub const EVENT_TYPE_COMPARE: usize = 9; pub const EVENT_TYPE_VRU: usize = 10; pub const EVENT_TYPE_PAK: usize = 11; -pub const EVENT_TYPE_COUNT: usize = 12; +pub const EVENT_TYPE_SAVE: usize = 12; +pub const EVENT_TYPE_COUNT: usize = 13; #[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct Event { @@ -60,6 +62,7 @@ fn get_event_handler(name: usize) -> fn(&mut device::Device) { EVENT_TYPE_COMPARE => device::cop0::compare_event, EVENT_TYPE_VRU => device::controller::vru::vru_talking_event, EVENT_TYPE_PAK => device::controller::pak_switch_event, + EVENT_TYPE_SAVE => ui::storage::save_event, _ => dummy_event, } } diff --git a/src/main.rs b/src/main.rs index 874248f1..4ae3c1e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,8 +74,15 @@ struct Args { clear_input_bindings: bool, } -#[tokio::main] -async fn main() -> std::io::Result<()> { +fn main() -> std::io::Result<()> { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(4) + .thread_name("n64") + .thread_stack_size(env!("N64_STACK_SIZE").parse().unwrap()) + .build() + .unwrap(); + let dirs = ui::get_dirs(); std::fs::create_dir_all(dirs.config_dir)?; @@ -90,32 +97,29 @@ async fn main() -> std::io::Result<()> { file_path.display() ))); }; - let handle = std::thread::Builder::new() - .name("n64".to_string()) - .stack_size(env!("N64_STACK_SIZE").parse().unwrap()) - .spawn(move || { - let mut device = device::Device::new(); - let overclock = device.ui.config.emulation.overclock; - let disable_expansion_pak = device.ui.config.emulation.disable_expansion_pak; - let game_cheats = { - let game_crc = ui::storage::get_game_crc(&rom_contents); - let cheats = ui::config::Cheats::new(); - cheats.cheats.get(&game_crc).cloned().unwrap_or_default() - }; - device::run_game( - &mut device, - rom_contents, - ui::gui::GameSettings { - fullscreen: args.fullscreen, - overclock, - disable_expansion_pak, - cheats: game_cheats, - }, - ); - })?; + let handle = runtime.spawn(async move { + let mut device = device::Device::new(); + let overclock = device.ui.config.emulation.overclock; + let disable_expansion_pak = device.ui.config.emulation.disable_expansion_pak; - handle.join().unwrap(); + let game_cheats = { + let game_crc = ui::storage::get_game_crc(&rom_contents); + let cheats = ui::config::Cheats::new(); + cheats.cheats.get(&game_crc).cloned().unwrap_or_default() + }; + device::run_game( + &mut device, + rom_contents, + ui::gui::GameSettings { + fullscreen: args.fullscreen, + overclock, + disable_expansion_pak, + cheats: game_cheats, + }, + ); + }); + runtime.block_on(handle).unwrap() } else if std::env::args().count() > 1 { let mut ui = ui::Ui::new(); @@ -157,7 +161,10 @@ async fn main() -> std::io::Result<()> { ui::input::bind_input_profile(&mut ui, profile, port); } } else { - gui::app_window(); + runtime.block_on(async { + gui::app_window(); + }); } + runtime.shutdown_timeout(std::time::Duration::from_secs(1)); Ok(()) } diff --git a/src/ui.rs b/src/ui.rs index da95e60e..57721c8d 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -53,6 +53,7 @@ pub struct Ui { pub storage: Storage, pub video: Video, pub usb: Usb, + pub weak: Option>, } impl Drop for Ui { @@ -156,27 +157,27 @@ impl Ui { write_to_disk: true, eeprom: storage::Save { data: Vec::new(), - written: false, + write_pending: false, }, sram: storage::Save { data: Vec::new(), - written: false, + write_pending: false, }, flash: storage::Save { data: Vec::new(), - written: false, + write_pending: false, }, mempak: storage::Save { data: Vec::new(), - written: false, + write_pending: false, }, sdcard: storage::Save { data: Vec::new(), - written: false, + write_pending: false, }, romsave: storage::RomSave { data: std::collections::HashMap::new(), - written: false, + write_pending: false, }, }, }, @@ -195,6 +196,7 @@ impl Ui { usb_tx: None, cart_rx: None, }, + weak: None, dirs, with_sdl, } diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 2cbf4911..e7de83e4 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -24,11 +24,6 @@ pub struct GbPaths { pub ram: [Option; 4], } -pub struct VruChannel { - pub vru_window_notifier: Option>>>, - pub vru_word_receiver: Option>, -} - #[derive(Clone)] pub struct GameSettings { pub fullscreen: bool, @@ -337,120 +332,66 @@ pub fn app_window() { save_settings(&app, &controller_paths); } -fn setup_vru_word_watcher( - weak_vru: slint::Weak, - vru_word_notifier: tokio::sync::mpsc::Sender, - mut vru_window_receiver: tokio::sync::mpsc::Receiver>>, -) { - tokio::spawn(async move { - loop { - let notifier = vru_word_notifier.clone(); - let notifier_closed = vru_word_notifier.clone(); - let result = vru_window_receiver.recv().await; - if let Some(Some(words)) = result { - weak_vru - .upgrade_in_event_loop(move |_handle| { - let vru_dialog = VruDialog::new().unwrap(); - let vru_dialog_weak = vru_dialog.as_weak(); - - vru_dialog.on_vru_button_clicked(move |chosen_word| { - notifier.try_send(chosen_word.to_string()).unwrap(); - vru_dialog_weak.unwrap().window().hide().unwrap(); - }); - - vru_dialog.window().on_close_requested(move || { - notifier_closed.try_send("".to_string()).unwrap(); - slint::CloseRequestResponse::HideWindow - }); - - let words_vec = slint::VecModel::default(); - for word in words { - words_vec.push(word.into()); - } - let words_model: std::rc::Rc> = - std::rc::Rc::new(words_vec); - vru_dialog.set_words(slint::ModelRc::from(words_model)); - - vru_dialog.show().unwrap(); - }) - .unwrap(); - } else { - return; - } - } - }); -} - pub fn run_rom( gb_paths: GbPaths, file_path: std::path::PathBuf, mut game_settings: GameSettings, - vru_channel: VruChannel, netplay: Option, usb: ui::Usb, weak: slint::Weak, ) { - std::thread::Builder::new() - .name("n64".to_string()) - .stack_size(env!("N64_STACK_SIZE").parse().unwrap()) - .spawn(move || { - weak.upgrade_in_event_loop(move |handle| handle.set_game_running(true)) - .unwrap(); - - let mut device = device::Device::new(); - device.ui.config.rom_dir = file_path.parent().unwrap().to_path_buf(); - - for i in 0..4 { - if gb_paths.rom[i].is_some() && gb_paths.ram[i].is_some() { - device.transferpaks[i].cart.rom = - std::fs::read(gb_paths.rom[i].as_ref().unwrap()).unwrap(); - - device.transferpaks[i].cart.ram = - std::fs::read(gb_paths.ram[i].as_ref().unwrap()).unwrap(); - } - } - - device.ui.usb.usb_tx = usb.usb_tx; - device.ui.usb.cart_rx = usb.cart_rx; - - device.vru_window.window_notifier = vru_channel.vru_window_notifier; - device.vru_window.word_receiver = vru_channel.vru_word_receiver; - - if let Some(rom_contents) = device::get_rom_contents(file_path.as_path()) { - if let Some(netplay_device) = netplay { - device.netplay = Some(netplay::init( - netplay_device.peer_addr, - netplay_device.player_number, - )); - } else { - game_settings.cheats = { - let game_crc = ui::storage::get_game_crc(&rom_contents); - let cheats = ui::config::Cheats::new(); - cheats.cheats.get(&game_crc).cloned().unwrap_or_default() - }; - } - device::run_game(&mut device, rom_contents, game_settings); - if device.netplay.is_some() { - netplay::close(&mut device); - } - } else { - println!("Could not read rom file"); - } - - if let Some(vru_window_notifier) = device.vru_window.window_notifier { - vru_window_notifier.try_send(None).unwrap(); - } - - let rom_dir = device.ui.config.rom_dir.clone(); - weak.upgrade_in_event_loop(move |handle| { - if let Some(rom_dir_str) = rom_dir.to_str() { - handle.set_rom_dir(rom_dir_str.into()); - } - handle.set_game_running(false); - }) + tokio::spawn(async move { + weak.upgrade_in_event_loop(move |handle| handle.set_game_running(true)) .unwrap(); + + let mut device = device::Device::new(); + device.ui.config.rom_dir = file_path.parent().unwrap().to_path_buf(); + + for i in 0..4 { + if gb_paths.rom[i].is_some() && gb_paths.ram[i].is_some() { + device.transferpaks[i].cart.rom = + std::fs::read(gb_paths.rom[i].as_ref().unwrap()).unwrap(); + + device.transferpaks[i].cart.ram = + std::fs::read(gb_paths.ram[i].as_ref().unwrap()).unwrap(); + } + } + + device.ui.usb.usb_tx = usb.usb_tx; + device.ui.usb.cart_rx = usb.cart_rx; + + device.ui.weak = Some(weak.clone()); + + if let Some(rom_contents) = device::get_rom_contents(file_path.as_path()) { + if let Some(netplay_device) = netplay { + device.netplay = Some(netplay::init( + netplay_device.peer_addr, + netplay_device.player_number, + )); + } else { + game_settings.cheats = { + let game_crc = ui::storage::get_game_crc(&rom_contents); + let cheats = ui::config::Cheats::new(); + cheats.cheats.get(&game_crc).cloned().unwrap_or_default() + }; + } + device::run_game(&mut device, rom_contents, game_settings); + if device.netplay.is_some() { + netplay::close(&mut device); + } + } else { + println!("Could not read rom file"); + } + + let rom_dir = device.ui.config.rom_dir.clone(); + weak.upgrade_in_event_loop(move |handle| { + if let Some(rom_dir_str) = rom_dir.to_str() { + handle.set_rom_dir(rom_dir_str.into()); + } + handle.set_game_running(false); }) .unwrap(); + }); } fn open_rom(app: &AppWindow, usb: ui::Usb) { @@ -486,25 +427,10 @@ fn open_rom(app: &AppWindow, usb: ui::Usb) { } } - #[allow(clippy::type_complexity)] - let (vru_window_notifier, vru_window_receiver): ( - tokio::sync::mpsc::Sender>>, - tokio::sync::mpsc::Receiver>>, - ) = tokio::sync::mpsc::channel(5); - - let (vru_word_notifier, vru_word_receiver): ( - tokio::sync::mpsc::Sender, - tokio::sync::mpsc::Receiver, - ) = tokio::sync::mpsc::channel(5); - let fullscreen = app.get_fullscreen(); let overclock = app.get_overclock_n64_cpu(); - let emulate_vru = app.get_emulate_vru(); let disable_expansion_pak = app.get_disable_expansion_pak(); - if emulate_vru { - setup_vru_word_watcher(app.as_weak(), vru_word_notifier, vru_window_receiver); - } let weak = app.as_weak(); tokio::spawn(async move { if let Some(file) = select_rom.await { @@ -533,17 +459,6 @@ fn open_rom(app: &AppWindow, usb: ui::Usb) { disable_expansion_pak, cheats: std::collections::HashMap::new(), // will be filled in later }, - if emulate_vru { - VruChannel { - vru_window_notifier: Some(vru_window_notifier), - vru_word_receiver: Some(vru_word_receiver), - } - } else { - VruChannel { - vru_window_notifier: None, - vru_word_receiver: None, - } - }, None, usb, weak, diff --git a/src/ui/netplay.rs b/src/ui/netplay.rs index b43150e8..f839e457 100644 --- a/src/ui/netplay.rs +++ b/src/ui/netplay.rs @@ -2,7 +2,7 @@ use crate::device; use crate::ui; use crate::ui::gui::{ AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, GameSettings, GbPaths, - NetplayCreate, NetplayDevice, NetplayJoin, NetplayWait, VruChannel, run_rom, save_settings, + NetplayCreate, NetplayDevice, NetplayJoin, NetplayWait, run_rom, save_settings, }; use futures::{SinkExt, StreamExt}; use sha2::{Digest, Sha256}; @@ -1084,10 +1084,6 @@ fn setup_wait_window( disable_expansion_pak: game_settings.disable_expansion_pak, cheats: game_settings.cheats, }, - VruChannel { - vru_window_notifier: None, - vru_word_receiver: None, - }, Some(NetplayDevice { peer_addr: socket_addr, player_number: player_number as u8, diff --git a/src/ui/storage.rs b/src/ui/storage.rs index 5b0b06b5..dff4a096 100644 --- a/src/ui/storage.rs +++ b/src/ui/storage.rs @@ -28,13 +28,13 @@ pub struct Paths { #[derive(serde::Serialize, serde::Deserialize)] pub struct Save { pub data: Vec, - pub written: bool, + pub write_pending: bool, } #[derive(serde::Serialize, serde::Deserialize)] pub struct RomSave { pub data: std::collections::HashMap, - pub written: bool, + pub write_pending: bool, } #[derive(serde::Serialize, serde::Deserialize)] @@ -357,7 +357,7 @@ pub fn compress_file(data: &[(&[u8], &str)]) -> Vec { fn writeback_sdcard(device: &mut device::Device) { let length; let save_data: &[u8]; - if device.ui.storage.saves.eeprom.written { + if device.ui.storage.saves.eeprom.write_pending { if device .ui .storage @@ -369,12 +369,15 @@ fn writeback_sdcard(device: &mut device::Device) { length = 4; } save_data = device.ui.storage.saves.eeprom.data.as_ref(); - } else if device.ui.storage.saves.sram.written { + device.ui.storage.saves.eeprom.write_pending = false; + } else if device.ui.storage.saves.sram.write_pending { length = device.ui.storage.saves.sram.data.len() / 512; save_data = device.ui.storage.saves.sram.data.as_ref(); - } else if device.ui.storage.saves.flash.written { + device.ui.storage.saves.sram.write_pending = false; + } else if device.ui.storage.saves.flash.write_pending { length = device.ui.storage.saves.flash.data.len() / 512; save_data = device.ui.storage.saves.flash.data.as_ref(); + device.ui.storage.saves.flash.write_pending = false; } else { return; } @@ -384,37 +387,69 @@ fn writeback_sdcard(device: &mut device::Device) { device.ui.storage.saves.sdcard.data[offset..offset + 512] .copy_from_slice(&save_data[i * 512..(i + 1) * 512]); } - device.ui.storage.saves.sdcard.written = true; + device.ui.storage.saves.sdcard.write_pending = true; } pub fn write_saves(device: &mut device::Device) { if device.netplay.is_none() || device.netplay.as_ref().unwrap().player_number == 0 { if device.ui.storage.saves.write_to_disk { - if device.ui.storage.saves.eeprom.written { - write_save(&device.ui, SaveTypes::Eeprom16k) + if device.ui.storage.saves.eeprom.write_pending { + write_save(&mut device.ui, SaveTypes::Eeprom16k) } - if device.ui.storage.saves.sram.written { - write_save(&device.ui, SaveTypes::Sram) + if device.ui.storage.saves.sram.write_pending { + write_save(&mut device.ui, SaveTypes::Sram) } - if device.ui.storage.saves.flash.written { - write_save(&device.ui, SaveTypes::Flash) + if device.ui.storage.saves.flash.write_pending { + write_save(&mut device.ui, SaveTypes::Flash) } - if device.ui.storage.saves.romsave.written { - write_save(&device.ui, SaveTypes::Romsave) + if device.ui.storage.saves.romsave.write_pending { + write_save(&mut device.ui, SaveTypes::Romsave) } } else { writeback_sdcard(device) } - if device.ui.storage.saves.mempak.written { - write_save(&device.ui, SaveTypes::Mempak) + if device.ui.storage.saves.mempak.write_pending { + write_save(&mut device.ui, SaveTypes::Mempak) } - if device.ui.storage.saves.sdcard.written { - write_save(&device.ui, SaveTypes::Sdcard) + if device.ui.storage.saves.sdcard.write_pending { + write_save(&mut device.ui, SaveTypes::Sdcard) } } } -fn write_save(ui: &ui::Ui, save_type: SaveTypes) { +pub fn save_event(device: &mut device::Device) { + write_saves(device); +} + +pub fn schedule_save(device: &mut device::Device, save_type: SaveTypes) { + match save_type { + SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => { + device.ui.storage.saves.eeprom.write_pending = true; + } + SaveTypes::Sram => { + device.ui.storage.saves.sram.write_pending = true; + } + SaveTypes::Flash => { + device.ui.storage.saves.flash.write_pending = true; + } + SaveTypes::Mempak => { + device.ui.storage.saves.mempak.write_pending = true; + } + SaveTypes::Sdcard => { + device.ui.storage.saves.sdcard.write_pending = true; + } + SaveTypes::Romsave => { + device.ui.storage.saves.romsave.write_pending = true; + } + } + device::events::create_event( + device, + device::events::EVENT_TYPE_SAVE, + device.cpu.clock_rate, // 1 second + ); +} + +fn write_save(ui: &mut ui::Ui, save_type: SaveTypes) { let path: &std::path::Path; let data: &Vec; let rom_save_data: Vec; @@ -422,35 +457,38 @@ fn write_save(ui: &ui::Ui, save_type: SaveTypes) { SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => { path = ui.storage.paths.eep_file_path.as_ref(); data = ui.storage.saves.eeprom.data.as_ref(); + ui.storage.saves.eeprom.write_pending = false; } SaveTypes::Sram => { path = ui.storage.paths.sra_file_path.as_ref(); data = ui.storage.saves.sram.data.as_ref(); + ui.storage.saves.sram.write_pending = false; } SaveTypes::Flash => { path = ui.storage.paths.fla_file_path.as_ref(); data = ui.storage.saves.flash.data.as_ref(); + ui.storage.saves.flash.write_pending = false; } SaveTypes::Mempak => { path = ui.storage.paths.pak_file_path.as_ref(); data = ui.storage.saves.mempak.data.as_ref(); + ui.storage.saves.mempak.write_pending = false; } SaveTypes::Sdcard => { path = ui.storage.paths.sdcard_file_path.as_ref(); data = ui.storage.saves.sdcard.data.as_ref(); + ui.storage.saves.sdcard.write_pending = false; } SaveTypes::Romsave => { path = ui.storage.paths.romsave_file_path.as_ref(); rom_save_data = postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap(); data = rom_save_data.as_ref(); + ui.storage.saves.romsave.write_pending = false; } } - let result = std::fs::write(path, data); - if result.is_err() { - panic!( - "could not save {} {}", - path.display(), - result.err().unwrap() - ) - } + let save_data = data.clone(); + let save_path = path.to_path_buf(); + tokio::spawn(async move { + tokio::fs::write(save_path, save_data).await.unwrap(); + }); } diff --git a/src/ui/usb.rs b/src/ui/usb.rs index afd675b8..d5c7db16 100644 --- a/src/ui/usb.rs +++ b/src/ui/usb.rs @@ -53,10 +53,6 @@ fn upload_rom( disable_expansion_pak: handle.get_disable_expansion_pak(), cheats: std::collections::HashMap::new(), // will be filled in later }, - ui::gui::VruChannel { - vru_window_notifier: None, - vru_word_receiver: None, - }, None, ui::Usb { usb_tx: Some(usb_tx), diff --git a/src/ui/vru.rs b/src/ui/vru.rs index 457f83f1..66154508 100644 --- a/src/ui/vru.rs +++ b/src/ui/vru.rs @@ -1,16 +1,49 @@ +use crate::gui; +use slint::ComponentHandle; + pub fn prompt_for_match( words: &[String], - window_notifier: &tokio::sync::mpsc::Sender>>, - word_index_receiver: &mut tokio::sync::mpsc::Receiver, + weak_vru: &slint::Weak, frame_time: f64, ) -> u16 { let mut dedup_words = words.to_owned(); dedup_words.sort(); dedup_words.dedup(); - window_notifier.try_send(Some(dedup_words)).unwrap(); - let mut result = word_index_receiver.try_recv(); + + let (tx, mut rx) = tokio::sync::mpsc::channel(1); + + weak_vru + .upgrade_in_event_loop(move |_handle| { + let vru_dialog = gui::VruDialog::new().unwrap(); + let vru_dialog_weak = vru_dialog.as_weak(); + + let tx_clicked = tx.clone(); + + vru_dialog.on_vru_button_clicked(move |chosen_word| { + tx_clicked.try_send(chosen_word.to_string()).unwrap(); + vru_dialog_weak.unwrap().window().hide().unwrap(); + }); + + vru_dialog.window().on_close_requested(move || { + tx.try_send("".to_string()).unwrap(); + slint::CloseRequestResponse::HideWindow + }); + + let words_vec = slint::VecModel::default(); + for word in dedup_words { + words_vec.push(word.into()); + } + let words_model: std::rc::Rc> = + std::rc::Rc::new(words_vec); + vru_dialog.set_words(slint::ModelRc::from(words_model)); + + vru_dialog.show().unwrap(); + }) + .unwrap(); + + let mut result = rx.try_recv(); while result.is_err() { - result = word_index_receiver.try_recv(); + result = rx.try_recv(); std::thread::sleep(std::time::Duration::from_secs_f64(frame_time)); unsafe { sdl3_sys::events::SDL_PumpEvents() }; // so the OS doesn't complain about the game window being frozen }