#[cfg(feature = "serde")]
mod dump;
#[cfg(feature = "serde")]
pub use dump::{StateDump, TimerDump};
mod timer;
pub use timer::HailTimer;
use crate::{
Run,
types::{
Comparison, SplitStatus, StateChange, StateChangeRequest, TimeType, TimerState,
TimingMethod,
},
};
use std::cell::RefCell;
#[derive(Debug, Clone)]
pub struct HailState {
pub run: Run,
pub run_changed: bool,
pub rta: HailTimer,
rta_state: TimerState,
pub igt: HailTimer,
igt_state: TimerState,
pub current_seg: usize,
pub comparison: Comparison,
requests: RefCell<Vec<StateChangeRequest>>,
pub changes: Vec<StateChange>,
}
impl HailState {
pub fn new(run: Run) -> Self {
let rta = HailTimer::new(&run, TimingMethod::Rta);
let igt = HailTimer::new(&run, TimingMethod::Igt);
Self {
run,
run_changed: false,
rta,
rta_state: TimerState::NotRunning,
igt,
igt_state: TimerState::NotRunning,
current_seg: 0,
comparison: Comparison::PersonalBest,
requests: RefCell::new(Vec::new()),
changes: vec![StateChange::Reset],
}
}
#[cfg(feature = "serde")]
pub fn create_dump(&self) -> StateDump {
StateDump {
run: self.run.clone(),
comparison: self.comparison,
current_seg: self.current_seg,
rta: self.rta.create_dump(),
igt: self.igt.create_dump(),
igt_paused: self.is_igt_paused(),
}
}
#[cfg(feature = "serde")]
pub fn from_dump(d: &StateDump) -> Self {
let rta = HailTimer::from_dump(d, &d.run, TimingMethod::Rta);
let igt = if d.run.ingame_time {
HailTimer::from_dump(d, &d.run, TimingMethod::Igt)
} else {
HailTimer::new(&d.run, TimingMethod::Igt)
};
Self {
run: d.run.clone(),
run_changed: true,
rta,
rta_state: TimerState::Paused,
igt,
igt_state: if d.igt_paused {
TimerState::Paused
} else {
TimerState::Running
},
current_seg: d.current_seg,
comparison: d.comparison,
requests: RefCell::new(Vec::new()),
changes: vec![StateChange::Reset],
}
}
pub fn update(&mut self) {
use StateChangeRequest as SCR;
use TimerState as TS;
self.changes.clear();
if self.rta_state == TS::Running {
self.rta.update_timing();
if self.igt_state == TS::Running {
self.igt.update_timing();
}
} else if self.rta_state == TS::Offset {
self.rta.update_timing();
self.igt.update_timing();
if self.rta.active_time >= 0 {
self.rta_state = TS::Running;
self.igt_state = TS::Running;
self.changes
.push(StateChange::StartSeg { segment: Some(0) });
}
}
for r in self.requests.borrow().iter() {
match (r, self.rta_state, self.igt_state) {
(SCR::Pause, TS::Running, i) => {
self.rta.save_adj();
if i != TS::Paused {
self.igt.save_adj();
}
self.rta_state = TS::Paused;
self.changes.push(StateChange::Pause);
}
(SCR::Pause, TS::Paused, i) => {
self.rta_state = TS::Running;
self.rta.tare();
if i != TS::Paused {
self.igt.tare();
}
self.changes.push(StateChange::Unpause);
}
(SCR::PauseIGT, _, TS::Running) => {
self.igt_state = TS::Paused;
self.igt.save_adj();
self.changes.push(StateChange::PauseIGT);
}
(SCR::UnpauseIGT, _, TS::Paused) => {
self.igt_state = TS::Running;
self.igt.tare();
self.changes.push(StateChange::UnpauseIGT);
}
(SCR::Split, TS::Running, TS::Running | TS::Paused) => {
let rta_seg_time = self.rta.split();
self.run
.add_segment_attempt(self.current_seg, rta_seg_time, TimingMethod::Rta);
if self.run.ingame_time {
let igt_seg_time = self.igt.split();
self.run.add_segment_attempt(
self.current_seg,
igt_seg_time,
TimingMethod::Igt,
);
}
self.run_changed = true;
self.changes.push(StateChange::FinishSeg {
segment: self.current_seg,
});
if self.last_segment() {
self.rta_state = TS::Finished;
self.igt_state = TS::Finished;
let rta_prev_pb = self.run.pb(TimingMethod::Rta);
if *self.rta.run_split_times.last().unwrap() < rta_prev_pb
|| !rta_prev_pb.is_time()
{
self.run.rta_pb_splits = self.rta.run_split_times.clone();
}
let mut prev = TimeType::None;
for (i, &spl) in self.run.rta_pb_splits.iter().enumerate() {
if self.rta.run_pb_statuses[i] == SplitStatus::Gold {
self.run.rta_gold_segments[i] = spl - prev;
}
prev = spl;
}
if self.run.ingame_time {
let igt_prev_pb = self.run.pb(TimingMethod::Igt);
if *self.igt.run_split_times.last().unwrap() < igt_prev_pb
|| !igt_prev_pb.is_time()
{
self.run.igt_pb_splits = self.igt.run_split_times.clone();
}
let mut prev = TimeType::None;
for (i, &spl) in self.igt.run_split_times.iter().enumerate() {
if self.igt.run_pb_statuses[i] == SplitStatus::Gold {
self.run.igt_gold_segments[i] = spl - prev;
}
prev = spl;
}
}
self.run.calc_segments();
} else {
self.changes.push(StateChange::ChangeSeg {
old: self.current_seg,
new: self.current_seg + 1,
});
self.current_seg += 1;
self.changes.push(StateChange::StartSeg {
segment: Some(self.current_seg),
});
}
}
(SCR::Split, TS::NotRunning, TS::NotRunning) => {
self.rta.tare();
self.igt.tare();
let chg_seg;
if self.run.offset.val() < 0 {
self.rta_state = TS::Offset;
self.igt_state = TS::Offset;
chg_seg = None;
} else {
self.rta_state = TS::Running;
self.igt_state = TS::Running;
chg_seg = Some(0);
}
self.changes
.push(StateChange::StartSeg { segment: chg_seg });
}
(SCR::Unsplit, TS::Running, TS::Running | TS::Paused) if self.current_seg != 0 => {
self.changes.push(StateChange::ChangeSeg {
old: self.current_seg,
new: self.current_seg - 1,
});
self.current_seg -= 1;
let rta_seg_time = self.rta.unsplit();
self.run.remove_segment_attempt(
self.current_seg,
rta_seg_time,
TimingMethod::Rta,
);
if self.run.ingame_time {
let igt_seg_time = self.igt.unsplit();
self.run.remove_segment_attempt(
self.current_seg,
igt_seg_time,
TimingMethod::Igt,
);
}
}
(SCR::Skip, TS::Running, TS::Running | TS::Paused) if !self.last_segment() => {
self.changes.push(StateChange::ChangeSeg {
old: self.current_seg,
new: self.current_seg + 1,
});
self.rta.skip();
if self.run.ingame_time {
self.igt.skip();
}
self.current_seg += 1;
}
(SCR::Reset, _, _) => {
self.rta_state = TS::NotRunning;
let mut prev = TimeType::None;
for (i, &spl) in self.rta.run_split_times.iter().enumerate() {
if self.rta.run_pb_statuses[i] == SplitStatus::Gold {
self.run.rta_gold_segments[i] = spl - prev;
}
prev = spl;
}
self.rta = HailTimer::new(&self.run, TimingMethod::Rta);
self.igt_state = TS::NotRunning;
if self.run.ingame_time {
let mut prev = TimeType::None;
for (i, &spl) in self.igt.run_split_times.iter().enumerate() {
if self.igt.run_pb_statuses[i] == SplitStatus::Gold {
self.run.igt_gold_segments[i] = spl - prev;
}
prev = spl;
}
}
self.igt = HailTimer::new(&self.run, TimingMethod::Igt);
self.current_seg = 0;
self.changes.push(StateChange::Reset);
}
(&SCR::SetComparison(c), _, _) => {
self.changes.push(StateChange::ChangeComparison {
old: self.comparison,
new: c,
});
self.comparison = c;
}
(&SCR::SetIGT(t), _, _) => {
self.igt.set_time(t);
}
_ => {}
}
}
if self.rta_state == TS::Running {
self.rta.update_status();
if self.igt_state == TS::Running {
self.igt.update_status();
}
}
self.requests.borrow_mut().clear();
}
pub fn is_running(&self, method: TimingMethod) -> bool {
if method == TimingMethod::Rta {
self.is_rta_running()
} else {
self.is_igt_running()
}
}
pub fn is_rta_running(&self) -> bool {
self.rta_state == TimerState::Running
}
pub fn is_igt_running(&self) -> bool {
self.is_rta_running() && self.igt_state == TimerState::Running
}
pub fn is_igt_paused(&self) -> bool {
self.igt_state == TimerState::Paused
}
pub fn request(&self, rq: StateChangeRequest) {
self.requests.borrow_mut().push(rq);
}
pub fn in_offset(&self) -> bool {
self.rta_state == TimerState::Offset
}
fn last_segment(&self) -> bool {
self.current_seg + 1 == self.run.segment_names.len()
}
}