Skip to content

Commit 5be35be

Browse files
authored
Allow copying comparisons (#812)
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 5be35be

File tree

10 files changed

+147
-40
lines changed

10 files changed

+147
-40
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: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,18 @@ 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();
63+
let hklm = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
64+
let cur_ver = hklm
65+
.open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion")
66+
.unwrap();
67+
let build_number: String = cur_ver.get_value("CurrentBuildNumber").unwrap();
68+
let build_number: u64 = build_number.parse().unwrap();
69+
let revision: u32 = cur_ver.get_value("UBR").unwrap();
6470

65-
if build_number < 22000 {
66-
// The hash is different before Windows 11.
71+
if (build_number, revision) < (22631, 3672) {
72+
// The hash is different before Windows 11 23H2 (end of May 2024 update).
6773
println!(
68-
"Skipping font fallback test on Windows with build number {}.",
69-
build_number
74+
"Skipping font fallback test on Windows with build number {build_number}.{revision}.",
7075
);
7176
return;
7277
}
@@ -122,8 +127,8 @@ fn font_fallback() {
122127
check(
123128
&state,
124129
&image_cache,
125-
"d908fda633352ba5",
126-
"299f188d2a8ccf5d",
130+
"e3c55d333d082bab",
131+
"267615d875c8cf61",
127132
"font_fallback",
128133
);
129134
}

0 commit comments

Comments
 (0)