|
| 1 | +use crate::{AtomicDateTime, Run, Time, TimeSpan, TimeStamp, TimingMethod}; |
| 2 | + |
| 3 | +#[derive(Debug, Clone)] |
| 4 | +pub struct ActiveAttempt { |
| 5 | + pub state: State, |
| 6 | + pub attempt_started: AtomicDateTime, |
| 7 | + pub start_time: TimeStamp, |
| 8 | + pub start_time_with_offset: TimeStamp, |
| 9 | + // This gets adjusted after resuming |
| 10 | + pub adjusted_start_time: TimeStamp, |
| 11 | + pub game_time_paused_at: Option<TimeSpan>, |
| 12 | + pub loading_times: Option<TimeSpan>, |
| 13 | +} |
| 14 | + |
| 15 | +#[derive(Debug, Clone)] |
| 16 | +pub enum State { |
| 17 | + NotEnded { |
| 18 | + current_split_index: usize, |
| 19 | + time_paused_at: Option<TimeSpan>, |
| 20 | + }, |
| 21 | + Ended { |
| 22 | + attempt_ended: AtomicDateTime, |
| 23 | + }, |
| 24 | +} |
| 25 | + |
| 26 | +pub struct TimerTime { |
| 27 | + pub real_time: TimeSpan, |
| 28 | + pub game_time: Option<TimeSpan>, |
| 29 | +} |
| 30 | + |
| 31 | +impl From<TimerTime> for Time { |
| 32 | + fn from(time: TimerTime) -> Self { |
| 33 | + Time { |
| 34 | + real_time: Some(time.real_time), |
| 35 | + game_time: time.game_time, |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +impl ActiveAttempt { |
| 41 | + pub fn current_time(&self, run: &Run) -> TimerTime { |
| 42 | + let real_time = match self.state { |
| 43 | + State::Ended { .. } => { |
| 44 | + let Time { |
| 45 | + real_time, |
| 46 | + game_time, |
| 47 | + } = run.segments().last().unwrap().split_time(); |
| 48 | + |
| 49 | + return TimerTime { |
| 50 | + real_time: real_time.unwrap_or_default(), |
| 51 | + game_time, |
| 52 | + }; |
| 53 | + } |
| 54 | + State::NotEnded { time_paused_at, .. } => { |
| 55 | + time_paused_at.unwrap_or_else(|| TimeStamp::now() - self.adjusted_start_time) |
| 56 | + } |
| 57 | + }; |
| 58 | + |
| 59 | + let game_time = self |
| 60 | + .game_time_paused_at |
| 61 | + .or_else(|| Some(real_time - self.loading_times?)); |
| 62 | + |
| 63 | + TimerTime { |
| 64 | + real_time, |
| 65 | + game_time, |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + pub fn get_pause_time(&self) -> Option<TimeSpan> { |
| 70 | + if let State::NotEnded { |
| 71 | + time_paused_at: Some(pause_time), |
| 72 | + .. |
| 73 | + } = self.state |
| 74 | + { |
| 75 | + return Some(TimeStamp::now() - self.start_time_with_offset - pause_time); |
| 76 | + } |
| 77 | + |
| 78 | + if self.start_time_with_offset != self.adjusted_start_time { |
| 79 | + Some(self.start_time_with_offset - self.adjusted_start_time) |
| 80 | + } else { |
| 81 | + None |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + pub fn set_loading_times(&mut self, time: TimeSpan, run: &Run) { |
| 86 | + self.loading_times = Some(time); |
| 87 | + if self.game_time_paused_at.is_some() { |
| 88 | + self.game_time_paused_at = Some(self.current_time(run).real_time - time); |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + pub fn prepare_split(&mut self, run: &Run) -> Option<(usize, Time)> { |
| 93 | + let State::NotEnded { |
| 94 | + current_split_index, |
| 95 | + time_paused_at: None, |
| 96 | + } = &mut self.state |
| 97 | + else { |
| 98 | + return None; |
| 99 | + }; |
| 100 | + |
| 101 | + let real_time = TimeStamp::now() - self.adjusted_start_time; |
| 102 | + |
| 103 | + if real_time < TimeSpan::zero() { |
| 104 | + return None; |
| 105 | + } |
| 106 | + |
| 107 | + let game_time = self |
| 108 | + .game_time_paused_at |
| 109 | + .or_else(|| Some(real_time - self.loading_times?)); |
| 110 | + |
| 111 | + let previous_split_index = *current_split_index; |
| 112 | + *current_split_index += 1; |
| 113 | + |
| 114 | + if *current_split_index == run.len() { |
| 115 | + self.state = State::Ended { |
| 116 | + attempt_ended: AtomicDateTime::now(), |
| 117 | + }; |
| 118 | + } |
| 119 | + |
| 120 | + Some(( |
| 121 | + previous_split_index, |
| 122 | + Time { |
| 123 | + real_time: Some(real_time), |
| 124 | + game_time, |
| 125 | + }, |
| 126 | + )) |
| 127 | + } |
| 128 | + |
| 129 | + pub const fn current_split_index(&self) -> Option<usize> { |
| 130 | + match self.state { |
| 131 | + State::NotEnded { |
| 132 | + current_split_index, |
| 133 | + .. |
| 134 | + } => Some(current_split_index), |
| 135 | + State::Ended { .. } => None, |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + pub fn current_split_index_mut(&mut self) -> Option<&mut usize> { |
| 140 | + match &mut self.state { |
| 141 | + State::NotEnded { |
| 142 | + current_split_index, |
| 143 | + .. |
| 144 | + } => Some(current_split_index), |
| 145 | + State::Ended { .. } => None, |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + pub fn current_split_index_overflowing(&self, run: &Run) -> usize { |
| 150 | + match self.state { |
| 151 | + State::NotEnded { |
| 152 | + current_split_index, |
| 153 | + .. |
| 154 | + } => current_split_index, |
| 155 | + State::Ended { .. } => run.len(), |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + pub fn update_times(&self, run: &mut Run, timing_method: TimingMethod) { |
| 160 | + self.update_attempt_history(run); |
| 161 | + update_best_segments(run); |
| 162 | + update_pb_splits(run, timing_method); |
| 163 | + run.update_segment_history(self.current_split_index_overflowing(run)); |
| 164 | + } |
| 165 | + |
| 166 | + pub fn update_attempt_history(&self, run: &mut Run) { |
| 167 | + let (attempt_ended, time) = match self.state { |
| 168 | + State::NotEnded { .. } => (AtomicDateTime::now(), Time::new()), |
| 169 | + State::Ended { attempt_ended } => { |
| 170 | + (attempt_ended, run.segments().last().unwrap().split_time()) |
| 171 | + } |
| 172 | + }; |
| 173 | + |
| 174 | + let pause_time = self.get_pause_time(); |
| 175 | + |
| 176 | + run.add_attempt( |
| 177 | + time, |
| 178 | + Some(self.attempt_started), |
| 179 | + Some(attempt_ended), |
| 180 | + pause_time, |
| 181 | + ); |
| 182 | + } |
| 183 | +} |
| 184 | + |
| 185 | +fn update_best_segments(run: &mut Run) { |
| 186 | + let mut previous_split_time_rta = Some(TimeSpan::zero()); |
| 187 | + let mut previous_split_time_game_time = Some(TimeSpan::zero()); |
| 188 | + |
| 189 | + for split in run.segments_mut() { |
| 190 | + let mut new_best_segment = split.best_segment_time(); |
| 191 | + if let Some(split_time) = split.split_time().real_time { |
| 192 | + let current_segment = previous_split_time_rta.map(|previous| split_time - previous); |
| 193 | + previous_split_time_rta = Some(split_time); |
| 194 | + if split |
| 195 | + .best_segment_time() |
| 196 | + .real_time |
| 197 | + .map_or(true, |b| current_segment.is_some_and(|c| c < b)) |
| 198 | + { |
| 199 | + new_best_segment.real_time = current_segment; |
| 200 | + } |
| 201 | + } |
| 202 | + if let Some(split_time) = split.split_time().game_time { |
| 203 | + let current_segment = |
| 204 | + previous_split_time_game_time.map(|previous| split_time - previous); |
| 205 | + previous_split_time_game_time = Some(split_time); |
| 206 | + if split |
| 207 | + .best_segment_time() |
| 208 | + .game_time |
| 209 | + .map_or(true, |b| current_segment.is_some_and(|c| c < b)) |
| 210 | + { |
| 211 | + new_best_segment.game_time = current_segment; |
| 212 | + } |
| 213 | + } |
| 214 | + split.set_best_segment_time(new_best_segment); |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +fn update_pb_splits(run: &mut Run, method: TimingMethod) { |
| 219 | + let (split_time, pb_split_time) = { |
| 220 | + let last_segment = run.segments().last().unwrap(); |
| 221 | + ( |
| 222 | + last_segment.split_time()[method], |
| 223 | + last_segment.personal_best_split_time()[method], |
| 224 | + ) |
| 225 | + }; |
| 226 | + if split_time.is_some_and(|s| pb_split_time.map_or(true, |pb| s < pb)) { |
| 227 | + super::set_run_as_pb(run); |
| 228 | + } |
| 229 | +} |
0 commit comments