Screenshots: a second F2 in the same second takes the next name

The PNG is now written on its own thread, so the first press's file may not
exist yet when a second press picks its name: both took the same path and
two writers raced on one file. screenshot_name also skips the paths of the
screenshots still being written (UiState::screenshot_messages).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Horizon agentandClaude Opus 5.5 committed 2026-11-09 18:44:35 +01:00
1 parent 15d2dd04c4
commit 1ffa9ed25f
1 file changed
+21 -3
+21 -3
View File
@@ -1982,7 +1982,8 @@ impl Game<'_> {
if std::fs::create_dir_all(&dir).is_err() {
return None;
}
let name = screenshot_name(&dir);
// A screenshot still being written has no file yet: its name is taken all the same.
let name = screenshot_name(&dir, &self.ui.screenshot_messages);
let renderer = self.renderer.as_mut()?;
let path = dir.join(name);
renderer.screenshot = Some(horizon_client::gfx::ScreenshotRequest {
@@ -2067,7 +2068,7 @@ fn chat_box_text(text: &str) -> String {
.filter(|&ch| ch >= ' ' && ch != '\u{7f}' && ch != '§')
.collect()
}
pub fn screenshot_name(dir: &std::path::Path) -> String {
pub fn screenshot_name(dir: &std::path::Path, pending: &[std::path::PathBuf]) -> String {
// Util.getFilenameFormattedDateTime: the local date and time.
let base = chrono::Local::now().format("%Y-%m-%d_%H.%M.%S").to_string();
let mut n = 1;
@@ -2077,7 +2078,8 @@ pub fn screenshot_name(dir: &std::path::Path) -> String {
} else {
format!("{base}_{n}.png")
};
if !dir.join(&name).exists() {
let path = dir.join(&name);
if !path.exists() && !pending.contains(&path) {
return name;
}
n += 1;
@@ -2119,6 +2121,22 @@ mod tests {
assert!(should_discard_draft(ChatExit::Done, true));
}
/// `Screenshot.getFile` picks the first free name of the second; an F2
/// screenshot still being written on its thread has no file yet, so a
/// second press in the same second takes the next name, not the same one.
#[test]
fn a_screenshot_being_written_keeps_its_name() {
let dir = std::env::temp_dir().join(format!("horizon-shots-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let first = screenshot_name(&dir, &[]);
let second = screenshot_name(&dir, &[dir.join(&first)]);
assert_ne!(first, second);
// Unless the clock turned to the next second in between.
let same_second = second.starts_with(first.trim_end_matches(".png"));
assert!(!same_second || second == first.replace(".png", "_2.png"), "{second}");
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn resource_ids_are_listed_once_per_root() {
let root = std::env::temp_dir().join(format!("horizon-ids-{}", std::process::id()));