1mod attempt;
18mod comparisons;
19pub mod editor;
20pub mod parser;
21mod run_metadata;
22pub mod saver;
23mod segment;
24mod segment_history;
25
26#[cfg(test)]
27mod tests;
28
29pub use attempt::Attempt;
30pub use comparisons::Comparisons;
31pub use editor::{Editor, RenameError};
32pub use run_metadata::{CustomVariable, RunMetadata};
33pub use segment::Segment;
34pub use segment_history::SegmentHistory;
35
36use crate::{
37 comparison::{default_generators, personal_best, ComparisonGenerator, RACE_COMPARISON_PREFIX},
38 platform::prelude::*,
39 settings::Image,
40 util::PopulateString,
41 AtomicDateTime, Time, TimeSpan, TimingMethod,
42};
43use alloc::borrow::Cow;
44use core::{cmp::max, fmt};
45use hashbrown::HashSet;
46
47#[derive(Clone, Debug, PartialEq)]
63pub struct Run {
64 game_icon: Image,
65 game_name: String,
66 category_name: String,
67 offset: TimeSpan,
68 attempt_count: u32,
69 attempt_history: Vec<Attempt>,
70 metadata: RunMetadata,
71 has_been_modified: bool,
72 segments: Vec<Segment>,
73 custom_comparisons: Vec<String>,
74 comparison_generators: ComparisonGenerators,
75 auto_splitter_settings: String,
76}
77
78#[derive(Clone, Debug)]
79struct ComparisonGenerators(Vec<Box<dyn ComparisonGenerator>>);
80
81impl PartialEq for ComparisonGenerators {
82 fn eq(&self, other: &ComparisonGenerators) -> bool {
83 self.0
84 .iter()
85 .map(|c| c.name())
86 .eq(other.0.iter().map(|c| c.name()))
87 }
88}
89
90#[derive(PartialEq, Eq, Debug, snafu::Snafu)]
92pub enum ComparisonError {
93 NameStartsWithRace,
95 DuplicateName,
97}
98
99pub type ComparisonResult<T> = Result<T, ComparisonError>;
101
102impl Run {
103 #[inline]
105 pub fn new() -> Self {
106 Self {
107 game_icon: Image::default(),
108 game_name: String::new(),
109 category_name: String::new(),
110 offset: TimeSpan::zero(),
111 attempt_count: 0,
112 attempt_history: Vec::new(),
113 metadata: RunMetadata::new(),
114 has_been_modified: false,
115 segments: Vec::new(),
116 custom_comparisons: vec![personal_best::NAME.to_string()],
117 comparison_generators: ComparisonGenerators(default_generators()),
118 auto_splitter_settings: String::new(),
119 }
120 }
121
122 #[inline]
124 pub fn game_name(&self) -> &str {
125 &self.game_name
126 }
127
128 #[inline]
130 pub fn set_game_name<S>(&mut self, name: S)
131 where
132 S: PopulateString,
133 {
134 name.populate(&mut self.game_name);
135 }
136
137 #[inline]
139 pub const fn game_icon(&self) -> &Image {
140 &self.game_icon
141 }
142
143 #[inline]
145 pub fn set_game_icon<D: Into<Image>>(&mut self, image: D) {
146 self.game_icon = image.into();
147 }
148
149 #[inline]
151 pub fn category_name(&self) -> &str {
152 &self.category_name
153 }
154
155 #[inline]
157 pub fn set_category_name<S>(&mut self, name: S)
158 where
159 S: PopulateString,
160 {
161 name.populate(&mut self.category_name);
162 }
163
164 #[inline]
166 pub const fn attempt_count(&self) -> u32 {
167 self.attempt_count
168 }
169
170 #[inline]
172 pub fn set_attempt_count(&mut self, attempts: u32) {
173 self.attempt_count = attempts;
174 }
175
176 #[inline]
179 pub const fn metadata(&self) -> &RunMetadata {
180 &self.metadata
181 }
182
183 #[inline]
186 pub fn metadata_mut(&mut self) -> &mut RunMetadata {
187 &mut self.metadata
188 }
189
190 #[inline]
192 pub fn set_offset(&mut self, offset: TimeSpan) {
193 self.offset = offset;
194 }
195
196 #[inline]
198 pub const fn offset(&self) -> TimeSpan {
199 self.offset
200 }
201
202 pub fn start_next_run(&mut self) {
205 self.attempt_count += 1;
206 self.has_been_modified = true;
207 }
208
209 #[inline]
211 pub fn segments(&self) -> &[Segment] {
212 &self.segments
213 }
214
215 #[inline]
217 pub fn segments_mut(&mut self) -> &mut Vec<Segment> {
218 &mut self.segments
219 }
220
221 #[inline]
223 pub fn push_segment(&mut self, segment: Segment) {
224 self.segments.push(segment);
225 }
226
227 #[inline]
233 pub fn segment(&self, index: usize) -> &Segment {
234 &self.segments[index]
235 }
236
237 #[inline]
243 pub fn segment_mut(&mut self, index: usize) -> &mut Segment {
244 &mut self.segments[index]
245 }
246
247 #[inline]
252 pub fn attempt_history(&self) -> &[Attempt] {
253 &self.attempt_history
254 }
255
256 #[inline]
260 pub fn custom_comparisons(&self) -> &[String] {
261 &self.custom_comparisons
262 }
263
264 #[inline]
272 pub fn custom_comparisons_mut(&mut self) -> &mut Vec<String> {
273 &mut self.custom_comparisons
274 }
275
276 #[inline]
280 pub fn comparisons(&self) -> ComparisonsIter<'_> {
281 ComparisonsIter {
282 custom: &self.custom_comparisons,
283 generators: &self.comparison_generators.0,
284 }
285 }
286
287 #[inline]
289 pub fn comparison_generators(&self) -> &[Box<dyn ComparisonGenerator>] {
290 &self.comparison_generators.0
291 }
292
293 #[inline]
295 pub fn comparison_generators_mut(&mut self) -> &mut Vec<Box<dyn ComparisonGenerator>> {
296 &mut self.comparison_generators.0
297 }
298
299 #[inline]
301 pub fn auto_splitter_settings(&self) -> &str {
302 &self.auto_splitter_settings
303 }
304
305 #[inline]
312 pub fn auto_splitter_settings_mut(&mut self) -> &mut String {
313 &mut self.auto_splitter_settings
314 }
315
316 #[inline]
318 pub fn len(&self) -> usize {
319 self.segments.len()
320 }
321
322 #[inline]
324 pub fn is_empty(&self) -> bool {
325 self.segments.is_empty()
326 }
327
328 #[inline]
331 pub fn mark_as_modified(&mut self) {
332 self.has_been_modified = true;
333 }
334
335 #[inline]
338 pub fn mark_as_unmodified(&mut self) {
339 self.has_been_modified = false;
340 }
341
342 #[inline]
345 pub const fn has_been_modified(&self) -> bool {
346 self.has_been_modified
347 }
348
349 pub fn add_attempt(
352 &mut self,
353 time: Time,
354 started: Option<AtomicDateTime>,
355 ended: Option<AtomicDateTime>,
356 pause_time: Option<TimeSpan>,
357 ) {
358 let index = self
359 .attempt_history
360 .iter()
361 .map(Attempt::index)
362 .max()
363 .unwrap_or(0);
364 let index = max(0, index + 1);
365 self.add_attempt_with_index(time, index, started, ended, pause_time);
366 }
367
368 pub fn add_attempt_with_index(
376 &mut self,
377 time: Time,
378 index: i32,
379 started: Option<AtomicDateTime>,
380 ended: Option<AtomicDateTime>,
381 pause_time: Option<TimeSpan>,
382 ) {
383 let attempt = Attempt::new(index, time, started, ended, pause_time);
384 self.attempt_history.push(attempt);
385 }
386
387 #[inline]
391 pub fn clear_run_id(&mut self) {
392 self.metadata.set_run_id("");
393 }
394
395 #[inline]
398 pub fn add_custom_comparison<S>(&mut self, comparison: S) -> ComparisonResult<()>
399 where
400 S: PopulateString,
401 {
402 self.validate_comparison_name(comparison.as_str())?;
403 self.custom_comparisons.push(comparison.into_string());
404 Ok(())
405 }
406
407 #[inline]
409 pub fn regenerate_comparisons(&mut self) {
410 for generator in &mut self.comparison_generators.0 {
411 generator.generate(&mut self.segments, &self.attempt_history);
412 }
413 }
414
415 pub fn extended_file_name(&self, use_extended_category_name: bool) -> String {
424 let mut extended_name = self.extended_name(use_extended_category_name).into_owned();
425 extended_name
426 .retain(|c| !matches!(c, '\\' | '/' | ':' | '*' | '?' | '"' | '<' | '>' | '|'));
427 extended_name
428 }
429
430 pub fn extended_name(&self, use_extended_category_name: bool) -> Cow<'_, str> {
437 let mut name = Cow::Borrowed(self.game_name());
438
439 let category_name = if use_extended_category_name {
440 Cow::Owned(self.extended_category_name(false, false, true).to_string())
441 } else {
442 Cow::Borrowed(self.category_name())
443 };
444
445 if !category_name.is_empty() {
446 if !name.is_empty() {
447 let name = name.to_mut();
448 name.push_str(" - ");
449 name.push_str(&category_name);
450 } else {
451 name = category_name;
452 }
453 }
454
455 name
456 }
457
458 pub const fn extended_category_name(
465 &self,
466 show_region: bool,
467 show_platform: bool,
468 show_variables: bool,
469 ) -> ExtendedCategoryName<'_> {
470 ExtendedCategoryName {
471 run: self,
472 show_region,
473 show_platform,
474 show_variables,
475 }
476 }
477
478 pub fn max_attempt_history_index(&self) -> Option<i32> {
481 self.attempt_history().iter().map(Attempt::index).max()
482 }
483
484 pub fn fix_splits(&mut self) {
488 for method in TimingMethod::all() {
489 self.fix_comparison_times_and_history(method);
490 }
491 self.remove_duplicates();
492 self.remove_none_values();
493 self.reattach_unattached_segment_history_elements();
494 }
495
496 pub fn clear_history(&mut self) {
498 self.attempt_history.clear();
499 for segment in &mut self.segments {
500 segment.segment_history_mut().clear();
501 }
502 }
503
504 pub fn clear_times(&mut self) {
509 self.clear_history();
510 self.custom_comparisons.retain(|c| c == personal_best::NAME);
511 for segment in &mut self.segments {
512 segment.comparisons_mut().clear();
513 segment.set_best_segment_time(Time::default());
514 }
515 self.attempt_count = 0;
516 self.clear_run_id();
517 }
518
519 fn fix_comparison_times_and_history(&mut self, method: TimingMethod) {
520 for segment in &mut self.segments {
522 if segment.best_segment_time_mut()[method].map_or(false, |t| t < TimeSpan::zero()) {
523 segment.best_segment_time_mut()[method] = None;
524 }
525 }
526
527 for segment in &mut self.segments {
528 fix_history_from_none_best_segments(segment, method);
529 }
530
531 for comparison in &self.custom_comparisons {
532 let mut previous_time = TimeSpan::zero();
533 for segment in &mut self.segments {
534 if let Some(mut time) = segment.comparison_mut(comparison)[method] {
535 if time < previous_time {
537 time = previous_time;
538 segment.comparison_mut(comparison)[method] = Some(time);
539 }
540
541 if comparison == personal_best::NAME {
543 let current_segment = time - previous_time;
544 if segment.best_segment_time()[method].map_or(true, |t| t > current_segment)
545 {
546 segment.best_segment_time_mut()[method] = Some(current_segment);
547 }
548 }
549
550 previous_time = time;
551 }
552 }
553 }
554
555 for segment in &mut self.segments {
556 fix_history_from_best_segment_times(segment, method);
557 }
558 }
559
560 fn remove_none_values(&mut self) {
561 let mut cache = Vec::new();
562 if let Some(min_index) = self.min_segment_history_index() {
563 let max_index = self.max_attempt_history_index().unwrap_or(0) + 1;
564 for run_index in min_index..max_index {
565 for index in 0..self.len() {
566 if let Some(element) = self.segments[index].segment_history().get(run_index) {
567 if element.real_time.is_none() && element.game_time.is_none() {
568 cache.push(run_index);
569 } else {
570 cache.clear();
571 }
572 } else {
573 self.remove_items_from_cache(index, &mut cache);
575 }
576 }
577 let len = self.len();
578 self.remove_items_from_cache(len, &mut cache);
579 }
580 }
581 }
582
583 fn remove_duplicates(&mut self) {
584 let mut rta_set = HashSet::new();
585 let mut igt_set = HashSet::new();
586
587 for segment in self.segments_mut() {
588 let history = segment.segment_history_mut();
589
590 rta_set.clear();
591 igt_set.clear();
592
593 for &(_, time) in history.iter_actual_runs() {
594 if let Some(time) = time.real_time {
595 rta_set.insert(time);
596 }
597 if let Some(time) = time.game_time {
598 igt_set.insert(time);
599 }
600 }
601
602 history.retain(|&(index, time)| {
603 if index >= 1 {
604 return true;
605 }
606
607 let (mut is_none, mut is_unique) = (true, false);
608 if let Some(time) = time.real_time {
609 is_unique |= rta_set.insert(time);
610 is_none = false;
611 }
612
613 if let Some(time) = time.game_time {
614 is_unique |= igt_set.insert(time);
615 is_none = false;
616 }
617
618 is_none || is_unique
619 });
620 }
621 }
622
623 fn remove_items_from_cache(&mut self, index: usize, cache: &mut Vec<i32>) {
624 let ind = index - cache.len();
625 for (index, segment) in cache.drain(..).zip(self.segments_mut()[ind..].iter_mut()) {
626 segment.segment_history_mut().remove(index);
627 }
628 }
629
630 pub fn min_segment_history_index(&self) -> Option<i32> {
633 self.segments
634 .iter()
635 .map(|s| s.segment_history().min_index())
636 .min()
637 }
638
639 pub fn import_pb_into_segment_history(&mut self) {
642 if let Some(mut index) = self.min_segment_history_index() {
643 for timing_method in TimingMethod::all() {
644 index -= 1;
645 let mut prev_time = TimeSpan::zero();
646
647 for segment in self.segments_mut() {
648 let pb_time = segment.personal_best_split_time()[timing_method];
650 let time = Time::new()
651 .with_timing_method(timing_method, pb_time.map(|p| p - prev_time));
652 segment.segment_history_mut().insert(index, time);
653
654 if let Some(time) = pb_time {
655 prev_time = time;
656 }
657 }
658 }
659 }
660 }
661
662 pub fn import_best_segment(&mut self, segment_index: usize) {
669 let best_segment_time = self.segments[segment_index].best_segment_time();
670 if best_segment_time.real_time.is_some() || best_segment_time.game_time.is_some() {
671 let index = self.min_segment_history_index().unwrap() - 1;
674 self.segments[segment_index]
675 .segment_history_mut()
676 .insert(index, best_segment_time);
677 }
678 }
679
680 pub fn update_segment_history(&mut self, current_split_index: usize) {
687 let mut last_split_time = Time::zero();
688
689 let segments = self.segments.iter_mut().take(current_split_index);
690 let index = self
691 .attempt_history
692 .last()
693 .expect("There is no attempt in the Attempt History.")
694 .index();
695
696 for segment in segments {
697 let split_time = segment.split_time();
698 let segment_time = Time::op(split_time, last_split_time, |a, b| a - b);
699 segment.segment_history_mut().insert(index, segment_time);
700 if let Some(time) = split_time.real_time {
701 last_split_time.real_time = Some(time);
702 }
703 if let Some(time) = split_time.game_time {
704 last_split_time.game_time = Some(time);
705 }
706 }
707 }
708
709 pub fn validate_comparison_name(&self, new: &str) -> ComparisonResult<()> {
712 if new.starts_with(RACE_COMPARISON_PREFIX) {
713 Err(ComparisonError::NameStartsWithRace)
714 } else if self.comparisons().any(|c| c == new) {
715 Err(ComparisonError::DuplicateName)
716 } else {
717 Ok(())
718 }
719 }
720
721 fn reattach_unattached_segment_history_elements(&mut self) {
722 let max_id = self.max_attempt_history_index().unwrap_or_default();
723 let mut min_id = self.min_segment_history_index().unwrap_or_default();
724
725 while let Some(unattached_id) = self
726 .segments
727 .iter()
728 .filter_map(|s| s.segment_history().try_get_max_index())
729 .filter(|&i| i > max_id)
730 .max()
731 {
732 let reassign_id = min_id - 1;
733
734 for segment in self.segments_mut() {
735 let history = segment.segment_history_mut();
736 if let Some(time) = history.remove(unattached_id) {
737 history.insert(reassign_id, time);
738 }
739 }
740
741 min_id = reassign_id;
742 }
743 }
744}
745
746impl Default for Run {
747 fn default() -> Self {
748 Run::new()
749 }
750}
751
752fn fix_history_from_none_best_segments(segment: &mut Segment, method: TimingMethod) {
753 if segment.best_segment_time()[method].is_none() {
755 segment
757 .segment_history_mut()
758 .retain(|&(_, time)| time[method].is_none());
759 }
760}
761
762fn fix_history_from_best_segment_times(segment: &mut Segment, method: TimingMethod) {
763 if let Some(best_segment) = segment.best_segment_time()[method] {
764 for (_, time) in segment.segment_history_mut().iter_mut() {
765 if let Some(time) = &mut time[method] {
767 if *time < best_segment {
768 *time = best_segment;
769 }
770 }
771 }
772 }
773}
774
775pub struct ComparisonsIter<'a> {
778 custom: &'a [String],
779 generators: &'a [Box<dyn ComparisonGenerator>],
780}
781
782impl<'a> Iterator for ComparisonsIter<'a> {
783 type Item = &'a str;
784
785 fn next(&mut self) -> Option<&'a str> {
786 if let Some((a, b)) = self.custom.split_first() {
787 self.custom = b;
788 Some(a)
789 } else if let Some((a, b)) = self.generators.split_first() {
790 self.generators = b;
791 Some(a.name())
792 } else {
793 None
794 }
795 }
796
797 fn size_hint(&self) -> (usize, Option<usize>) {
798 let len = self.custom.len() + self.generators.len();
799 (len, Some(len))
800 }
801}
802
803impl ExactSizeIterator for ComparisonsIter<'_> {}
804
805pub struct ExtendedCategoryName<'run> {
812 run: &'run Run,
813 show_region: bool,
814 show_platform: bool,
815 show_variables: bool,
816}
817
818impl fmt::Display for ExtendedCategoryName<'_> {
819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820 let category_name = self.run.category_name.as_str();
821
822 if category_name.is_empty() {
823 return Ok(());
824 }
825
826 let mut is_empty = true;
827 let mut has_pushed = false;
828
829 let (before, after_parenthesis) = if let Some((i, u)) = self
830 .run
831 .category_name
832 .find('(')
833 .and_then(|i| category_name[i..].find(')').map(|u| (i, i + u)))
834 {
835 is_empty = u == i + 1;
836 category_name.split_at(u)
837 } else {
838 (category_name, "")
839 };
840 f.write_str(before)?;
841
842 let mut push = |values: &[&str]| {
843 if is_empty {
844 if !has_pushed {
845 f.write_str(" (")?;
846 }
847 is_empty = false;
848 } else {
849 f.write_str(", ")?;
850 }
851 for value in values {
852 f.write_str(value)?;
853 }
854 has_pushed = true;
855 Ok(())
856 };
857
858 if self.show_variables {
859 for (name, value) in self.run.metadata.speedrun_com_variables() {
860 let name = name.trim_end_matches('?');
861
862 if unicase::eq(value.as_str(), "yes") {
863 push(&[name])?;
864 } else if unicase::eq(value.as_str(), "no") {
865 push(&["No ", value])?;
866 } else {
867 push(&[value])?;
868 }
869 }
870 }
871
872 if self.show_region {
873 let region = self.run.metadata.region_name();
874 if !region.is_empty() {
875 push(&[region])?;
876 }
877 }
878
879 if self.show_platform {
880 let platform = self.run.metadata.platform_name();
881 let uses_emulator = self.run.metadata.uses_emulator();
882
883 match (!platform.is_empty(), uses_emulator) {
884 (true, true) => push(&[platform, " Emulator"])?,
885 (true, false) => push(&[platform])?,
886 (false, true) => push(&["Emulator"])?,
887 _ => (),
888 }
889 }
890
891 if !after_parenthesis.is_empty() {
892 f.write_str(after_parenthesis)?;
893 } else if !is_empty {
894 f.write_str(")")?;
895 }
896
897 Ok(())
898 }
899}