#[cfg(feature = "serde")]
mod back_compat;
#[cfg(feature = "serde")]
mod hrn;
#[cfg(feature = "serde")]
mod lss;
#[cfg(feature = "serde")]
mod ser_des;
#[cfg(feature = "serde")]
pub use ser_des::SerDesRun;
use crate::types::{TimeType, TimingMethod};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Run {
pub game_title: String,
pub category: String,
pub ingame_time: bool,
pub offset: TimeType,
pub segment_names: Vec<String>,
pub rta_pb_splits: Vec<TimeType>,
pub igt_pb_splits: Vec<TimeType>,
rta_pb_segments: Vec<TimeType>,
igt_pb_segments: Vec<TimeType>,
pub rta_gold_segments: Vec<TimeType>,
pub igt_gold_segments: Vec<TimeType>,
rta_sum_segments: Vec<(usize, TimeType)>,
igt_sum_segments: Vec<(usize, TimeType)>,
rta_avg_segments: Vec<TimeType>,
igt_avg_segments: Vec<TimeType>,
}
impl Run {
pub fn add_segment_attempt(&mut self, idx: usize, time: TimeType, method: TimingMethod) {
if method == TimingMethod::Rta {
if idx >= self.rta_sum_segments.len() {
return;
}
let s = &mut self.rta_sum_segments[idx];
s.0 += 1;
s.1 += time;
self.rta_avg_segments[idx] = TimeType::from(s.1.val() / (s.0 as i128));
} else {
if idx >= self.igt_sum_segments.len() {
return;
}
let s = &mut self.igt_sum_segments[idx];
s.0 += 1;
s.1 += time;
self.igt_avg_segments[idx] = TimeType::from(s.1.val() / (s.0 as i128));
};
}
pub fn remove_segment_attempt(&mut self, idx: usize, time: TimeType, method: TimingMethod) {
let (s, a) = if method == TimingMethod::Rta {
if idx >= self.rta_sum_segments.len() {
return;
}
(
&mut self.rta_sum_segments[idx],
&mut self.rta_avg_segments[idx],
)
} else {
if idx >= self.igt_sum_segments.len() {
return;
}
(
&mut self.igt_sum_segments[idx],
&mut self.igt_avg_segments[idx],
)
};
if s.0 > 1 {
s.0 -= 1;
s.1 -= time;
*a = TimeType::from(s.1.val() / (s.0 as i128));
} else if s.0 == 1 && s.1 == time {
s.0 -= 1;
s.1 = TimeType::None;
*a = TimeType::None;
}
}
pub fn pb(&self, method: TimingMethod) -> TimeType {
*if method == TimingMethod::Rta {
&self.rta_pb_splits
} else {
&self.igt_pb_splits
}
.last()
.unwrap_or(&TimeType::None)
}
pub fn avg_segments(&self, method: TimingMethod) -> &Vec<TimeType> {
if method == TimingMethod::Rta {
&self.rta_avg_segments
} else {
&self.igt_avg_segments
}
}
pub fn pb_segments(&self, method: TimingMethod) -> &Vec<TimeType> {
if method == TimingMethod::Rta {
&self.rta_pb_segments
} else {
&self.igt_pb_segments
}
}
pub fn verify(&self) -> Result<(), InvalidReason> {
use InvalidReason::*;
let len = self.segment_names.len();
if len == 0 {
return Err(NoSegments);
}
if self.rta_pb_splits.len() != len {
return Err(WrongLenPb(TimingMethod::Rta));
} else if self.rta_gold_segments.len() != len {
return Err(WrongLenGold(TimingMethod::Rta));
} else if self.rta_sum_segments.len() != len {
return Err(WrongLenSum(TimingMethod::Rta));
}
let mut prev = TimeType::None;
for (i, &t) in self.rta_pb_splits.iter().enumerate() {
if t.val() < 0 {
return Err(NegPbSplit(i, TimingMethod::Rta));
}
if t.is_time() && t <= prev {
return Err(NonIncreasingSplit(i, TimingMethod::Rta));
}
prev = t;
}
for (i, &t) in self.rta_gold_segments.iter().enumerate() {
if t.val() < 0 {
return Err(NegGoldSeg(i, TimingMethod::Rta));
}
}
for (i, &(n, t)) in self.rta_sum_segments.iter().enumerate() {
if t.val() < 0 {
return Err(NegSumTime(i, TimingMethod::Rta));
} else if n == 0 && t.val() != 0 {
return Err(ZeroSegCount(i, TimingMethod::Rta));
}
}
if self.ingame_time {
if self.igt_pb_splits.len() != len {
return Err(WrongLenPb(TimingMethod::Igt));
} else if self.igt_sum_segments.len() != len {
return Err(WrongLenSum(TimingMethod::Igt));
}
let mut prev = TimeType::None;
for (i, &t) in self.igt_pb_splits.iter().enumerate() {
if t.val() < 0 {
return Err(NegPbSplit(i, TimingMethod::Igt));
}
if t.is_time() && t <= prev {
return Err(NonIncreasingSplit(i, TimingMethod::Rta));
}
prev = t;
}
for (i, &t) in self.igt_gold_segments.iter().enumerate() {
if t.val() < 0 {
return Err(NegGoldSeg(i, TimingMethod::Igt));
}
}
for (i, &(n, t)) in self.igt_sum_segments.iter().enumerate() {
if t.val() < 0 {
return Err(NegSumTime(i, TimingMethod::Igt));
} else if n == 0 && t.val() != 0 {
return Err(ZeroSegCount(i, TimingMethod::Igt));
}
}
}
Ok(())
}
pub fn normalize(&mut self) {
self.offset.normalize();
for t in &mut self.rta_pb_splits {
t.normalize();
}
for t in &mut self.rta_gold_segments {
t.normalize();
}
for (_, t) in &mut self.rta_sum_segments {
t.normalize();
}
for t in &mut self.igt_pb_splits {
t.normalize();
}
for t in &mut self.igt_gold_segments {
t.normalize();
}
for (_, t) in &mut self.igt_sum_segments {
t.normalize();
}
}
pub fn calc_avgs(&mut self) {
self.rta_avg_segments = self
.rta_sum_segments
.iter()
.map(|&(n, t)| {
if t == TimeType::None {
TimeType::None
} else {
TimeType::from(t.val() / n as i128)
}
})
.collect();
self.igt_avg_segments = self
.igt_sum_segments
.iter()
.map(|&(n, t)| {
if t == TimeType::None {
TimeType::None
} else {
TimeType::from(t.val() / n as i128)
}
})
.collect();
}
pub fn calc_segments(&mut self) {
self.rta_pb_segments = self
.rta_pb_splits
.iter()
.enumerate()
.scan(TimeType::None, split_to_seg)
.collect();
self.igt_pb_segments = self
.igt_pb_splits
.iter()
.enumerate()
.scan(TimeType::None, split_to_seg)
.collect();
}
}
impl Default for Run {
fn default() -> Self {
Self {
game_title: "".into(),
category: "".into(),
ingame_time: false,
offset: TimeType::None,
segment_names: vec!["".into()],
rta_pb_splits: vec![TimeType::None],
igt_pb_splits: vec![TimeType::None],
rta_pb_segments: vec![TimeType::None],
igt_pb_segments: vec![TimeType::None],
rta_gold_segments: vec![TimeType::None],
igt_gold_segments: vec![TimeType::None],
rta_sum_segments: vec![(0, TimeType::None)],
igt_sum_segments: vec![(0, TimeType::None)],
rta_avg_segments: vec![TimeType::None],
igt_avg_segments: vec![TimeType::None],
}
}
}
fn split_to_seg(last: &mut TimeType, (i, &time): (usize, &TimeType)) -> Option<TimeType> {
let ret = Some(if !time.is_time() || (!last.is_time() && i != 0) {
TimeType::None
} else {
time - *last
});
*last = time;
ret
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Error)]
pub enum InvalidReason {
#[error("PB split {0} ({1:?}) is negative!")]
NegPbSplit(usize, TimingMethod),
#[error("Gold segment {0} ({1:?}) is negative!")]
NegGoldSeg(usize, TimingMethod),
#[error("Sum time {0} ({1:?}) is negative!")]
NegSumTime(usize, TimingMethod),
#[error("Sum count {0} ({1:?}) is zero, but a time is given!")]
ZeroSegCount(usize, TimingMethod),
#[error("No segments given!")]
NoSegments,
#[error("Wrong number of PB segments! ({0:?})")]
WrongLenPb(TimingMethod),
#[error("Wrong number of gold segments! ({0:?})")]
WrongLenGold(TimingMethod),
#[error("Wrong number of sum segments! ({0:?})")]
WrongLenSum(TimingMethod),
#[error("Split time {0} ({1:?}) is before the previous split!")]
NonIncreasingSplit(usize, TimingMethod),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn update_avg_calc() {
let mut r = Run::default();
r.add_segment_attempt(0, TimeType::Time(777), TimingMethod::Rta);
assert_eq!(r.avg_segments(TimingMethod::Rta)[0], TimeType::Time(777));
r.add_segment_attempt(0, TimeType::Time(9999), TimingMethod::Rta);
assert_eq!(r.avg_segments(TimingMethod::Rta)[0], TimeType::Time(5388));
}
#[test]
fn calc_all_avg() {
let mut r = Run {
rta_sum_segments: vec![(5, TimeType::Time(505050)), (4, TimeType::Time(770239))],
..Default::default()
};
r.calc_avgs();
assert_eq!(
r.avg_segments(TimingMethod::Rta),
&vec![TimeType::Time(101010), TimeType::Time(192559)]
);
}
#[test]
fn verify_wrong_len_pb() {
let r = Run {
rta_pb_splits: vec![TimeType::Time(1234), TimeType::Time(-1234)],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::WrongLenPb(TimingMethod::Rta))
);
}
#[test]
fn verify_wrong_len_gold() {
let r = Run {
rta_gold_segments: vec![TimeType::Time(1234), TimeType::Time(-1234)],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::WrongLenGold(TimingMethod::Rta))
);
}
#[test]
fn verify_wrong_len_sum() {
let r = Run {
rta_sum_segments: vec![(0, TimeType::Time(1234)), (0, TimeType::Time(-1234))],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::WrongLenSum(TimingMethod::Rta))
);
}
#[test]
fn verify_neg_pb_seg() {
let r = Run {
segment_names: vec!["".into(), "".into()],
rta_pb_splits: vec![TimeType::Time(1234), TimeType::Time(-1234)],
rta_gold_segments: vec![TimeType::Time(1234), TimeType::Time(-1234)],
rta_sum_segments: vec![(0, TimeType::Time(1234)), (0, TimeType::Time(-1234))],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::NegPbSplit(1, TimingMethod::Rta))
);
}
#[test]
fn verify_neg_gold_seg() {
let r = Run {
segment_names: vec!["".into(), "".into()],
rta_pb_splits: vec![TimeType::Time(1234), TimeType::Time(1235)],
rta_gold_segments: vec![TimeType::Time(1234), TimeType::Time(-1234)],
rta_sum_segments: vec![(0, TimeType::Time(1234)), (0, TimeType::Time(-1234))],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::NegGoldSeg(1, TimingMethod::Rta))
);
}
#[test]
fn verify_neg_sum_time() {
let r = Run {
segment_names: vec!["".into(), "".into()],
rta_pb_splits: vec![TimeType::Time(1234), TimeType::Time(1235)],
rta_gold_segments: vec![TimeType::Time(1234), TimeType::Time(1234)],
rta_sum_segments: vec![(1, TimeType::Time(1234)), (1, TimeType::Time(-1234))],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::NegSumTime(1, TimingMethod::Rta))
);
}
#[test]
fn verify_no_segments() {
let r = Run {
segment_names: vec![],
..Default::default()
};
assert_eq!(r.verify(), Err(InvalidReason::NoSegments));
}
#[test]
fn verify_zero_seg_count() {
let r = Run {
segment_names: vec!["".into(), "".into()],
rta_pb_splits: vec![TimeType::Time(1234), TimeType::Time(1235)],
rta_gold_segments: vec![TimeType::Time(1234), TimeType::Time(1234)],
rta_sum_segments: vec![(0, TimeType::Time(1234)), (0, TimeType::Time(1234))],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::ZeroSegCount(0, TimingMethod::Rta))
);
}
#[test]
fn verify_non_increasing() {
let mut r = Run {
segment_names: vec!["".into(), "".into(), "".into()],
rta_pb_splits: vec![
TimeType::Time(1234),
TimeType::Time(1000),
TimeType::Time(2000),
],
rta_gold_segments: vec![
TimeType::Time(1234),
TimeType::Time(1234),
TimeType::Time(1234),
],
rta_sum_segments: vec![
(1, TimeType::Time(1234)),
(1, TimeType::Time(1234)),
(1, TimeType::Time(1234)),
],
..Default::default()
};
assert_eq!(
r.verify(),
Err(InvalidReason::NonIncreasingSplit(1, TimingMethod::Rta))
);
r.rta_pb_splits[1] = TimeType::None;
assert!(r.verify().is_ok());
}
#[test]
fn normalize() {
let mut r = Run {
offset: TimeType::Time(0),
..Default::default()
};
r.rta_pb_splits[0] = TimeType::Time(0);
r.rta_gold_segments[0] = TimeType::Time(0);
r.rta_sum_segments[0].1 = TimeType::Time(0);
r.normalize();
assert_eq!(r, Run::default());
}
}