flush saves to disk (#658)

* flush saves to disk

* more

* more

* more

* more

* more

* more

* more

* more
This commit is contained in:
Logan McNaughton
2026-01-22 20:09:13 +01:00
committed by GitHub
parent bf01898c6f
commit 9775f103e0
16 changed files with 216 additions and 242 deletions
+1 -1
View File
@@ -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"
-5
View File
@@ -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,
+1 -1
View File
@@ -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) {
+3 -2
View File
@@ -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)
+3 -3
View File
@@ -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;
+5 -5
View File
@@ -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;
+2 -1
View File
@@ -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);
}
}
+2 -15
View File
@@ -29,14 +29,6 @@ pub struct Vru {
pub word_mappings: HashMap<String, String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VruWindow {
#[serde(skip)]
pub window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
#[serde(skip)]
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
}
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
};
+4 -1
View File
@@ -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,
}
}
+34 -27
View File
@@ -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(())
}
+8 -6
View File
@@ -53,6 +53,7 @@ pub struct Ui {
pub storage: Storage,
pub video: Video,
pub usb: Usb,
pub weak: Option<slint::Weak<gui::AppWindow>>,
}
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,
}
+49 -134
View File
@@ -24,11 +24,6 @@ pub struct GbPaths {
pub ram: [Option<std::path::PathBuf>; 4],
}
pub struct VruChannel {
pub vru_window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
pub vru_word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
}
#[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<AppWindow>,
vru_word_notifier: tokio::sync::mpsc::Sender<String>,
mut vru_window_receiver: tokio::sync::mpsc::Receiver<Option<Vec<String>>>,
) {
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<slint::VecModel<slint::SharedString>> =
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<NetplayDevice>,
usb: ui::Usb,
weak: slint::Weak<AppWindow>,
) {
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<Option<Vec<String>>>,
tokio::sync::mpsc::Receiver<Option<Vec<String>>>,
) = tokio::sync::mpsc::channel(5);
let (vru_word_notifier, vru_word_receiver): (
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = 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,
+1 -5
View File
@@ -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,
+65 -27
View File
@@ -28,13 +28,13 @@ pub struct Paths {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Save {
pub data: Vec<u8>,
pub written: bool,
pub write_pending: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct RomSave {
pub data: std::collections::HashMap<u32, u8>,
pub written: bool,
pub write_pending: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -357,7 +357,7 @@ pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
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<u8>;
let rom_save_data: Vec<u8>;
@@ -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();
});
}
-4
View File
@@ -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),
+38 -5
View File
@@ -1,16 +1,49 @@
use crate::gui;
use slint::ComponentHandle;
pub fn prompt_for_match(
words: &[String],
window_notifier: &tokio::sync::mpsc::Sender<Option<Vec<String>>>,
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
weak_vru: &slint::Weak<gui::AppWindow>,
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<slint::VecModel<slint::SharedString>> =
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
}