Snapshots of the machine, kept as files beside the ROM: zelda.sfc keeps
zelda.states/<name>.state.
One name is the app's own. It writes [Name::resume] every few seconds and
on the way out, and starts from it, which is what makes restarting the app
cheap: the window closes and reopens on the same frame of the same game.
The zbanks bot names snapshots of its own - ap_init loads "home" and
"hpegs" (packages/zbanks) - and they come from the same directory.
What a snapshot is called: lower-case letters, digits, - and _. It
becomes a file name, so it is never anything a path could be made of.
31impl fmt::Display for Name { 32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 f.write_str(&self.0) 34 } 35} 36 37pub struct States { 38 dir: PathBuf, 39} 40 41impl States { 42 pub fn beside(rom: &Path) -> Self { 43 Self { dir: rom.with_extension("states") } 44 } 45 46 fn path(&self, name: &Name) -> PathBuf { 47 self.dir.join(format!("{name}.state")) 48 }
Replace the snapshot of this name. The old one stays whole until the new one is: a window killed halfway through still resumes.
The names there are, in order.
64 pub fn names(&self) -> Vec<String> { 65 let mut names: Vec<String> = fs::read_dir(&self.dir) 66 .into_iter() 67 .flatten() 68 .flatten() 69 .map(|entry| entry.path()) 70 .filter(|path| path.extension().is_some_and(|e| e == "state")) 71 .filter_map(|path| Some(path.file_stem()?.to_str()?.to_owned())) 72 .collect(); 73 names.sort(); 74 names 75 } 76}
78#[cfg(test)] 79mod tests { 80 use super::*; 81 82 fn scratch(tag: &str) -> States { 83 let dir = std::env::temp_dir().join(format!("jev-states-{tag}-{}", std::process::id())); 84 let _ = fs::remove_dir_all(&dir); 85 States { dir } 86 } 87 88 #[test] 89 fn a_snapshot_comes_back_as_written_and_only_snapshots_are_names() { 90 let states = scratch("roundtrip"); 91 let early = Name::new("early").expect("a name"); 92 let late = Name::new("late").expect("a name"); 93 states.write(&early, b"machine at t0").expect("written"); 94 states.write(&late, b"machine at t1").expect("written"); 95 states.write(&late, b"machine at t2").expect("rewritten"); 96 assert_eq!(states.read(&early).expect("read"), b"machine at t0"); 97 assert_eq!(states.read(&late).expect("read"), b"machine at t2"); 98 // Anything else in the directory (an old memory file) is not a name. 99 fs::write(states.dir.join("early.memory.json"), "{}").expect("written"); 100 assert_eq!(states.names(), vec!["early", "late"]); 101 let _ = fs::remove_dir_all(&states.dir); 102 } 103}