From c7b331cec1cc49a17a90764ce066645cac8da216 Mon Sep 17 00:00:00 2001 From: Theron Date: Sat, 29 Feb 2020 22:26:59 -0600 Subject: [PATCH] drag and drop working for save states --- README.md | 6 +++--- src/apu/serialize.rs | 3 +-- src/main.rs | 15 ++++++++++++--- src/state.rs | 36 +++++++++++------------------------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 9898c3c..0cebe6e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ ___________________ ------------------- Save state: F5 Load state: F9 -(Only one save state at a time supported currently: saving a second time overwrites the first.) +(Saving state a second time overwrites the first unless you copy/rename the `.dat` file. You can drag and drop any `.dat` file onto the window to load it.) ``` The code aims to follow the explanations from the [NES dev wiki](https://wiki.nesdev.com/w/index.php/NES_reference_guide) where possible, especially in the PPU, and the comments quote from it often. Thanks to everyone who contributes to that wiki/forum, and to Michael Fogleman's [NES](https://github.com/fogleman/nes) and Scott Ferguson's [Fergulator](https://github.com/scottferg/Fergulator) for getting me unstuck at several points. @@ -47,9 +47,9 @@ The code aims to follow the explanations from the [NES dev wiki](https://wiki.ne - DMC audio channel, high- and low-pass filters -- Better GUI and distributable solution +- Better GUI -- Save states +- Better save state handling - Player 2 controller? diff --git a/src/apu/serialize.rs b/src/apu/serialize.rs index aa5450f..73d8b5c 100644 --- a/src/apu/serialize.rs +++ b/src/apu/serialize.rs @@ -2,8 +2,7 @@ pub type ApuData = super::Apu; impl super::Apu{ pub fn save_state(&self) -> ApuData { - let x: ApuData = self.clone(); - x + self.clone() } pub fn load_state(&mut self, data: ApuData) { diff --git a/src/main.rs b/src/main.rs index 03748d7..fccd7db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,13 +13,14 @@ use apu::Apu; use cartridge::get_mapper; use input::poll_buttons; use screen::{init_window, draw_pixel, draw_to_window}; -use state::{save_state, load_state}; +use state::{save_state, load_state, change_file_extension}; use std::sync::{Arc, Mutex}; use std::time::{Instant, Duration}; use sdl2::keyboard::Keycode; use sdl2::event::Event; use sdl2::pixels::PixelFormatEnum; +use std::path::Path; // use cpuprofiler::PROFILER; @@ -96,12 +97,20 @@ fn main() -> Result<(), String> { Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => break 'running, Event::KeyDown{ keycode: Some(Keycode::F5), .. } => { - let res: Result<(), String> = save_state(&cpu, &filename) + let dat_filename = state::change_file_extension(&filename, "dat").unwrap(); + let res: Result<(), String> = save_state(&cpu, dat_filename) .or_else(|e| {println!("{}", e); Ok(())}); res.unwrap(); }, Event::KeyDown{ keycode: Some(Keycode::F9), .. } => { - let res: Result<(), String> = load_state(&mut cpu, &filename) + let dat_filename = state::change_file_extension(&filename, "dat").unwrap(); + let res: Result<(), String> = load_state(&mut cpu, dat_filename) + .or_else(|e| {println!("{}", e); Ok(())}); + res.unwrap(); + }, + Event::DropFile{ timestamp: _t, window_id: _w, filename: f } => { + let p = Path::new(&f).to_path_buf(); + let res: Result<(), String> = load_state(&mut cpu, p) .or_else(|e| {println!("{}", e); Ok(())}); res.unwrap(); }, diff --git a/src/state.rs b/src/state.rs index 7b08953..d15295b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,7 +4,7 @@ use super::apu; use std::fs::File; use std::io::{Read, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] @@ -14,7 +14,7 @@ struct SaveState { apu: apu::serialize::ApuData, } -pub fn save_state(cpu: &cpu::Cpu, filename: &str) -> Result<(), String> { +pub fn save_state(cpu: &cpu::Cpu, save_file: PathBuf) -> Result<(), String> { let data = SaveState{ cpu: cpu.save_state(), ppu: cpu.ppu.save_state(), @@ -22,16 +22,6 @@ pub fn save_state(cpu: &cpu::Cpu, filename: &str) -> Result<(), String> { }; let serialized = serde_json::to_string(&data) .map_err(|e| e.to_string())?; - let path = match Path::new(&filename).parent() { - Some(p) => p, - None => return Err("couldn't convert filename to path".to_string()), - }; - let stem = match Path::new(&filename).file_stem() { - Some(s) => s, - None => return Err("couldn't get file stem".to_string()), - }; - let mut save_file = path.join(stem); - save_file.set_extension("dat"); println!("state saved to file: {:?}", save_file); let mut f = File::create(&save_file) .expect("could not create output file for save state"); @@ -39,19 +29,7 @@ pub fn save_state(cpu: &cpu::Cpu, filename: &str) -> Result<(), String> { .map_err(|_| "couldn't write serialized data to file".to_string()) } -pub fn load_state(cpu: &mut cpu::Cpu, filename: &str) -> Result<(), String> { - // load file, deserialize to cpudata, set cpu fields to data fields - let path = match Path::new(&filename).parent() { - Some(p) => p, - None => return Err("couldn't convert filename to path".to_string()), - }; - let stem = match Path::new(&filename).file_stem() { - Some(s) => s, - None => return Err("couldn't get file stem".to_string()), - }; - let mut save_file = path.join(stem); - save_file.set_extension("dat"); - +pub fn load_state(cpu: &mut cpu::Cpu, save_file: PathBuf) -> Result<(), String> { if Path::new(&save_file).exists() { let mut f = File::open(save_file.clone()) .map_err(|e| e.to_string())?; @@ -71,3 +49,11 @@ pub fn load_state(cpu: &mut cpu::Cpu, filename: &str) -> Result<(), String> { Err(format!("no save state file at {:?}", save_file)) } } + +pub fn change_file_extension(filename: &str, extension: &str) -> Option { + let path = Path::new(filename).parent()?; + let stem = Path::new(&filename).file_stem()?; + let mut save_file = path.join(stem); + save_file.set_extension(extension); + Some(save_file) +}