use crate::platform::autorun::AutorunMode;
use crate::platform::autorun::{
AUTORUN_VERSION, AutorunCheckpoint, AutorunFile, AutorunFormat, AutorunFrame,
CHECKPOINT_INTERVAL_FRAMES, autorun_path_for_rom, backup_autorun_file, load_autorun_file,
save_autorun_file,
};
use std::path::PathBuf;
fn resolve_checkpoint_index(raw: i64, total: usize) -> Result<usize, String> {
if raw >= 0 {
Ok(raw as usize)
} else {
let resolved = (total as i64 - 1) + raw;
if resolved < 0 {
Err(format!(
"Checkpoint index {} out of range (recording has {} checkpoints)",
raw, total
))
} else {
Ok(resolved as usize)
}
}
}
pub struct PendingRestore {
pub state_bytes: Vec<u8>,
}
pub struct AutorunState {
autorun: AutorunFile,
autorun_path: PathBuf,
frame_index: usize,
extending_playback: bool,
mode: AutorunMode,
playback_checkpoint_idx: usize,
crc_mismatches: usize,
total_checkpoints_verified: usize,
current_frame_prerecorded: bool,
format: AutorunFormat,
}
impl AutorunState {
pub fn new(
mode: AutorunMode,
rom_path: &str,
overwrite: bool,
extend: bool,
from_checkpoint: Option<i64>,
format: AutorunFormat,
) -> Result<(Self, Option<PendingRestore>), String> {
let autorun_path = autorun_path_for_rom(&PathBuf::from(rom_path));
match mode {
AutorunMode::None => Err("AutorunState requires Record or Playback mode".to_string()),
AutorunMode::Record => {
if extend && autorun_path.exists() {
Self::new_extend(autorun_path, format)
} else {
if autorun_path.exists() {
if overwrite {
backup_autorun_file(&autorun_path)?;
} else {
return Err(format!(
"Recording already exists: {} (use --create-recording to replace)",
autorun_path.display()
));
}
}
let state = Self {
autorun: AutorunFile {
version: AUTORUN_VERSION,
frames: Vec::new(),
checkpoints: Vec::new(),
},
autorun_path,
frame_index: 0,
extending_playback: false,
mode,
playback_checkpoint_idx: 0,
crc_mismatches: 0,
total_checkpoints_verified: 0,
current_frame_prerecorded: false,
format,
};
Ok((state, None))
}
}
AutorunMode::Playback => {
let autorun = load_autorun_file(&autorun_path, None)?;
let (frame_index, playback_checkpoint_idx, pending) = if let Some(cp_idx_raw) =
from_checkpoint
{
let cp_idx = resolve_checkpoint_index(cp_idx_raw, autorun.checkpoints.len())?;
let cp = autorun.checkpoints.get(cp_idx).ok_or_else(|| {
format!(
"Checkpoint index {cp_idx} out of range (recording has {})",
autorun.checkpoints.len()
)
})?;
let pending = PendingRestore {
state_bytes: cp.state_bytes.clone(),
};
(cp.frame_index as usize + 1, cp_idx + 1, Some(pending))
} else {
(0, 0, None)
};
let state = Self {
autorun,
autorun_path,
frame_index,
extending_playback: false,
mode,
playback_checkpoint_idx,
crc_mismatches: 0,
total_checkpoints_verified: 0,
current_frame_prerecorded: false,
format,
};
Ok((state, pending))
}
}
}
fn new_extend(
autorun_path: PathBuf,
format: AutorunFormat,
) -> Result<(Self, Option<PendingRestore>), String> {
let existing = load_autorun_file(&autorun_path, None)?;
let n = existing.checkpoints.len();
let (frame_index, playback_checkpoint_idx, pending) = if n >= 2 {
let second_to_last = &existing.checkpoints[n - 2];
let pending = PendingRestore {
state_bytes: second_to_last.state_bytes.clone(),
};
(
second_to_last.frame_index as usize + 1,
n - 1,
Some(pending),
)
} else if n == 1 {
let cp = &existing.checkpoints[0];
let pending = PendingRestore {
state_bytes: cp.state_bytes.clone(),
};
(cp.frame_index as usize + 1, 1, Some(pending))
} else {
(0, 0, None)
};
let state = Self {
autorun: existing,
autorun_path,
frame_index,
extending_playback: true,
mode: AutorunMode::Record,
playback_checkpoint_idx,
crc_mismatches: 0,
total_checkpoints_verified: 0,
current_frame_prerecorded: false,
format,
};
Ok((state, pending))
}
pub fn total_frames(&self) -> usize {
self.autorun.frames.len()
}
pub fn current_frame_index(&self) -> usize {
self.frame_index
}
pub fn is_extending_playback(&self) -> bool {
self.extending_playback && self.frame_index < self.autorun.frames.len()
}
pub fn begin_frame(&mut self) {
self.current_frame_prerecorded = false;
}
pub fn current_frame_was_prerecorded(&self) -> bool {
self.current_frame_prerecorded
}
pub fn next_playback_frame(&mut self) -> Option<AutorunFrame> {
if self.frame_index < self.autorun.frames.len() {
let frame = self.autorun.frames[self.frame_index].clone();
self.frame_index += 1;
self.current_frame_prerecorded = true;
Some(frame)
} else {
None
}
}
pub fn record_frame(&mut self, player1: u8, player2: u8) -> bool {
self.autorun.frames.push(AutorunFrame { player1, player2 });
self.frame_index += 1;
(self.frame_index as u32).is_multiple_of(CHECKPOINT_INTERVAL_FRAMES)
}
pub fn record_checkpoint(&mut self, screen_crc: u32, state_bytes: Vec<u8>) {
self.autorun.checkpoints.push(AutorunCheckpoint {
frame_index: (self.frame_index - 1) as u32,
screen_crc,
state_bytes,
});
}
pub fn check_playback_checkpoint(&mut self, screen_crc: u32) -> Option<bool> {
let current = (self.frame_index - 1) as u32; let mut matched = None;
while self.playback_checkpoint_idx < self.autorun.checkpoints.len()
&& self.autorun.checkpoints[self.playback_checkpoint_idx].frame_index == current
{
let expected = self.autorun.checkpoints[self.playback_checkpoint_idx].screen_crc;
let ok = screen_crc == expected;
if !ok {
self.crc_mismatches += 1;
}
self.total_checkpoints_verified += 1;
self.playback_checkpoint_idx += 1;
matched = Some(ok);
}
matched
}
pub fn save_with_final_checkpoint(
&mut self,
screen_crc: u32,
state_bytes: Vec<u8>,
) -> Result<(), String> {
self.autorun.checkpoints.push(AutorunCheckpoint {
frame_index: self.frame_index.saturating_sub(1) as u32,
screen_crc,
state_bytes,
});
save_autorun_file(&self.autorun_path, &self.autorun, self.format, None)
}
pub fn crc_mismatches(&self) -> usize {
self.crc_mismatches
}
pub fn total_checkpoints_verified(&self) -> usize {
self.total_checkpoints_verified
}
pub fn total_checkpoints(&self) -> usize {
self.autorun.checkpoints.len()
}
pub fn mode(&self) -> AutorunMode {
self.mode
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
fn make_file_with_checkpoints(num_frames: u32, checkpoint_crcs: &[(u32, u32)]) -> AutorunFile {
AutorunFile {
version: AUTORUN_VERSION,
frames: (0..num_frames)
.map(|_| AutorunFrame {
player1: 0,
player2: 0,
})
.collect(),
checkpoints: checkpoint_crcs
.iter()
.map(|(frame_idx, crc)| AutorunCheckpoint {
frame_index: *frame_idx,
screen_crc: *crc,
state_bytes: vec![],
})
.collect(),
}
}
#[test]
fn test_total_frames_empty() {
let rom_file = NamedTempFile::new().expect("create temp file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
assert_eq!(state.total_frames(), 0);
}
#[test]
fn test_total_frames_with_data() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = make_file_with_checkpoints(3, &[(2, 0xABCD)]);
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None)
.expect("save file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (state, _) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
assert_eq!(state.total_frames(), 3);
}
#[test]
fn test_record_frame_returns_true_at_checkpoint_interval() {
let rom_file = NamedTempFile::new().expect("create temp file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (mut state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
for _ in 0..299 {
let checkpoint_due = state.record_frame(0, 0);
assert!(!checkpoint_due);
}
let checkpoint_due = state.record_frame(0, 0);
assert!(checkpoint_due, "frame 300 should trigger a checkpoint");
}
#[test]
fn test_record_checkpoint_stores_crc_and_state() {
let rom_file = NamedTempFile::new().expect("create temp file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (mut state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
for _ in 0..300 {
state.record_frame(0, 0);
}
state.record_checkpoint(0xCAFEBABE, vec![1, 2, 3]);
assert_eq!(state.autorun.checkpoints.len(), 1);
assert_eq!(state.autorun.checkpoints[0].frame_index, 299);
assert_eq!(state.autorun.checkpoints[0].screen_crc, 0xCAFEBABE);
assert_eq!(state.autorun.checkpoints[0].state_bytes, vec![1, 2, 3]);
}
#[test]
fn test_check_playback_checkpoint_matching_crc() {
let rom_file = NamedTempFile::new().expect("create temp file");
let file = make_file_with_checkpoints(300, &[(299, 0x1234)]);
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &file, AutorunFormat::Json, None).expect("save");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (mut state, _) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create state");
for _ in 0..300 {
state.next_playback_frame();
}
let result = state.check_playback_checkpoint(0x1234);
assert_eq!(result, Some(true));
assert_eq!(state.crc_mismatches(), 0);
assert_eq!(state.total_checkpoints_verified(), 1);
}
#[test]
fn test_check_playback_checkpoint_mismatching_crc() {
let rom_file = NamedTempFile::new().expect("create temp file");
let file = make_file_with_checkpoints(300, &[(299, 0x1234)]);
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &file, AutorunFormat::Json, None).expect("save");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (mut state, _) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create state");
for _ in 0..300 {
state.next_playback_frame();
}
let result = state.check_playback_checkpoint(0xBADBAD00);
assert_eq!(result, Some(false));
assert_eq!(state.crc_mismatches(), 1);
}
#[test]
fn test_overwrite_record_creates_backup() {
let rom_file = NamedTempFile::new().expect("create temp file");
let existing_file = make_file_with_checkpoints(1, &[(0, 0xABCD)]);
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &existing_file, AutorunFormat::Json, None)
.expect("save existing");
assert!(autorun_path.exists());
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let _ = AutorunState::new(
AutorunMode::Record,
rom_path_str,
true,
false,
None,
AutorunFormat::Json,
)
.expect("create state with overwrite");
let bak_path = autorun_path.with_extension("autorun.bak");
assert!(
bak_path.exists(),
"backup file should be created on overwrite"
);
let _ = std::fs::remove_file(&bak_path);
}
#[test]
fn test_is_extending_playback_starts_true_in_extend_mode() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![
AutorunFrame {
player1: 1,
player2: 2,
},
AutorunFrame {
player1: 3,
player2: 4,
},
],
checkpoints: vec![
AutorunCheckpoint {
frame_index: 0,
screen_crc: 0,
state_bytes: vec![],
},
AutorunCheckpoint {
frame_index: 1,
screen_crc: 0,
state_bytes: vec![],
},
],
};
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None)
.expect("save file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
true,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
assert!(state.is_extending_playback());
}
#[test]
fn test_extend_mode_starts_from_second_to_last_checkpoint() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![
AutorunFrame {
player1: 0,
player2: 0
};
1200
],
checkpoints: vec![
AutorunCheckpoint {
frame_index: 299,
screen_crc: 0,
state_bytes: b"state0".to_vec(),
},
AutorunCheckpoint {
frame_index: 599,
screen_crc: 0,
state_bytes: b"state1".to_vec(),
},
AutorunCheckpoint {
frame_index: 899,
screen_crc: 0,
state_bytes: b"state2".to_vec(),
},
AutorunCheckpoint {
frame_index: 1199,
screen_crc: 0,
state_bytes: b"state3".to_vec(),
},
],
};
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, pending) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
true,
None,
AutorunFormat::Json,
)
.expect("create state");
assert_eq!(state.current_frame_index(), 900);
assert!(
pending.is_some(),
"should have pending restore for second-to-last checkpoint"
);
assert_eq!(
pending.unwrap().state_bytes,
b"state2".to_vec(),
"should restore state from second-to-last checkpoint"
);
}
#[test]
fn test_playback_from_checkpoint_sets_frame_index() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![
AutorunFrame {
player1: 0,
player2: 0
};
600
],
checkpoints: vec![
AutorunCheckpoint {
frame_index: 299,
screen_crc: 0x1111,
state_bytes: b"s0".to_vec(),
},
AutorunCheckpoint {
frame_index: 599,
screen_crc: 0x2222,
state_bytes: b"s1".to_vec(),
},
],
};
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, pending) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
Some(0),
AutorunFormat::Json,
)
.expect("create state");
assert_eq!(state.current_frame_index(), 300);
assert!(pending.is_some());
assert_eq!(pending.unwrap().state_bytes, b"s0".to_vec());
}
#[test]
fn test_is_extending_playback_in_normal_record_mode() {
let rom_file = NamedTempFile::new().expect("create temp file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
assert!(!state.is_extending_playback());
}
#[test]
fn test_is_extending_playback_in_playback_mode() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![AutorunFrame {
player1: 1,
player2: 2,
}],
checkpoints: vec![AutorunCheckpoint {
frame_index: 0,
screen_crc: 0,
state_bytes: vec![],
}],
};
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None)
.expect("save file");
let rom_path_str = rom_file.path().to_str().expect("rom path to string");
let (state, _) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create autorun state");
assert!(!state.is_extending_playback());
}
fn make_file_with_4_checkpoints() -> (tempfile::NamedTempFile, AutorunFile) {
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![
AutorunFrame {
player1: 0,
player2: 0
};
4
],
checkpoints: vec![
AutorunCheckpoint {
frame_index: 0,
screen_crc: 0,
state_bytes: b"s0".to_vec(),
},
AutorunCheckpoint {
frame_index: 1,
screen_crc: 0,
state_bytes: b"s1".to_vec(),
},
AutorunCheckpoint {
frame_index: 2,
screen_crc: 0,
state_bytes: b"s2".to_vec(),
},
AutorunCheckpoint {
frame_index: 3,
screen_crc: 0,
state_bytes: b"s3".to_vec(),
},
],
};
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
(rom_file, autorun_file)
}
#[test]
fn test_playback_from_negative_checkpoint_minus_1_is_second_to_last() {
let (rom_file, _) = make_file_with_4_checkpoints();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, pending) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
Some(-1),
AutorunFormat::Json,
)
.expect("create state");
assert_eq!(state.current_frame_index(), 3);
assert_eq!(pending.unwrap().state_bytes, b"s2".to_vec());
}
#[test]
fn test_playback_from_negative_checkpoint_minus_2_is_third_from_end() {
let (rom_file, _) = make_file_with_4_checkpoints();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, pending) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
Some(-2),
AutorunFormat::Json,
)
.expect("create state");
assert_eq!(state.current_frame_index(), 2);
assert_eq!(pending.unwrap().state_bytes, b"s1".to_vec());
}
#[test]
fn test_playback_from_negative_checkpoint_out_of_range_is_error() {
let (rom_file, _) = make_file_with_4_checkpoints();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let result = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
Some(-4),
AutorunFormat::Json,
);
assert!(result.is_err(), "negative index beyond range should fail");
}
fn make_recording_with_single_checkpoint() -> (tempfile::NamedTempFile, AutorunFile) {
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![
AutorunFrame {
player1: 0,
player2: 0
};
300
],
checkpoints: vec![AutorunCheckpoint {
frame_index: 299,
screen_crc: 0xABCD,
state_bytes: b"single_checkpoint_state".to_vec(),
}],
};
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
(rom_file, autorun_file)
}
fn make_recording_with_no_checkpoints() -> tempfile::NamedTempFile {
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![],
checkpoints: vec![],
};
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
rom_file
}
#[test]
fn test_extend_with_single_checkpoint_restores_from_it() {
let (rom_file, _) = make_recording_with_single_checkpoint();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (_, pending) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
true,
None,
AutorunFormat::Json,
)
.expect("create extend state");
assert!(
pending.is_some(),
"extending with 1 checkpoint must restore from that checkpoint"
);
assert_eq!(
pending.unwrap().state_bytes,
b"single_checkpoint_state".to_vec()
);
}
#[test]
fn test_extend_with_single_checkpoint_starts_at_frame_after_checkpoint() {
let (rom_file, _) = make_recording_with_single_checkpoint();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
true,
None,
AutorunFormat::Json,
)
.expect("create extend state");
assert_eq!(
state.current_frame_index(),
300,
"extend with 1 checkpoint should start at CP[0].frame_index + 1"
);
}
#[test]
fn test_extend_with_single_checkpoint_has_no_extend_playback() {
let (rom_file, _) = make_recording_with_single_checkpoint();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
true,
None,
AutorunFormat::Json,
)
.expect("create extend state");
assert!(
!state.is_extending_playback(),
"extend with 1 checkpoint should not replay frames — restoration alone is sufficient"
);
}
#[test]
fn test_extend_with_no_checkpoints_starts_from_scratch() {
let rom_file = make_recording_with_no_checkpoints();
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (state, pending) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
true,
None,
AutorunFormat::Json,
)
.expect("create extend state");
assert!(
pending.is_none(),
"no checkpoints means no state to restore"
);
assert_eq!(state.current_frame_index(), 0, "should start from frame 0");
}
#[test]
fn test_begin_frame_clears_prerecorded_flag() {
let rom_file = NamedTempFile::new().expect("create temp file");
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (mut state, _) = AutorunState::new(
AutorunMode::Record,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create state");
state.begin_frame();
assert!(!state.current_frame_was_prerecorded());
}
#[test]
fn test_prerecorded_flag_true_after_next_playback_frame_returns_some() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![AutorunFrame {
player1: 7,
player2: 0,
}],
checkpoints: vec![AutorunCheckpoint {
frame_index: 0,
screen_crc: 0,
state_bytes: vec![],
}],
};
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (mut state, _) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create state");
state.begin_frame();
let frame = state.next_playback_frame();
assert!(frame.is_some(), "should return the pre-recorded frame");
assert!(
state.current_frame_was_prerecorded(),
"flag must be true after next_playback_frame returned Some"
);
}
#[test]
fn test_prerecorded_flag_false_after_next_playback_frame_returns_none() {
let rom_file = NamedTempFile::new().expect("create temp file");
let autorun_file = AutorunFile {
version: AUTORUN_VERSION,
frames: vec![],
checkpoints: vec![],
};
let autorun_path = autorun_path_for_rom(rom_file.path());
save_autorun_file(&autorun_path, &autorun_file, AutorunFormat::Json, None).expect("save");
let rom_path_str = rom_file.path().to_str().expect("rom path");
let (mut state, _) = AutorunState::new(
AutorunMode::Playback,
rom_path_str,
false,
false,
None,
AutorunFormat::Json,
)
.expect("create state");
state.begin_frame();
let frame = state.next_playback_frame();
assert!(frame.is_none());
assert!(
!state.current_frame_was_prerecorded(),
"flag must remain false when next_playback_frame returns None"
);
}
}