From e40df24bd5e66d44a474df898edc8ad2c3b3adaa Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Sat, 8 Jun 2024 14:07:59 +0200 Subject: [PATCH] Allow copying comparisons This adds functionality to the run editor to allow copying comparisons. This is not only useful for copying custom comparisons, but also for "baking" the times of a generated comparison, such as the `Latest Run` or the `Average Segments` to a custom comparison that you can keep around as long as you want. This is somewhat also meant to replace the functionality of storing the current run as a Personal Best. Instead you can just store the `Latest Run` as a custom comparison after you are done with it. --- Cargo.toml | 2 +- capi/src/run_editor.rs | 14 ++++++- src/comparison/latest_run.rs | 2 +- src/run/editor/mod.rs | 42 ++++++++++++++++--- src/run/editor/tests/comparison.rs | 61 +++++++++++++++++++++++++--- src/run/mod.rs | 25 ++++++++---- src/run/parser/livesplit.rs | 12 +++--- src/run/parser/time_split_tracker.rs | 6 +-- src/run/tests/comparison.rs | 4 +- tests/rendering.rs | 19 +++++---- 10 files changed, 147 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 57b92cd4..a0f17252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,7 +123,7 @@ libc = { version = "0.2.101", optional = true } seahash = "4.1.0" [target.'cfg(windows)'.dev-dependencies] -sysinfo = { version = "0.30.0", default-features = false } +winreg = "0.52.0" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] criterion = "0.5.0" diff --git a/capi/src/run_editor.rs b/capi/src/run_editor.rs index 8352c4a5..8d4acd16 100644 --- a/capi/src/run_editor.rs +++ b/capi/src/run_editor.rs @@ -352,7 +352,7 @@ pub unsafe extern "C" fn RunEditor_active_parse_and_set_comparison_time( } /// Adds a new custom comparison. It can't be added if it starts with -/// `[Race]` or already exists. +/// `[Race]` or it already exists. #[no_mangle] pub unsafe extern "C" fn RunEditor_add_comparison( this: &mut RunEditor, @@ -425,6 +425,18 @@ pub unsafe extern "C" fn RunEditor_parse_and_generate_goal_comparison( this.parse_and_generate_goal_comparison(str(time)).is_ok() } +/// Copies a comparison with the given name as a new custom comparison with the +/// new name provided. It can't be added if it starts with `[Race]` or it +/// already exists. The old comparison needs to exist. +#[no_mangle] +pub unsafe extern "C" fn RunEditor_copy_comparison( + this: &mut RunEditor, + old_name: *const c_char, + new_name: *const c_char, +) -> bool { + this.copy_comparison(str(old_name), str(new_name)).is_ok() +} + /// Clears out the Attempt History and the Segment Histories of all the /// segments. #[no_mangle] diff --git a/src/comparison/latest_run.rs b/src/comparison/latest_run.rs index 7d5ccc96..dd18616a 100644 --- a/src/comparison/latest_run.rs +++ b/src/comparison/latest_run.rs @@ -27,7 +27,7 @@ pub const NAME: &str = "Latest Run"; fn generate(segments: &mut [Segment], method: TimingMethod) { let mut attempt_id = None; - for segment in segments.iter_mut().rev() { + for segment in segments.iter().rev() { if let Some(max_index) = segment.segment_history().try_get_max_index() { attempt_id = Some(max_index); break; diff --git a/src/run/editor/mod.rs b/src/run/editor/mod.rs index be60c301..be2b3dda 100644 --- a/src/run/editor/mod.rs +++ b/src/run/editor/mod.rs @@ -4,7 +4,7 @@ //! current state of the editor as state objects that can be visualized by any //! kind of User Interface. -use super::{ComparisonError, ComparisonResult, LinkedLayout}; +use super::{AddComparisonError, CopyComparisonError, LinkedLayout}; use crate::{ comparison, platform::prelude::*, @@ -63,7 +63,7 @@ pub enum RenameError { /// Name was invalid. InvalidName { /// The underlying error. - source: ComparisonError, + source: AddComparisonError, }, } @@ -743,8 +743,11 @@ impl Editor { } /// Adds a new custom comparison. It can't be added if it starts with - /// `[Race]` or already exists. - pub fn add_comparison(&mut self, comparison: S) -> ComparisonResult<()> { + /// `[Race]` or it already exists. + pub fn add_comparison( + &mut self, + comparison: S, + ) -> Result<(), AddComparisonError> { self.run.add_custom_comparison(comparison)?; self.fix(); Ok(()) @@ -753,7 +756,11 @@ impl Editor { /// Imports the Personal Best from the provided run as a comparison. The /// comparison can't be added if its name starts with `[Race]` or it already /// exists. - pub fn import_comparison(&mut self, run: &Run, comparison: &str) -> ComparisonResult<()> { + pub fn import_comparison( + &mut self, + run: &Run, + comparison: &str, + ) -> Result<(), AddComparisonError> { self.run.add_custom_comparison(comparison)?; let mut remaining_segments = self.run.segments_mut().as_mut_slice(); @@ -908,6 +915,31 @@ impl Editor { Ok(()) } + /// Copies a comparison with the given name as a new custom comparison with + /// the new name provided. It can't be added if it starts with `[Race]` or + /// it already exists. The old comparison needs to exist. + pub fn copy_comparison( + &mut self, + old_name: &str, + new_name: &str, + ) -> Result<(), CopyComparisonError> { + if !self.run.comparisons().any(|c| c == old_name) { + return Err(CopyComparisonError::NoSuchComparison); + } + + self.run + .add_custom_comparison(new_name) + .map_err(|source| CopyComparisonError::AddComparison { source })?; + + for segment in self.run.segments_mut() { + *segment.comparison_mut(new_name) = segment.comparison(old_name); + } + + self.raise_run_edited(); + + Ok(()) + } + /// Clears out the Attempt History and the Segment Histories of all the /// segments. pub fn clear_history(&mut self) { diff --git a/src/run/editor/tests/comparison.rs b/src/run/editor/tests/comparison.rs index f24801f8..59529840 100644 --- a/src/run/editor/tests/comparison.rs +++ b/src/run/editor/tests/comparison.rs @@ -1,5 +1,8 @@ -use crate::run::{ComparisonError, Editor, RenameError}; -use crate::{Run, Segment}; +use crate::{ + comparison::{best_segments, personal_best}, + run::{AddComparisonError, CopyComparisonError, Editor, RenameError}, + Run, Segment, +}; #[test] fn adding_a_new_comparison_works() { @@ -15,8 +18,54 @@ fn adding_a_duplicate_fails() { let mut run = Run::new(); run.push_segment(Segment::new("s")); let mut editor = Editor::new(run).unwrap(); - let c = editor.add_comparison("Best Segments"); - assert_eq!(c, Err(ComparisonError::DuplicateName)); + let c = editor.add_comparison(best_segments::NAME); + assert_eq!(c, Err(AddComparisonError::DuplicateName)); +} + +#[test] +fn copying_a_comparison_works() { + let mut run = Run::new(); + run.push_segment(Segment::new("s")); + let mut editor = Editor::new(run).unwrap(); + let c = editor.copy_comparison(personal_best::NAME, "My Comparison"); + assert_eq!(c, Ok(())); +} + +#[test] +fn copying_a_duplicate_fails() { + let mut run = Run::new(); + run.push_segment(Segment::new("s")); + let mut editor = Editor::new(run).unwrap(); + let c = editor.copy_comparison(personal_best::NAME, best_segments::NAME); + assert_eq!( + c, + Err(CopyComparisonError::AddComparison { + source: AddComparisonError::DuplicateName, + }) + ); +} + +#[test] +fn copying_to_a_race_name_fails() { + let mut run = Run::new(); + run.push_segment(Segment::new("s")); + let mut editor = Editor::new(run).unwrap(); + let c = editor.copy_comparison(personal_best::NAME, "[Race]Custom"); + assert_eq!( + c, + Err(CopyComparisonError::AddComparison { + source: AddComparisonError::NameStartsWithRace, + }) + ); +} + +#[test] +fn copying_an_inexistent_comparison_fails() { + let mut run = Run::new(); + run.push_segment(Segment::new("s")); + let mut editor = Editor::new(run).unwrap(); + let c = editor.copy_comparison("My Comparison", "My Comparison 2"); + assert_eq!(c, Err(CopyComparisonError::NoSuchComparison)); } #[test] @@ -49,7 +98,7 @@ fn renaming_to_a_race_name_fails() { assert_eq!( c, Err(RenameError::InvalidName { - source: ComparisonError::NameStartsWithRace + source: AddComparisonError::NameStartsWithRace }) ); } @@ -65,7 +114,7 @@ fn renaming_to_an_existing_name_fails() { assert_eq!( c, Err(RenameError::InvalidName { - source: ComparisonError::DuplicateName + source: AddComparisonError::DuplicateName }) ); } diff --git a/src/run/mod.rs b/src/run/mod.rs index ca44dcbc..135948c7 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -90,17 +90,26 @@ impl PartialEq for ComparisonGenerators { } } -/// Error type for an invalid comparison name +/// Error type for an invalid comparison name to be added. #[derive(PartialEq, Eq, Debug, snafu::Snafu)] -pub enum ComparisonError { +pub enum AddComparisonError { /// Comparison name starts with "\[Race\]". NameStartsWithRace, /// Comparison name is a duplicate. DuplicateName, } -/// Result type for an invalid comparison name -pub type ComparisonResult = Result; +/// Error type for copying a comparison. +#[derive(PartialEq, Eq, Debug, snafu::Snafu)] +pub enum CopyComparisonError { + /// There is no comparison with the provided name to copy. + NoSuchComparison, + /// The new comparison could not be added. + AddComparison { + /// The underlying error. + source: AddComparisonError, + }, +} impl Run { /// Creates a new Run object with no segments. @@ -443,7 +452,7 @@ impl Run { /// Adds a new custom comparison. If a custom comparison with that name /// already exists, it is not added. #[inline] - pub fn add_custom_comparison(&mut self, comparison: S) -> ComparisonResult<()> + pub fn add_custom_comparison(&mut self, comparison: S) -> Result<(), AddComparisonError> where S: PopulateString, { @@ -756,11 +765,11 @@ impl Run { /// Checks a given name against the current comparisons in the Run to /// ensure that it is valid for use. - pub fn validate_comparison_name(&self, new: &str) -> ComparisonResult<()> { + pub fn validate_comparison_name(&self, new: &str) -> Result<(), AddComparisonError> { if new.starts_with(RACE_COMPARISON_PREFIX) { - Err(ComparisonError::NameStartsWithRace) + Err(AddComparisonError::NameStartsWithRace) } else if self.comparisons().any(|c| c == new) { - Err(ComparisonError::DuplicateName) + Err(AddComparisonError::DuplicateName) } else { Ok(()) } diff --git a/src/run/parser/livesplit.rs b/src/run/parser/livesplit.rs index 4c6376f5..d36578ea 100644 --- a/src/run/parser/livesplit.rs +++ b/src/run/parser/livesplit.rs @@ -2,7 +2,7 @@ use crate::{ platform::prelude::*, - run::{ComparisonError, LinkedLayout}, + run::{AddComparisonError, LinkedLayout}, settings::Image, util::{ ascii_char::AsciiChar, @@ -52,7 +52,7 @@ pub enum Error { /// Parsed comparison has an invalid name. InvalidComparisonName { /// The underlying error. - source: ComparisonError, + source: AddComparisonError, }, /// Failed to parse a boolean. ParseBool, @@ -82,8 +82,8 @@ impl From for Error { } } -impl From for Error { - fn from(source: ComparisonError) -> Self { +impl From for Error { + fn from(source: AddComparisonError) -> Self { Self::InvalidComparisonName { source } } } @@ -295,10 +295,10 @@ fn parse_segment( } else { time_old(reader, |t| *segment.comparison_mut(&comparison) = t)?; } - if let Err(ComparisonError::NameStartsWithRace) = + if let Err(AddComparisonError::NameStartsWithRace) = run.add_custom_comparison(comparison) { - return Err(ComparisonError::NameStartsWithRace.into()); + return Err(AddComparisonError::NameStartsWithRace.into()); } Ok(()) } else { diff --git a/src/run/parser/time_split_tracker.rs b/src/run/parser/time_split_tracker.rs index 14f0cd3b..d70f7bc8 100644 --- a/src/run/parser/time_split_tracker.rs +++ b/src/run/parser/time_split_tracker.rs @@ -1,6 +1,6 @@ //! Provides the parser for Time Split Tracker splits files. -use super::super::ComparisonError; +use super::super::AddComparisonError; use crate::{ comparison::RACE_COMPARISON_PREFIX, platform::{path::Path, prelude::*}, @@ -124,13 +124,13 @@ pub fn parse( loop { match run.add_custom_comparison(&**comparison) { Ok(_) => break, - Err(ComparisonError::DuplicateName) => { + Err(AddComparisonError::DuplicateName) => { let comparison = comparison.to_mut(); comparison.drain(orig_len..); let _ = write!(comparison, " {number}"); number += 1; } - Err(ComparisonError::NameStartsWithRace) => { + Err(AddComparisonError::NameStartsWithRace) => { let comparison = comparison.to_mut(); // After removing the `[Race]`, there might be some // whitespace we want to trim too. diff --git a/src/run/tests/comparison.rs b/src/run/tests/comparison.rs index a9eb864e..d7f211d9 100644 --- a/src/run/tests/comparison.rs +++ b/src/run/tests/comparison.rs @@ -1,4 +1,4 @@ -use crate::run::{ComparisonError, Run}; +use crate::run::{AddComparisonError, Run}; #[test] fn adding_a_new_comparison_works() { @@ -11,5 +11,5 @@ fn adding_a_new_comparison_works() { fn adding_a_duplicate_fails() { let mut run = Run::new(); let c = run.add_custom_comparison("Best Segments"); - assert_eq!(c, Err(ComparisonError::DuplicateName)); + assert_eq!(c, Err(AddComparisonError::DuplicateName)); } diff --git a/tests/rendering.rs b/tests/rendering.rs index 711a8658..5d8353cc 100644 --- a/tests/rendering.rs +++ b/tests/rendering.rs @@ -60,13 +60,18 @@ fn default() { #[cfg(all(feature = "font-loading", windows))] #[test] fn font_fallback() { - let build_number: u64 = sysinfo::System::kernel_version().unwrap().parse().unwrap(); + let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); + let cur_ver = hklm + .open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion") + .unwrap(); + let build_number: String = cur_ver.get_value("CurrentBuildNumber").unwrap(); + let build_number: u64 = build_number.parse().unwrap(); + let revision: u32 = cur_ver.get_value("UBR").unwrap(); - if build_number < 22000 { - // The hash is different before Windows 11. + if (build_number, revision) < (22631, 3672) { + // The hash is different before Windows 11 23H2 (end of May 2024 update). println!( - "Skipping font fallback test on Windows with build number {}.", - build_number + "Skipping font fallback test on Windows with build number {build_number}.{revision}.", ); return; } @@ -122,8 +127,8 @@ fn font_fallback() { check( &state, &image_cache, - "d908fda633352ba5", - "299f188d2a8ccf5d", + "e3c55d333d082bab", + "267615d875c8cf61", "font_fallback", ); }