Skip to content

Commit af420fe

Browse files
committed
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.
1 parent 2f404c3 commit af420fe

File tree

10 files changed

+146
-41
lines changed

10 files changed

+146
-41
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ libc = { version = "0.2.101", optional = true }
123123
seahash = "4.1.0"
124124

125125
[target.'cfg(windows)'.dev-dependencies]
126-
sysinfo = { version = "0.30.0", default-features = false }
126+
winreg = "0.52.0"
127127

128128
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
129129
criterion = "0.5.0"

capi/src/run_editor.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ pub unsafe extern "C" fn RunEditor_active_parse_and_set_comparison_time(
352352
}
353353

354354
/// Adds a new custom comparison. It can't be added if it starts with
355-
/// `[Race]` or already exists.
355+
/// `[Race]` or it already exists.
356356
#[no_mangle]
357357
pub unsafe extern "C" fn RunEditor_add_comparison(
358358
this: &mut RunEditor,
@@ -425,6 +425,18 @@ pub unsafe extern "C" fn RunEditor_parse_and_generate_goal_comparison(
425425
this.parse_and_generate_goal_comparison(str(time)).is_ok()
426426
}
427427

428+
/// Copies a comparison with the given name as a new custom comparison with the
429+
/// new name provided. It can't be added if it starts with `[Race]` or it
430+
/// already exists. The old comparison needs to exist.
431+
#[no_mangle]
432+
pub unsafe extern "C" fn RunEditor_copy_comparison(
433+
this: &mut RunEditor,
434+
old_name: *const c_char,
435+
new_name: *const c_char,
436+
) -> bool {
437+
this.copy_comparison(str(old_name), str(new_name)).is_ok()
438+
}
439+
428440
/// Clears out the Attempt History and the Segment Histories of all the
429441
/// segments.
430442
#[no_mangle]

src/comparison/latest_run.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub const NAME: &str = "Latest Run";
2727

2828
fn generate(segments: &mut [Segment], method: TimingMethod) {
2929
let mut attempt_id = None;
30-
for segment in segments.iter_mut().rev() {
30+
for segment in segments.iter().rev() {
3131
if let Some(max_index) = segment.segment_history().try_get_max_index() {
3232
attempt_id = Some(max_index);
3333
break;

src/run/editor/mod.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! current state of the editor as state objects that can be visualized by any
55
//! kind of User Interface.
66
7-
use super::{ComparisonError, ComparisonResult, LinkedLayout};
7+
use super::{AddComparisonError, CopyComparisonError, LinkedLayout};
88
use crate::{
99
comparison,
1010
platform::prelude::*,
@@ -63,7 +63,7 @@ pub enum RenameError {
6363
/// Name was invalid.
6464
InvalidName {
6565
/// The underlying error.
66-
source: ComparisonError,
66+
source: AddComparisonError,
6767
},
6868
}
6969

@@ -743,8 +743,11 @@ impl Editor {
743743
}
744744

745745
/// Adds a new custom comparison. It can't be added if it starts with
746-
/// `[Race]` or already exists.
747-
pub fn add_comparison<S: PopulateString>(&mut self, comparison: S) -> ComparisonResult<()> {
746+
/// `[Race]` or it already exists.
747+
pub fn add_comparison<S: PopulateString>(
748+
&mut self,
749+
comparison: S,
750+
) -> Result<(), AddComparisonError> {
748751
self.run.add_custom_comparison(comparison)?;
749752
self.fix();
750753
Ok(())
@@ -753,7 +756,11 @@ impl Editor {
753756
/// Imports the Personal Best from the provided run as a comparison. The
754757
/// comparison can't be added if its name starts with `[Race]` or it already
755758
/// exists.
756-
pub fn import_comparison(&mut self, run: &Run, comparison: &str) -> ComparisonResult<()> {
759+
pub fn import_comparison(
760+
&mut self,
761+
run: &Run,
762+
comparison: &str,
763+
) -> Result<(), AddComparisonError> {
757764
self.run.add_custom_comparison(comparison)?;
758765

759766
let mut remaining_segments = self.run.segments_mut().as_mut_slice();
@@ -908,6 +915,31 @@ impl Editor {
908915
Ok(())
909916
}
910917

918+
/// Copies a comparison with the given name as a new custom comparison with
919+
/// the new name provided. It can't be added if it starts with `[Race]` or
920+
/// it already exists. The old comparison needs to exist.
921+
pub fn copy_comparison(
922+
&mut self,
923+
old_name: &str,
924+
new_name: &str,
925+
) -> Result<(), CopyComparisonError> {
926+
if !self.run.comparisons().any(|c| c == old_name) {
927+
return Err(CopyComparisonError::NoSuchComparison);
928+
}
929+
930+
self.run
931+
.add_custom_comparison(new_name)
932+
.map_err(|source| CopyComparisonError::AddComparison { source })?;
933+
934+
for segment in self.run.segments_mut() {
935+
*segment.comparison_mut(new_name) = segment.comparison(old_name);
936+
}
937+
938+
self.raise_run_edited();
939+
940+
Ok(())
941+
}
942+
911943
/// Clears out the Attempt History and the Segment Histories of all the
912944
/// segments.
913945
pub fn clear_history(&mut self) {

src/run/editor/tests/comparison.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
use crate::run::{ComparisonError, Editor, RenameError};
2-
use crate::{Run, Segment};
1+
use crate::{
2+
comparison::{best_segments, personal_best},
3+
run::{AddComparisonError, CopyComparisonError, Editor, RenameError},
4+
Run, Segment,
5+
};
36

47
#[test]
58
fn adding_a_new_comparison_works() {
@@ -15,8 +18,54 @@ fn adding_a_duplicate_fails() {
1518
let mut run = Run::new();
1619
run.push_segment(Segment::new("s"));
1720
let mut editor = Editor::new(run).unwrap();
18-
let c = editor.add_comparison("Best Segments");
19-
assert_eq!(c, Err(ComparisonError::DuplicateName));
21+
let c = editor.add_comparison(best_segments::NAME);
22+
assert_eq!(c, Err(AddComparisonError::DuplicateName));
23+
}
24+
25+
#[test]
26+
fn copying_a_comparison_works() {
27+
let mut run = Run::new();
28+
run.push_segment(Segment::new("s"));
29+
let mut editor = Editor::new(run).unwrap();
30+
let c = editor.copy_comparison(personal_best::NAME, "My Comparison");
31+
assert_eq!(c, Ok(()));
32+
}
33+
34+
#[test]
35+
fn copying_a_duplicate_fails() {
36+
let mut run = Run::new();
37+
run.push_segment(Segment::new("s"));
38+
let mut editor = Editor::new(run).unwrap();
39+
let c = editor.copy_comparison(personal_best::NAME, best_segments::NAME);
40+
assert_eq!(
41+
c,
42+
Err(CopyComparisonError::AddComparison {
43+
source: AddComparisonError::DuplicateName,
44+
})
45+
);
46+
}
47+
48+
#[test]
49+
fn copying_to_a_race_name_fails() {
50+
let mut run = Run::new();
51+
run.push_segment(Segment::new("s"));
52+
let mut editor = Editor::new(run).unwrap();
53+
let c = editor.copy_comparison(personal_best::NAME, "[Race]Custom");
54+
assert_eq!(
55+
c,
56+
Err(CopyComparisonError::AddComparison {
57+
source: AddComparisonError::NameStartsWithRace,
58+
})
59+
);
60+
}
61+
62+
#[test]
63+
fn copying_an_inexistent_comparison_fails() {
64+
let mut run = Run::new();
65+
run.push_segment(Segment::new("s"));
66+
let mut editor = Editor::new(run).unwrap();
67+
let c = editor.copy_comparison("My Comparison", "My Comparison 2");
68+
assert_eq!(c, Err(CopyComparisonError::NoSuchComparison));
2069
}
2170

2271
#[test]
@@ -49,7 +98,7 @@ fn renaming_to_a_race_name_fails() {
4998
assert_eq!(
5099
c,
51100
Err(RenameError::InvalidName {
52-
source: ComparisonError::NameStartsWithRace
101+
source: AddComparisonError::NameStartsWithRace
53102
})
54103
);
55104
}
@@ -65,7 +114,7 @@ fn renaming_to_an_existing_name_fails() {
65114
assert_eq!(
66115
c,
67116
Err(RenameError::InvalidName {
68-
source: ComparisonError::DuplicateName
117+
source: AddComparisonError::DuplicateName
69118
})
70119
);
71120
}

src/run/mod.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,26 @@ impl PartialEq for ComparisonGenerators {
9090
}
9191
}
9292

93-
/// Error type for an invalid comparison name
93+
/// Error type for an invalid comparison name to be added.
9494
#[derive(PartialEq, Eq, Debug, snafu::Snafu)]
95-
pub enum ComparisonError {
95+
pub enum AddComparisonError {
9696
/// Comparison name starts with "\[Race\]".
9797
NameStartsWithRace,
9898
/// Comparison name is a duplicate.
9999
DuplicateName,
100100
}
101101

102-
/// Result type for an invalid comparison name
103-
pub type ComparisonResult<T> = Result<T, ComparisonError>;
102+
/// Error type for copying a comparison.
103+
#[derive(PartialEq, Eq, Debug, snafu::Snafu)]
104+
pub enum CopyComparisonError {
105+
/// There is no comparison with the provided name to copy.
106+
NoSuchComparison,
107+
/// The new comparison could not be added.
108+
AddComparison {
109+
/// The underlying error.
110+
source: AddComparisonError,
111+
},
112+
}
104113

105114
impl Run {
106115
/// Creates a new Run object with no segments.
@@ -443,7 +452,7 @@ impl Run {
443452
/// Adds a new custom comparison. If a custom comparison with that name
444453
/// already exists, it is not added.
445454
#[inline]
446-
pub fn add_custom_comparison<S>(&mut self, comparison: S) -> ComparisonResult<()>
455+
pub fn add_custom_comparison<S>(&mut self, comparison: S) -> Result<(), AddComparisonError>
447456
where
448457
S: PopulateString,
449458
{
@@ -756,11 +765,11 @@ impl Run {
756765

757766
/// Checks a given name against the current comparisons in the Run to
758767
/// ensure that it is valid for use.
759-
pub fn validate_comparison_name(&self, new: &str) -> ComparisonResult<()> {
768+
pub fn validate_comparison_name(&self, new: &str) -> Result<(), AddComparisonError> {
760769
if new.starts_with(RACE_COMPARISON_PREFIX) {
761-
Err(ComparisonError::NameStartsWithRace)
770+
Err(AddComparisonError::NameStartsWithRace)
762771
} else if self.comparisons().any(|c| c == new) {
763-
Err(ComparisonError::DuplicateName)
772+
Err(AddComparisonError::DuplicateName)
764773
} else {
765774
Ok(())
766775
}

src/run/parser/livesplit.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
platform::prelude::*,
5-
run::{ComparisonError, LinkedLayout},
5+
run::{AddComparisonError, LinkedLayout},
66
settings::Image,
77
util::{
88
ascii_char::AsciiChar,
@@ -52,7 +52,7 @@ pub enum Error {
5252
/// Parsed comparison has an invalid name.
5353
InvalidComparisonName {
5454
/// The underlying error.
55-
source: ComparisonError,
55+
source: AddComparisonError,
5656
},
5757
/// Failed to parse a boolean.
5858
ParseBool,
@@ -82,8 +82,8 @@ impl From<crate::timing::ParseError> for Error {
8282
}
8383
}
8484

85-
impl From<ComparisonError> for Error {
86-
fn from(source: ComparisonError) -> Self {
85+
impl From<AddComparisonError> for Error {
86+
fn from(source: AddComparisonError) -> Self {
8787
Self::InvalidComparisonName { source }
8888
}
8989
}
@@ -295,10 +295,10 @@ fn parse_segment(
295295
} else {
296296
time_old(reader, |t| *segment.comparison_mut(&comparison) = t)?;
297297
}
298-
if let Err(ComparisonError::NameStartsWithRace) =
298+
if let Err(AddComparisonError::NameStartsWithRace) =
299299
run.add_custom_comparison(comparison)
300300
{
301-
return Err(ComparisonError::NameStartsWithRace.into());
301+
return Err(AddComparisonError::NameStartsWithRace.into());
302302
}
303303
Ok(())
304304
} else {

src/run/parser/time_split_tracker.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Provides the parser for Time Split Tracker splits files.
22
3-
use super::super::ComparisonError;
3+
use super::super::AddComparisonError;
44
use crate::{
55
comparison::RACE_COMPARISON_PREFIX,
66
platform::{path::Path, prelude::*},
@@ -124,13 +124,13 @@ pub fn parse(
124124
loop {
125125
match run.add_custom_comparison(&**comparison) {
126126
Ok(_) => break,
127-
Err(ComparisonError::DuplicateName) => {
127+
Err(AddComparisonError::DuplicateName) => {
128128
let comparison = comparison.to_mut();
129129
comparison.drain(orig_len..);
130130
let _ = write!(comparison, " {number}");
131131
number += 1;
132132
}
133-
Err(ComparisonError::NameStartsWithRace) => {
133+
Err(AddComparisonError::NameStartsWithRace) => {
134134
let comparison = comparison.to_mut();
135135
// After removing the `[Race]`, there might be some
136136
// whitespace we want to trim too.

src/run/tests/comparison.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::run::{ComparisonError, Run};
1+
use crate::run::{AddComparisonError, Run};
22

33
#[test]
44
fn adding_a_new_comparison_works() {
@@ -11,5 +11,5 @@ fn adding_a_new_comparison_works() {
1111
fn adding_a_duplicate_fails() {
1212
let mut run = Run::new();
1313
let c = run.add_custom_comparison("Best Segments");
14-
assert_eq!(c, Err(ComparisonError::DuplicateName));
14+
assert_eq!(c, Err(AddComparisonError::DuplicateName));
1515
}

tests/rendering.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,16 @@ fn default() {
6060
#[cfg(all(feature = "font-loading", windows))]
6161
#[test]
6262
fn font_fallback() {
63-
let build_number: u64 = sysinfo::System::kernel_version().unwrap().parse().unwrap();
64-
65-
if build_number < 22000 {
66-
// The hash is different before Windows 11.
63+
let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
64+
let cur_ver = hklm.open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion").unwrap();
65+
let build_number: String = cur_ver.get_value("CurrentBuildNumber").unwrap();
66+
let build_number: u64 = build_number.parse().unwrap();
67+
let revision: u32 = cur_ver.get_value("UBR").unwrap();
68+
69+
if (build_number, revision) < (22631, 3672) {
70+
// The hash is different before Windows 11 23H2 (end of May 2024 update).
6771
println!(
68-
"Skipping font fallback test on Windows with build number {}.",
69-
build_number
72+
"Skipping font fallback test on Windows with build number {build_number}.{revision}.",
7073
);
7174
return;
7275
}
@@ -122,8 +125,8 @@ fn font_fallback() {
122125
check(
123126
&state,
124127
&image_cache,
125-
"d908fda633352ba5",
126-
"299f188d2a8ccf5d",
128+
"e3c55d333d082bab",
129+
"267615d875c8cf61",
127130
"font_fallback",
128131
);
129132
}

0 commit comments

Comments
 (0)