state.rsannotatedstate.rssource238 lines · 7.8 KB · raw
1use crate::NativeEmulatorResult;
2use crate::mainloop::{NativeEmulatorError, bincode_config};
3use jgenesis_common::frontend::EmulatorTrait;
4use std::ffi::OsStr;
5use std::fs::File;
6use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
7use std::path::{Path, PathBuf};
8use std::sync::mpsc::{Receiver, SendError, Sender};
9use std::sync::{Arc, Mutex, mpsc};
10use std::time::SystemTime;
11use std::{array, fs, thread};
12
13pub const SAVE_STATE_SLOTS: usize = 10;
14pub const EXTENSION: &str = "jst";
15
16const FILE_PREFIX: &[u8] = b"jgenstate";
17
18// 000.111.222.333
19const MAX_VERSION_LEN: usize = 15;
20
21// Prefix + version
22const MAX_HEADER_LEN: usize = FILE_PREFIX.len() + MAX_VERSION_LEN;
23
24pub type SaveStatePaths = [PathBuf; SAVE_STATE_SLOTS];
25
26pub fn init_paths(path: &Path) -> NativeEmulatorResult<[PathBuf; SAVE_STATE_SLOTS]> {
27    let path_no_ext = path.with_extension("");
28    let file_name = path_no_ext
29        .file_name()
30        .and_then(OsStr::to_str)
31        .ok_or_else(|| NativeEmulatorError::ParseFileName(path.display().to_string()))?;
32
33    let file_names: [_; SAVE_STATE_SLOTS] =
34        array::from_fn(|i| format!("{file_name}_{i}.{EXTENSION}"));
35
36    Ok(file_names.map(|name| path.with_file_name(name)))
37}
38
39#[derive(Debug, Clone, Default)]
40pub struct SaveStateMetadata {
41    pub times_nanos: [Option<u128>; SAVE_STATE_SLOTS],
42}
43
44impl SaveStateMetadata {
45    pub(crate) fn load(paths: &SaveStatePaths, version: &str) -> Self {
46        let times_nanos = array::from_fn(|i| {
47            let header_version = read_version_from_header(&paths[i]);
48            if header_version.as_deref() != Some(version) {
49                return None;
50            }
51
52            let metadata = fs::metadata(&paths[i]).ok()?;
53            let modified = metadata.modified().ok()?;
54            Some(modified.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos())
55        });
56
57        Self { times_nanos }
58    }
59}
60
61fn read_version_from_header(path: &Path) -> Option<String> {
62    let mut file = File::open(path).ok()?;
63
64    let mut buffer = [0_u8; MAX_HEADER_LEN];
65    file.read_exact(&mut buffer).ok()?;
66
67    if &buffer[..FILE_PREFIX.len()] != FILE_PREFIX {
68        return None;
69    }
70
71    read_version_from_buffer(&buffer)
72}
73
74fn read_version_from_buffer(buffer: &[u8]) -> Option<String> {
75    let version_len = buffer[FILE_PREFIX.len()] as usize;
76    if !(1..=MAX_VERSION_LEN).contains(&version_len) {
77        return None;
78    }
79
80    let version_bytes = &buffer[FILE_PREFIX.len() + 1..FILE_PREFIX.len() + 1 + version_len];
81    String::from_utf8(version_bytes.to_vec()).ok()
82}
83
84pub struct SaveStateRequest<State> {
85    pub paths: SaveStatePaths,
86    pub slot: usize,
87    pub state: Box<State>,
88    pub metadata: Arc<Mutex<SaveStateMetadata>>,
89}
90
91pub struct SaveStateResponse {
92    pub slot: usize,
93}
94
95pub type SaveStateResult = Result<SaveStateResponse, (NativeEmulatorError, SaveStateResponse)>;
96
97pub struct StateSaverThreadHandle<Emulator: EmulatorTrait> {
98    request_sender: Sender<SaveStateRequest<Emulator::SaveState>>,
99    response_receiver: Receiver<SaveStateResult>,
100}
101
102impl<Emulator: EmulatorTrait> StateSaverThreadHandle<Emulator> {
103    pub fn send_save_request(
104        &self,
105        emulator: &Emulator,
106        paths: &SaveStatePaths,
107        slot: usize,
108        metadata: Arc<Mutex<SaveStateMetadata>>,
109    ) -> Result<(), SendError<()>> {
110        let state = Box::new(emulator.to_save_state());
111
112        self.request_sender
113            .send(SaveStateRequest { paths: paths.clone(), slot, state, metadata })
114            .map_err(|_| SendError(()))
115    }
116
117    pub fn try_recv_save_response(&self) -> Option<SaveStateResult> {
118        self.response_receiver.try_recv().ok()
119    }
120}
121
122pub fn spawn_state_saver_thread<Emulator: EmulatorTrait>() -> StateSaverThreadHandle<Emulator> {
123    let (request_sender, request_receiver) = mpsc::channel();
124    let (response_sender, response_receiver) = mpsc::channel();
125
126    thread::spawn(move || run_state_saver_thread::<Emulator>(request_receiver, response_sender));
127
128    StateSaverThreadHandle { request_sender, response_receiver }
129}
130
131fn run_state_saver_thread<Emulator: EmulatorTrait>(
132    request_receiver: Receiver<SaveStateRequest<Emulator::SaveState>>,
133    response_sender: Sender<SaveStateResult>,
134) {
135    loop {
136        let Ok(request) = request_receiver.recv() else {
137            // Runner thread has dropped sender; stop running
138            return;
139        };
140
141        let save_result =
142            save::<Emulator>(&request.state, &request.paths, request.slot, &request.metadata);
143
144        let response = SaveStateResponse { slot: request.slot };
145        let result = match save_result {
146            Ok(()) => Ok(response),
147            Err(err) => Err((err, response)),
148        };
149
150        if response_sender.send(result).is_err() {
151            // Runner thread has dropped receiver; stop running
152            return;
153        }
154    }
155}
156
157pub fn save<Emulator: EmulatorTrait>(
158    state: &Emulator::SaveState,
159    paths: &SaveStatePaths,
160    slot: usize,
161    metadata: &Mutex<SaveStateMetadata>,
162) -> NativeEmulatorResult<()> {
163    let current_version = Emulator::save_state_version();
164    assert!(
165        current_version.len() <= MAX_VERSION_LEN,
166        "save state version is '{current_version}' (len {}), len must be at most {MAX_VERSION_LEN}",
167        current_version.len()
168    );
169
170    let path = &paths[slot];
171    let file = File::create(path).map_err(|source| NativeEmulatorError::StateFileOpen {
172        path: path.display().to_string(),
173        source,
174    })?;
175
176    let mut writer = BufWriter::new(file);
177    writer.write_all(FILE_PREFIX).map_err(NativeEmulatorError::SaveStateIo)?;
178
179    let version_len = current_version.len() as u8;
180    writer.write_all(&[version_len]).map_err(NativeEmulatorError::SaveStateIo)?;
181    writer.write_all(current_version.as_bytes()).map_err(NativeEmulatorError::SaveStateIo)?;
182
183    let mut encoder = zstd::Encoder::new(writer, 0).map_err(NativeEmulatorError::SaveStateIo)?;
184    bincode::encode_into_std_write(state, &mut encoder, bincode_config!())?;
185    encoder.finish().map_err(NativeEmulatorError::SaveStateIo)?;
186
187    let now_nanos =
188        SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_nanos();
189    {
190        let mut metadata = metadata.lock().unwrap();
191        metadata.times_nanos[slot] = Some(now_nanos);
192    }
193
194    Ok(())
195}
196
197pub fn load<Emulator: EmulatorTrait>(
198    emulator: &mut Emulator,
199    config: &Emulator::Config,
200    paths: &SaveStatePaths,
201    slot: usize,
202) -> NativeEmulatorResult<()> {
203    let path = &paths[slot];
204    let file = File::open(path).map_err(|source| NativeEmulatorError::StateFileOpen {
205        path: path.display().to_string(),
206        source,
207    })?;
208
209    let mut reader = BufReader::new(file);
210    let mut header_buffer = [0_u8; MAX_HEADER_LEN];
211    reader.read_exact(&mut header_buffer).map_err(NativeEmulatorError::LoadStateIo)?;
212
213    if &header_buffer[..FILE_PREFIX.len()] != FILE_PREFIX {
214        return Err(NativeEmulatorError::LoadStatePrefixMismatch);
215    }
216
217    let current_version = Emulator::save_state_version();
218    let version_in_header = read_version_from_buffer(&header_buffer)
219        .ok_or(NativeEmulatorError::LoadStatePrefixMismatch)?;
220
221    if version_in_header.as_str() != current_version {
222        return Err(NativeEmulatorError::LoadStateVersionMismatch {
223            expected: current_version.into(),
224            actual: version_in_header,
225        });
226    }
227
228    let total_header_len = (FILE_PREFIX.len() + 1 + current_version.len()) as u64;
229    reader.seek(SeekFrom::Start(total_header_len)).map_err(NativeEmulatorError::SaveStateIo)?;
230    let mut decoder = zstd::Decoder::new(reader).map_err(NativeEmulatorError::LoadStateIo)?;
231    let loaded_state: Emulator::SaveState =
232        bincode::decode_from_std_read(&mut decoder, bincode_config!())?;
233
234    emulator.load_state(loaded_state);
235    emulator.reload_config(config);
236
237    Ok(())
238}