diff --git a/src/run/mod.rs b/src/run/mod.rs index 43af5d34..6c2cc59b 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -782,10 +782,10 @@ impl Run { /// # Panics /// /// This panics if there is no attempt in the Attempt History. - pub fn update_segment_history(&mut self, segments_count: usize) { + pub fn update_segment_history(&mut self, current_split_index: usize) { let mut previous_split_time = Time::zero(); - let segments = &mut self.segments[..segments_count]; + let segments = self.segments.iter_mut().take(current_split_index); let index = self .attempt_history .last() diff --git a/src/timing/timer/active_attempt.rs b/src/timing/timer/active_attempt.rs deleted file mode 100644 index b2cfa87f..00000000 --- a/src/timing/timer/active_attempt.rs +++ /dev/null @@ -1,229 +0,0 @@ -use crate::{AtomicDateTime, Run, Time, TimeSpan, TimeStamp, TimingMethod}; - -#[derive(Debug, Clone)] -pub struct ActiveAttempt { - pub state: State, - pub attempt_started: AtomicDateTime, - pub start_time: TimeStamp, - pub start_time_with_offset: TimeStamp, - // This gets adjusted after resuming - pub adjusted_start_time: TimeStamp, - pub game_time_paused_at: Option, - pub loading_times: Option, -} - -#[derive(Debug, Clone)] -pub enum State { - NotEnded { - current_split_index: usize, - time_paused_at: Option, - }, - Ended { - attempt_ended: AtomicDateTime, - }, -} - -pub struct TimerTime { - pub real_time: TimeSpan, - pub game_time: Option, -} - -impl From for Time { - fn from(time: TimerTime) -> Self { - Time { - real_time: Some(time.real_time), - game_time: time.game_time, - } - } -} - -impl ActiveAttempt { - pub fn current_time(&self, run: &Run) -> TimerTime { - let real_time = match self.state { - State::Ended { .. } => { - let Time { - real_time, - game_time, - } = run.segments().last().unwrap().split_time(); - - return TimerTime { - real_time: real_time.unwrap_or_default(), - game_time, - }; - } - State::NotEnded { time_paused_at, .. } => { - time_paused_at.unwrap_or_else(|| TimeStamp::now() - self.adjusted_start_time) - } - }; - - let game_time = self - .game_time_paused_at - .or_else(|| Some(real_time - self.loading_times?)); - - TimerTime { - real_time, - game_time, - } - } - - pub fn get_pause_time(&self) -> Option { - if let State::NotEnded { - time_paused_at: Some(pause_time), - .. - } = self.state - { - return Some(TimeStamp::now() - self.start_time_with_offset - pause_time); - } - - if self.start_time_with_offset != self.adjusted_start_time { - Some(self.start_time_with_offset - self.adjusted_start_time) - } else { - None - } - } - - pub fn set_loading_times(&mut self, time: TimeSpan, run: &Run) { - self.loading_times = Some(time); - if self.game_time_paused_at.is_some() { - self.game_time_paused_at = Some(self.current_time(run).real_time - time); - } - } - - pub fn prepare_split(&mut self, run: &Run) -> Option<(usize, Time)> { - let State::NotEnded { - current_split_index, - time_paused_at: None, - } = &mut self.state - else { - return None; - }; - - let real_time = TimeStamp::now() - self.adjusted_start_time; - - if real_time < TimeSpan::zero() { - return None; - } - - let game_time = self - .game_time_paused_at - .or_else(|| Some(real_time - self.loading_times?)); - - let previous_split_index = *current_split_index; - *current_split_index += 1; - - if *current_split_index == run.len() { - self.state = State::Ended { - attempt_ended: AtomicDateTime::now(), - }; - } - - Some(( - previous_split_index, - Time { - real_time: Some(real_time), - game_time, - }, - )) - } - - pub const fn current_split_index(&self) -> Option { - match self.state { - State::NotEnded { - current_split_index, - .. - } => Some(current_split_index), - State::Ended { .. } => None, - } - } - - pub fn current_split_index_mut(&mut self) -> Option<&mut usize> { - match &mut self.state { - State::NotEnded { - current_split_index, - .. - } => Some(current_split_index), - State::Ended { .. } => None, - } - } - - pub fn current_split_index_overflowing(&self, run: &Run) -> usize { - match self.state { - State::NotEnded { - current_split_index, - .. - } => current_split_index, - State::Ended { .. } => run.len(), - } - } - - pub fn update_times(&self, run: &mut Run, timing_method: TimingMethod) { - self.update_attempt_history(run); - update_best_segments(run); - update_pb_splits(run, timing_method); - run.update_segment_history(self.current_split_index_overflowing(run)); - } - - pub fn update_attempt_history(&self, run: &mut Run) { - let (attempt_ended, time) = match self.state { - State::NotEnded { .. } => (AtomicDateTime::now(), Time::new()), - State::Ended { attempt_ended } => { - (attempt_ended, run.segments().last().unwrap().split_time()) - } - }; - - let pause_time = self.get_pause_time(); - - run.add_attempt( - time, - Some(self.attempt_started), - Some(attempt_ended), - pause_time, - ); - } -} - -fn update_best_segments(run: &mut Run) { - let mut previous_split_time_rta = Some(TimeSpan::zero()); - let mut previous_split_time_game_time = Some(TimeSpan::zero()); - - for split in run.segments_mut() { - let mut new_best_segment = split.best_segment_time(); - if let Some(split_time) = split.split_time().real_time { - let current_segment = previous_split_time_rta.map(|previous| split_time - previous); - previous_split_time_rta = Some(split_time); - if split - .best_segment_time() - .real_time - .map_or(true, |b| current_segment.is_some_and(|c| c < b)) - { - new_best_segment.real_time = current_segment; - } - } - if let Some(split_time) = split.split_time().game_time { - let current_segment = - previous_split_time_game_time.map(|previous| split_time - previous); - previous_split_time_game_time = Some(split_time); - if split - .best_segment_time() - .game_time - .map_or(true, |b| current_segment.is_some_and(|c| c < b)) - { - new_best_segment.game_time = current_segment; - } - } - split.set_best_segment_time(new_best_segment); - } -} - -fn update_pb_splits(run: &mut Run, method: TimingMethod) { - let (split_time, pb_split_time) = { - let last_segment = run.segments().last().unwrap(); - ( - last_segment.split_time()[method], - last_segment.personal_best_split_time()[method], - ) - }; - if split_time.is_some_and(|s| pb_split_time.map_or(true, |pb| s < pb)) { - super::set_run_as_pb(run); - } -} diff --git a/src/timing/timer/mod.rs b/src/timing/timer/mod.rs index 0b52a49d..ecb1feed 100644 --- a/src/timing/timer/mod.rs +++ b/src/timing/timer/mod.rs @@ -8,9 +8,6 @@ use core::{mem, ops::Deref}; #[cfg(test)] mod tests; -mod active_attempt; -use active_attempt::{ActiveAttempt, State}; - /// A `Timer` provides all the capabilities necessary for doing speedrun attempts. /// /// # Examples @@ -46,9 +43,20 @@ use active_attempt::{ActiveAttempt, State}; #[derive(Debug, Clone)] pub struct Timer { run: Run, - current_comparison: String, + phase: TimerPhase, + current_split_index: Option, current_timing_method: TimingMethod, - active_attempt: Option, + current_comparison: String, + attempt_started: Option, + attempt_ended: Option, + start_time: TimeStamp, + start_time_with_offset: TimeStamp, + // This gets adjusted after resuming + adjusted_start_time: TimeStamp, + time_paused_at: TimeSpan, + is_game_time_paused: bool, + game_time_pause_time: Option, + loading_times: Option, } /// A snapshot represents a specific point in time that the timer was observed @@ -98,12 +106,23 @@ impl Timer { run.fix_splits(); run.regenerate_comparisons(); + let now = TimeStamp::now(); Ok(Timer { run, - current_comparison: personal_best::NAME.into(), + phase: NotRunning, + current_split_index: None, current_timing_method: TimingMethod::RealTime, - active_attempt: None, + current_comparison: personal_best::NAME.into(), + attempt_started: None, + attempt_ended: None, + start_time: now, + start_time_with_offset: now, + adjusted_start_time: now, + time_paused_at: TimeSpan::zero(), + is_game_time_paused: false, + game_time_pause_time: None, + loading_times: None, }) } @@ -180,19 +199,34 @@ impl Timer { /// Returns the current Timer Phase. #[inline] pub const fn current_phase(&self) -> TimerPhase { - let Some(active_attempt) = &self.active_attempt else { - return TimerPhase::NotRunning; + self.phase + } + + fn current_time(&self) -> Time { + let real_time = match self.phase { + NotRunning => Some(self.run.offset()), + Running => Some(TimeStamp::now() - self.adjusted_start_time), + Paused => Some(self.time_paused_at), + Ended => self.run.segments().last().unwrap().split_time().real_time, }; - match active_attempt.state { - State::NotEnded { time_paused_at, .. } => { - if time_paused_at.is_some() { - Paused + + let game_time = match self.phase { + NotRunning => Some(self.run.offset()), + Ended => self.run.segments().last().unwrap().split_time().game_time, + _ => { + if self.is_game_time_paused() { + self.game_time_pause_time + } else if self.is_game_time_initialized() { + catch! { real_time? - self.loading_times() } } else { - Running + None } } - State::Ended { .. } => Ended, - } + }; + + Time::new() + .with_real_time(real_time) + .with_game_time(game_time) } /// Creates a new snapshot of the timer at the point in time of this call. @@ -200,18 +234,10 @@ impl Timer { /// work with an entirely consistent view of the timer without the current /// time changing underneath. pub fn snapshot(&self) -> Snapshot<'_> { - let time = match &self.active_attempt { - Some(active_attempt) => active_attempt.current_time(&self.run).into(), - None => { - let offset = Some(self.run.offset()); - Time { - real_time: offset, - game_time: offset, - } - } - }; - - Snapshot { timer: self, time } + Snapshot { + timer: self, + time: self.current_time(), + } } /// Returns the currently selected Timing Method. @@ -258,10 +284,13 @@ impl Timer { /// Accesses the split the attempt is currently on. If there's no attempt in /// progress or the run finished, `None` is returned instead. pub fn current_split(&self) -> Option<&Segment> { - self.active_attempt - .as_ref()? - .current_split_index() - .map(|i| self.run.segment(i)) + self.current_split_index + .and_then(|i| self.run.segments().get(i)) + } + + fn current_split_mut(&mut self) -> Option<&mut Segment> { + self.current_split_index + .and_then(move |i| self.run.segments_mut().get_mut(i)) } /// Accesses the index of the split the attempt is currently on. If there's @@ -270,34 +299,22 @@ impl Timer { /// finished, but has not been reset. So you need to be careful when using /// this value for indexing. #[inline] - pub fn current_split_index(&self) -> Option { - Some( - self.active_attempt - .as_ref()? - .current_split_index_overflowing(&self.run), - ) + pub const fn current_split_index(&self) -> Option { + self.current_split_index } /// Starts the Timer if there is no attempt in progress. If that's not the /// case, nothing happens. pub fn start(&mut self) { - if self.active_attempt.is_none() { - let attempt_started = AtomicDateTime::now(); - let start_time = TimeStamp::now(); - let start_time_with_offset = start_time - self.run.offset(); - - self.active_attempt = Some(ActiveAttempt { - state: State::NotEnded { - current_split_index: 0, - time_paused_at: None, - }, - attempt_started, - start_time, - start_time_with_offset, - adjusted_start_time: start_time_with_offset, - game_time_paused_at: None, - loading_times: None, - }); + if self.phase == NotRunning { + self.phase = Running; + self.current_split_index = Some(0); + self.attempt_started = Some(AtomicDateTime::now()); + self.start_time = TimeStamp::now(); + self.start_time_with_offset = self.start_time - self.run.offset(); + self.adjusted_start_time = self.start_time_with_offset; + self.time_paused_at = self.run.offset(); + self.deinitialize_game_time(); self.run.start_next_run(); } } @@ -305,33 +322,37 @@ impl Timer { /// If an attempt is in progress, stores the current time as the time of the /// current split. The attempt ends if the last split time is stored. pub fn split(&mut self) { - let Some(active_attempt) = &mut self.active_attempt else { - return; - }; - - let Some((split_index, current_time)) = active_attempt.prepare_split(&self.run) else { - return; - }; - - // FIXME: We shouldn't need to collect here. - let variables = self - .run - .metadata() - .custom_variables() - .map(|(k, v)| (k.to_owned(), v.value.clone())) - .collect(); - - let segment = self.run.segment_mut(split_index); - segment.set_split_time(current_time); - *segment.variables_mut() = variables; - - self.run.mark_as_modified(); + let current_time = self.current_time(); + if self.phase == Running + && current_time + .real_time + .is_some_and(|t| t >= TimeSpan::zero()) + { + // FIXME: We shouldn't need to collect here. + let variables = self + .run + .metadata() + .custom_variables() + .map(|(k, v)| (k.to_owned(), v.value.clone())) + .collect(); + let segment = self.current_split_mut().unwrap(); + + segment.set_split_time(current_time); + *segment.variables_mut() = variables; + + *self.current_split_index.as_mut().unwrap() += 1; + if Some(self.run.len()) == self.current_split_index { + self.phase = Ended; + self.attempt_ended = Some(AtomicDateTime::now()); + } + self.run.mark_as_modified(); + } } /// Starts a new attempt or stores the current time as the time of the /// current split. The attempt ends if the last split time is stored. pub fn split_or_start(&mut self) { - if self.active_attempt.is_none() { + if self.phase == NotRunning { self.start(); } else { self.split(); @@ -341,21 +362,12 @@ impl Timer { /// Skips the current split if an attempt is in progress and the /// current split is not the last split. pub fn skip_split(&mut self) { - let Some(active_attempt) = &mut self.active_attempt else { - return; - }; - - let Some(current_split_index) = active_attempt.current_split_index_mut() else { - return; - }; - - if *current_split_index + 1 < self.run.len() { - self.run - .segment_mut(*current_split_index) - .clear_split_info(); - - *current_split_index += 1; + if (self.phase == Running || self.phase == Paused) + && self.current_split_index < self.run.len().checked_sub(1) + { + self.current_split_mut().unwrap().clear_split_info(); + self.current_split_index = self.current_split_index.map(|i| i + 1); self.run.mark_as_modified(); } } @@ -364,27 +376,13 @@ impl Timer { /// and there is a previous split. The Timer Phase also switches to /// [`Running`] if it previously was [`Ended`]. pub fn undo_split(&mut self) { - let Some(active_attempt) = &mut self.active_attempt else { - return; - }; - - if let Some(previous_split_index) = active_attempt - .current_split_index_overflowing(&self.run) - .checked_sub(1) - { - let time_paused_at = match &active_attempt.state { - State::NotEnded { time_paused_at, .. } => *time_paused_at, - State::Ended { .. } => None, - }; - - active_attempt.state = State::NotEnded { - current_split_index: previous_split_index, - time_paused_at, - }; + if self.phase != NotRunning && self.current_split_index > Some(0) { + if self.phase == Ended { + self.phase = Running; + } + self.current_split_index = self.current_split_index.map(|i| i - 1); - self.run - .segment_mut(previous_split_index) - .clear_split_info(); + self.current_split_mut().unwrap().clear_split_info(); self.run.mark_as_modified(); } @@ -393,7 +391,7 @@ impl Timer { /// Checks whether the current attempt has a new Personal Best for the /// [`TimingMethod`] specified. pub fn current_attempt_has_new_personal_best(&self, timing_method: TimingMethod) -> bool { - if self.current_phase() != Ended { + if self.phase != Ended { return false; } @@ -413,7 +411,7 @@ impl Timer { /// Checks whether the current attempt has new best segment times in any of /// the segments for the [`TimingMethod`] specified. pub fn current_attempt_has_new_best_segments(&self, timing_method: TimingMethod) -> bool { - if self.active_attempt.is_none() { + if self.phase == NotRunning { return false; } @@ -441,7 +439,7 @@ impl Timer { /// in the Run's history. Otherwise the current attempt's information is /// discarded. pub fn reset(&mut self, update_splits: bool) { - if self.active_attempt.is_some() { + if self.phase != NotRunning { self.reset_state(update_splits); self.reset_splits(); } @@ -451,24 +449,32 @@ impl Timer { /// updated such that the current attempt's split times are being stored as /// the new Personal Best. pub fn reset_and_set_attempt_as_pb(&mut self) { - if self.active_attempt.is_some() { + if self.phase != NotRunning { self.reset_state(true); - set_run_as_pb(&mut self.run); + self.set_run_as_pb(); self.reset_splits(); } } fn reset_state(&mut self, update_times: bool) { - let Some(active_attempt) = self.active_attempt.take() else { - return; - }; + if self.phase != Ended { + self.attempt_ended = Some(AtomicDateTime::now()); + } + self.resume_game_time(); + self.set_loading_times(TimeSpan::zero()); if update_times { - active_attempt.update_times(&mut self.run, self.current_timing_method); + self.update_attempt_history(); + self.update_best_segments(); + self.update_pb_splits(); + self.update_segment_history(); } } fn reset_splits(&mut self) { + self.phase = NotRunning; + self.current_split_index = None; + // Reset Splits for segment in self.run.segments_mut() { segment.clear_split_info(); @@ -480,36 +486,23 @@ impl Timer { /// Pauses an active attempt that is not paused. pub fn pause(&mut self) { - if let Some(ActiveAttempt { - state: State::NotEnded { time_paused_at, .. }, - adjusted_start_time, - .. - }) = &mut self.active_attempt - { - if time_paused_at.is_none() { - *time_paused_at = Some(TimeStamp::now() - *adjusted_start_time); - } + if self.phase == Running { + self.time_paused_at = self.current_time().real_time.unwrap(); + self.phase = Paused; } } /// Resumes an attempt that is paused. pub fn resume(&mut self) { - if let Some(ActiveAttempt { - state: State::NotEnded { time_paused_at, .. }, - adjusted_start_time, - .. - }) = &mut self.active_attempt - { - if let Some(pause_time) = *time_paused_at { - *adjusted_start_time = TimeStamp::now() - pause_time; - *time_paused_at = None; - } + if self.phase == Paused { + self.adjusted_start_time = TimeStamp::now() - self.time_paused_at; + self.phase = Running; } } /// Toggles an active attempt between `Paused` and `Running`. pub fn toggle_pause(&mut self) { - match self.current_phase() { + match self.phase { Running => self.pause(), Paused => self.resume(), _ => {} @@ -519,7 +512,7 @@ impl Timer { /// Toggles an active attempt between [`Paused`] and [`Running`] or starts /// an attempt if there's none in progress. pub fn toggle_pause_or_start(&mut self) { - match self.current_phase() { + match self.phase { Running => self.pause(), Paused => self.resume(), NotRunning => self.start(), @@ -558,9 +551,7 @@ impl Timer { _ => {} } - if let Some(active_attempt) = &mut self.active_attempt { - active_attempt.adjusted_start_time = active_attempt.start_time_with_offset; - } + self.adjusted_start_time = self.start_time_with_offset; } /// Switches the current comparison to the next comparison in the list. @@ -601,85 +592,70 @@ impl Timer { /// duration only counts the time the Timer Phase has actually been /// `Running`. pub fn current_attempt_duration(&self) -> TimeSpan { - let Some(active_attempt) = &self.active_attempt else { - return TimeSpan::zero(); - }; - - if let State::Ended { attempt_ended } = active_attempt.state { - attempt_ended - active_attempt.attempt_started - } else { - TimeStamp::now() - active_attempt.start_time + match self.current_phase() { + NotRunning => TimeSpan::zero(), + Paused | Running => TimeStamp::now() - self.start_time, + Ended => self.attempt_ended.unwrap() - self.attempt_started.unwrap(), } } /// Returns the total amount of time the current attempt has been paused /// for. None is returned if there have not been any pauses. pub fn get_pause_time(&self) -> Option { - self.active_attempt.as_ref()?.get_pause_time() + match self.current_phase() { + Paused => Some(TimeStamp::now() - self.start_time_with_offset - self.time_paused_at), + Running | Ended if self.start_time_with_offset != self.adjusted_start_time => { + Some(self.adjusted_start_time - self.start_time_with_offset) + } + _ => None, + } } /// Returns whether Game Time is currently initialized. Game Time /// automatically gets uninitialized for each new attempt. #[inline] pub const fn is_game_time_initialized(&self) -> bool { - match &self.active_attempt { - Some(active_attempt) => active_attempt.loading_times.is_some(), - None => false, - } + self.loading_times.is_some() } /// Initializes Game Time for the current attempt. Game Time automatically /// gets uninitialized for each new attempt. #[inline] pub fn initialize_game_time(&mut self) { - if let Some(active_attempt) = &mut self.active_attempt { - if active_attempt.loading_times.is_none() { - active_attempt.loading_times = Some(TimeSpan::zero()); - } - } + self.loading_times = Some(self.loading_times()); } /// Deinitializes Game Time for the current attempt. #[inline] pub fn deinitialize_game_time(&mut self) { - if let Some(active_attempt) = &mut self.active_attempt { - active_attempt.loading_times = None; - } + self.loading_times = None; } /// Returns whether the Game Timer is currently paused. If the Game Timer is /// not paused, it automatically increments similar to Real Time. #[inline] pub const fn is_game_time_paused(&self) -> bool { - match &self.active_attempt { - Some(active_attempt) => active_attempt.game_time_paused_at.is_some(), - None => false, - } + self.is_game_time_paused } /// Pauses the Game Timer such that it doesn't automatically increment /// similar to Real Time. pub fn pause_game_time(&mut self) { - if let Some(active_attempt) = &mut self.active_attempt { - if active_attempt.game_time_paused_at.is_none() { - let current_time = active_attempt.current_time(&self.run); - - active_attempt.game_time_paused_at = - current_time.game_time.or(Some(current_time.real_time)); - } + if !self.is_game_time_paused() { + let current_time = self.current_time(); + self.game_time_pause_time = current_time.game_time.or(current_time.real_time); + self.is_game_time_paused = true; } } /// Resumes the Game Timer such that it automatically increments similar to /// Real Time, starting from the Game Time it was paused at. pub fn resume_game_time(&mut self) { - if let Some(active_attempt) = &mut self.active_attempt { - if active_attempt.game_time_paused_at.take().is_some() { - let current_time = active_attempt.current_time(&self.run); - - let diff = catch! { current_time.real_time - current_time.game_time? }; - active_attempt.set_loading_times(diff.unwrap_or_default(), &self.run); - } + if self.is_game_time_paused() { + let current_time = self.current_time(); + let diff = catch! { current_time.real_time? - current_time.game_time? }; + self.set_loading_times(diff.unwrap_or_default()); + self.is_game_time_paused = false; } } @@ -689,22 +665,16 @@ impl Timer { /// the Game Timer never shows any time that is not coming from the game. #[inline] pub fn set_game_time(&mut self, game_time: TimeSpan) { - if let Some(active_attempt) = &mut self.active_attempt { - if active_attempt.game_time_paused_at.is_some() { - active_attempt.game_time_paused_at = Some(game_time); - } - active_attempt.loading_times = - Some(active_attempt.current_time(&self.run).real_time - game_time); + if self.is_game_time_paused() { + self.game_time_pause_time = Some(game_time); } + self.loading_times = Some(self.current_time().real_time.unwrap() - game_time); } /// Accesses the loading times. Loading times are defined as Game Time - Real Time. #[inline] pub fn loading_times(&self) -> TimeSpan { - self.active_attempt - .as_ref() - .and_then(|a| a.loading_times) - .unwrap_or_default() + self.loading_times.unwrap_or_default() } /// Instead of setting the Game Time directly, this method can be used to @@ -712,8 +682,9 @@ impl Timer { /// is then automatically determined by Real Time - Loading Times. #[inline] pub fn set_loading_times(&mut self, time: TimeSpan) { - if let Some(active_attempt) = &mut self.active_attempt { - active_attempt.set_loading_times(time, &self.run); + self.loading_times = Some(time); + if self.is_game_time_paused() { + self.game_time_pause_time = Some(self.current_time().real_time.unwrap() - time); } } @@ -746,14 +717,80 @@ impl Timer { self.run.mark_as_modified(); } } -} -fn set_run_as_pb(run: &mut Run) { - run.import_pb_into_segment_history(); - run.fix_splits(); - for segment in run.segments_mut() { - let split_time = segment.split_time(); - segment.set_personal_best_split_time(split_time); + fn update_attempt_history(&mut self) { + let time = if self.phase == Ended { + self.current_time() + } else { + Default::default() + }; + + let pause_time = self.get_pause_time(); + + self.run + .add_attempt(time, self.attempt_started, self.attempt_ended, pause_time); + } + + fn update_best_segments(&mut self) { + let mut previous_split_time_rta = Some(TimeSpan::zero()); + let mut previous_split_time_game_time = Some(TimeSpan::zero()); + + for split in self.run.segments_mut() { + let mut new_best_segment = split.best_segment_time(); + if let Some(split_time) = split.split_time().real_time { + let current_segment = previous_split_time_rta.map(|previous| split_time - previous); + previous_split_time_rta = Some(split_time); + if split + .best_segment_time() + .real_time + .map_or(true, |b| current_segment.is_some_and(|c| c < b)) + { + new_best_segment.real_time = current_segment; + } + } + if let Some(split_time) = split.split_time().game_time { + let current_segment = + previous_split_time_game_time.map(|previous| split_time - previous); + previous_split_time_game_time = Some(split_time); + if split + .best_segment_time() + .game_time + .map_or(true, |b| current_segment.is_some_and(|c| c < b)) + { + new_best_segment.game_time = current_segment; + } + } + split.set_best_segment_time(new_best_segment); + } + } + + fn update_pb_splits(&mut self) { + let method = self.current_timing_method; + let (split_time, pb_split_time) = { + let last_segment = self.run.segments().last().unwrap(); + ( + last_segment.split_time()[method], + last_segment.personal_best_split_time()[method], + ) + }; + if split_time.is_some_and(|s| pb_split_time.map_or(true, |pb| s < pb)) { + self.set_run_as_pb(); + } + } + + fn update_segment_history(&mut self) { + if let Some(index) = self.current_split_index { + self.run.update_segment_history(index); + } + } + + fn set_run_as_pb(&mut self) { + self.run.import_pb_into_segment_history(); + self.run.fix_splits(); + for segment in self.run.segments_mut() { + let split_time = segment.split_time(); + segment.set_personal_best_split_time(split_time); + } + self.run.clear_run_id(); } - run.clear_run_id(); } diff --git a/src/timing/timer/tests/mod.rs b/src/timing/timer/tests/mod.rs index 7a58460c..0e6cfa3b 100644 --- a/src/timing/timer/tests/mod.rs +++ b/src/timing/timer/tests/mod.rs @@ -659,18 +659,3 @@ fn identifies_new_best_times_in_current_attempt() { assert!(!timer.current_attempt_has_new_personal_best(TimingMethod::GameTime)); assert!(!timer.current_attempt_has_new_best_segments(TimingMethod::GameTime)); } - -#[test] -fn skipping_keeps_timer_paused() { - let mut timer = timer(); - start_run(&mut timer); - timer.pause(); - - timer.skip_split(); - assert_eq!(timer.current_phase(), TimerPhase::Paused); - assert_eq!(timer.current_split_index(), Some(1)); - - timer.undo_split(); - assert_eq!(timer.current_phase(), TimerPhase::Paused); - assert_eq!(timer.current_split_index(), Some(0)); -} diff --git a/src/util/xml/helper.rs b/src/util/xml/helper.rs index c01da593..feae0dc1 100644 --- a/src/util/xml/helper.rs +++ b/src/util/xml/helper.rs @@ -157,10 +157,10 @@ pub fn reencode_children(reader: &mut Reader<'_>, target_buf: &mut String) -> Re .processing_instruction(text) .map_err(|_| Error::Xml)?; } - Event::Decl => { + Event::Decl(_) => { // Shouldn't really be a child anyway. } - Event::DocType => { + Event::DocType(_) => { // A DOCTYPE is not allowed in content. } Event::Ended => return Err(Error::UnexpectedEndOfFile), diff --git a/src/util/xml/reader.rs b/src/util/xml/reader.rs index c56f8371..68e10164 100644 --- a/src/util/xml/reader.rs +++ b/src/util/xml/reader.rs @@ -15,8 +15,8 @@ pub enum Event<'a> { End(TagName<'a>), Comment(Text<'a>), CData(Text<'a>), - DocType, - Decl, + DocType(Text<'a>), + Decl(&'a str), ProcessingInstruction(Text<'a>), Ended, } @@ -59,8 +59,8 @@ impl<'a> Reader<'a> { Event::Comment(Text(comment)) } else if let Some(cdata) = strip_surrounding("[CDATA[", tag_inner, "]]") { Event::CData(Text(cdata)) - } else if tag_inner.starts_with("DOCTYPE") { - Event::DocType + } else if let Some(doc_type) = tag_inner.strip_prefix("DOCTYPE") { + Event::DocType(Text(doc_type)) } else { return None; } @@ -68,12 +68,11 @@ impl<'a> Reader<'a> { self.source = rem; let tag_inner = self.read_until(AsciiChar::GREATER_THAN)?; if let Some(pi) = tag_inner.strip_suffix('?') { - if pi + if let Some(decl) = pi .strip_prefix("xml") .and_then(|decl| decl.strip_prefix(|b: char| b.is_ascii_whitespace())) - .is_some() { - Event::Decl + Event::Decl(decl) } else { Event::ProcessingInstruction(Text(pi)) }