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}