livesplit_core/run/
comparisons.rs

1use crate::{platform::prelude::*, Time};
2
3// We use a Vec here because a HashMap would require hashing the comparison and
4// then comparing the comparison with the string at the index calculated from
5// the hash. This means at least two full iterations over the string are
6// necessary, with one of them being somewhat expensive due to the hashing. Most
7// of the time, it is faster to just iterate over the few comparisons we have and
8// compare them directly. Most will be rejected right away since the first byte
9// doesn't even match, so in the end, you'll end up with less than two full
10// iterations over the string. In addition, Personal Best will be the first
11// comparison in the list most of the time, and that's the one we want to look
12// up most often anyway.
13//
14// One additional reason for doing this is that the ahash that was calculated
15// for the HashMap uses 128-bit multiplications, which regressed a lot in Rust
16// 1.44 for targets where the `compiler-builtins` helpers were used.
17// https://github.com/rust-lang/rust/issues/73135
18//
19// We could potentially look into interning our comparisons in the future, which
20// could yield even better performance.
21
22/// A collection of a segment's comparison times.
23#[derive(Clone, Default, Debug, PartialEq, Eq)]
24pub struct Comparisons(Vec<(Box<str>, Time)>);
25
26impl Comparisons {
27    fn index_of(&self, comparison: &str) -> Option<usize> {
28        Some(
29            self.0
30                .iter()
31                .enumerate()
32                .find(|(_, (c, _))| &**c == comparison)?
33                .0,
34        )
35    }
36
37    /// Accesses the time for the comparison specified.
38    pub fn get(&self, comparison: &str) -> Option<Time> {
39        Some(self.0[self.index_of(comparison)?].1)
40    }
41
42    /// Accesses the time for the comparison specified, or inserts a new empty
43    /// one if there is none.
44    pub fn get_or_insert_default(&mut self, comparison: &str) -> &mut Time {
45        if let Some(index) = self.index_of(comparison) {
46            &mut self.0[index].1
47        } else {
48            self.0.push((comparison.into(), Time::default()));
49            &mut self.0.last_mut().unwrap().1
50        }
51    }
52
53    /// Sets the time for the comparison specified.
54    pub fn set(&mut self, comparison: &str, time: Time) {
55        *self.get_or_insert_default(comparison) = time;
56    }
57
58    /// Removes the time for the comparison specified and returns it if there
59    /// was one.
60    pub fn remove(&mut self, comparison: &str) -> Option<Time> {
61        let index = self.index_of(comparison)?;
62        let (_, time) = self.0.remove(index);
63        Some(time)
64    }
65
66    /// Clears all the comparisons and their times.
67    pub fn clear(&mut self) {
68        self.0.clear();
69    }
70
71    /// Iterates over all the comparisons and their times.
72    pub fn iter(&self) -> impl Iterator<Item = &(Box<str>, Time)> + '_ {
73        self.0.iter()
74    }
75
76    /// Mutably iterates over all the comparisons and their times. Be careful
77    /// when modifying the comparison name. Having duplicates will likely cause
78    /// problems.
79    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (Box<str>, Time)> + '_ {
80        self.0.iter_mut()
81    }
82}