optimized LimitedQueue implementation
This commit is contained in:
@@ -1,88 +1,94 @@
|
||||
use crate::parse::HitObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
static COMMON_RHYTHMS: [HitObjectRhythm; 9] = [
|
||||
HitObjectRhythm {
|
||||
id: 0,
|
||||
ratio: 1.0,
|
||||
difficulty: 0.0,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 1,
|
||||
ratio: 2.0 / 1.0,
|
||||
difficulty: 0.3,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 2,
|
||||
ratio: 1.0 / 2.0,
|
||||
difficulty: 0.5,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 3,
|
||||
ratio: 3.0 / 1.0,
|
||||
difficulty: 0.3,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 4,
|
||||
ratio: 1.0 / 3.0,
|
||||
difficulty: 0.35,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 5,
|
||||
ratio: 3.0 / 2.0,
|
||||
difficulty: 0.6,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 6,
|
||||
ratio: 2.0 / 3.0,
|
||||
difficulty: 0.4,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 7,
|
||||
ratio: 5.0 / 4.0,
|
||||
difficulty: 0.5,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 8,
|
||||
ratio: 4.0 / 5.0,
|
||||
difficulty: 0.7,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct HitObjectRhythm {
|
||||
id: u8,
|
||||
ratio: f64,
|
||||
pub(crate) difficulty: f64,
|
||||
}
|
||||
|
||||
impl PartialEq for HitObjectRhythm {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HitObjectRhythm {}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn closest_rhythm(
|
||||
delta_time: f64,
|
||||
last: &HitObject,
|
||||
last_last: &HitObject,
|
||||
clock_rate: f64,
|
||||
) -> &'static HitObjectRhythm {
|
||||
let prev_len = (last.start_time - last_last.start_time) / clock_rate;
|
||||
let ratio = delta_time / prev_len;
|
||||
|
||||
COMMON_RHYTHMS
|
||||
.iter()
|
||||
.min_by(|r1, r2| {
|
||||
(r1.ratio - ratio)
|
||||
.abs()
|
||||
.partial_cmp(&(r2.ratio - ratio).abs())
|
||||
.unwrap_or(Ordering::Equal)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
use crate::parse::HitObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
static COMMON_RHYTHMS: [HitObjectRhythm; 9] = [
|
||||
HitObjectRhythm {
|
||||
id: 0,
|
||||
ratio: 1.0,
|
||||
difficulty: 0.0,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 1,
|
||||
ratio: 2.0 / 1.0,
|
||||
difficulty: 0.3,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 2,
|
||||
ratio: 1.0 / 2.0,
|
||||
difficulty: 0.5,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 3,
|
||||
ratio: 3.0 / 1.0,
|
||||
difficulty: 0.3,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 4,
|
||||
ratio: 1.0 / 3.0,
|
||||
difficulty: 0.35,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 5,
|
||||
ratio: 3.0 / 2.0,
|
||||
difficulty: 0.6,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 6,
|
||||
ratio: 2.0 / 3.0,
|
||||
difficulty: 0.4,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 7,
|
||||
ratio: 5.0 / 4.0,
|
||||
difficulty: 0.5,
|
||||
},
|
||||
HitObjectRhythm {
|
||||
id: 8,
|
||||
ratio: 4.0 / 5.0,
|
||||
difficulty: 0.7,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct HitObjectRhythm {
|
||||
id: u8,
|
||||
ratio: f64,
|
||||
pub(crate) difficulty: f64,
|
||||
}
|
||||
|
||||
impl HitObjectRhythm {
|
||||
pub(crate) fn static_ref() -> &'static Self {
|
||||
&COMMON_RHYTHMS[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for HitObjectRhythm {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HitObjectRhythm {}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn closest_rhythm(
|
||||
delta_time: f64,
|
||||
last: &HitObject,
|
||||
last_last: &HitObject,
|
||||
clock_rate: f64,
|
||||
) -> &'static HitObjectRhythm {
|
||||
let prev_len = (last.start_time - last_last.start_time) / clock_rate;
|
||||
let ratio = delta_time / prev_len;
|
||||
|
||||
COMMON_RHYTHMS
|
||||
.iter()
|
||||
.min_by(|r1, r2| {
|
||||
(r1.ratio - ratio)
|
||||
.abs()
|
||||
.partial_cmp(&(r2.ratio - ratio).abs())
|
||||
.unwrap_or(Ordering::Equal)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
+34
-13
@@ -2,8 +2,6 @@ use crate::limited_queue::LimitedQueue;
|
||||
|
||||
use super::{DifficultyObject, HitObjectRhythm, Rim};
|
||||
|
||||
use std::ops::Index;
|
||||
|
||||
const RHYTHM_STRAIN_DECAY: f64 = 0.96;
|
||||
const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2;
|
||||
|
||||
@@ -11,20 +9,44 @@ const MONO_HISTORY_MAX_LEN: usize = 5;
|
||||
const RHYTHM_HISTORY_MAX_LEN: usize = 8;
|
||||
const STAMINA_HISTORY_MAX_LEN: usize = 2;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct RhythmHistoryElement {
|
||||
idx: usize,
|
||||
rhythm: &'static HitObjectRhythm,
|
||||
}
|
||||
|
||||
impl RhythmHistoryElement {
|
||||
fn new(difficulty_object: &DifficultyObject<'_>) -> Self {
|
||||
Self {
|
||||
idx: difficulty_object.idx,
|
||||
rhythm: difficulty_object.rhythm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RhythmHistoryElement {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
idx: 0,
|
||||
rhythm: HitObjectRhythm::static_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum SkillKind {
|
||||
Color {
|
||||
mono_history: LimitedQueue<usize>,
|
||||
mono_history: LimitedQueue<usize, MONO_HISTORY_MAX_LEN>,
|
||||
prev_is_rim: Option<bool>,
|
||||
current_mono_len: usize,
|
||||
},
|
||||
Rhythm {
|
||||
rhythm_history: LimitedQueue<(usize, &'static HitObjectRhythm)>, // (idx, rhythm)
|
||||
rhythm_history: LimitedQueue<RhythmHistoryElement, RHYTHM_HISTORY_MAX_LEN>,
|
||||
notes_since_rhythm_change: usize,
|
||||
current_strain: f64,
|
||||
},
|
||||
Stamina {
|
||||
note_pair_duration_history: LimitedQueue<f64>,
|
||||
note_pair_duration_history: LimitedQueue<f64, STAMINA_HISTORY_MAX_LEN>,
|
||||
hand: u8,
|
||||
off_hand_object_duration: f64,
|
||||
},
|
||||
@@ -34,7 +56,7 @@ impl SkillKind {
|
||||
#[inline]
|
||||
pub(crate) fn color() -> Self {
|
||||
Self::Color {
|
||||
mono_history: LimitedQueue::new(MONO_HISTORY_MAX_LEN),
|
||||
mono_history: LimitedQueue::new(),
|
||||
prev_is_rim: None,
|
||||
current_mono_len: 0,
|
||||
}
|
||||
@@ -43,7 +65,7 @@ impl SkillKind {
|
||||
#[inline]
|
||||
pub(crate) fn rhythm() -> Self {
|
||||
Self::Rhythm {
|
||||
rhythm_history: LimitedQueue::new(RHYTHM_HISTORY_MAX_LEN),
|
||||
rhythm_history: LimitedQueue::new(),
|
||||
notes_since_rhythm_change: 0,
|
||||
current_strain: 0.0,
|
||||
}
|
||||
@@ -52,7 +74,7 @@ impl SkillKind {
|
||||
#[inline]
|
||||
pub(crate) fn stamina(right_hand: bool) -> Self {
|
||||
Self::Stamina {
|
||||
note_pair_duration_history: LimitedQueue::new(STAMINA_HISTORY_MAX_LEN),
|
||||
note_pair_duration_history: LimitedQueue::new(),
|
||||
hand: right_hand as u8,
|
||||
off_hand_object_duration: f64::MAX,
|
||||
}
|
||||
@@ -114,7 +136,7 @@ impl SkillKind {
|
||||
let to_compare =
|
||||
mono_history.len() + i - MOST_RECENT_PATTERNS_TO_COMPARE;
|
||||
|
||||
mono_history.index(start + i) != mono_history.index(to_compare)
|
||||
mono_history[start + i] != mono_history[to_compare]
|
||||
});
|
||||
|
||||
if different_pattern {
|
||||
@@ -166,7 +188,7 @@ impl SkillKind {
|
||||
|
||||
let mut strain = current.rhythm.difficulty;
|
||||
|
||||
rhythm_history.push((current.idx, current.rhythm));
|
||||
rhythm_history.push(RhythmHistoryElement::new(current));
|
||||
|
||||
let mut reps_penalty = 1.0;
|
||||
|
||||
@@ -181,15 +203,14 @@ impl SkillKind {
|
||||
let to_compare =
|
||||
rhythm_history.len() + i - most_recent_patterns_to_compare;
|
||||
|
||||
rhythm_history.index(start + i).1 != rhythm_history.index(to_compare).1
|
||||
rhythm_history[start + i].rhythm != rhythm_history[to_compare].rhythm
|
||||
});
|
||||
|
||||
if different_pattern {
|
||||
continue;
|
||||
}
|
||||
|
||||
reps_penalty *=
|
||||
repetition_penalty(current.idx - rhythm_history.index(start).0);
|
||||
reps_penalty *= repetition_penalty(current.idx - rhythm_history[start].idx);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
+26
-21
@@ -6,27 +6,33 @@ const TL_MIN_REPETITIONS: isize = 16;
|
||||
|
||||
pub(crate) trait StaminaCheeseDetector {
|
||||
fn find_cheese(&self) -> Vec<bool>;
|
||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]);
|
||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]);
|
||||
fn find_rolls<const PATTERN_LEN: usize, const DOUBLE_PATTERN_LEN: usize>(
|
||||
&self,
|
||||
cheese: &mut [bool],
|
||||
);
|
||||
fn find_tl_tap<const PARITY: usize, const IS_RIN: bool>(&self, cheese: &mut [bool]);
|
||||
}
|
||||
|
||||
impl StaminaCheeseDetector for Beatmap {
|
||||
fn find_cheese(&self) -> Vec<bool> {
|
||||
let mut cheese = vec![false; self.hit_objects.len()];
|
||||
|
||||
self.find_rolls(3, &mut cheese);
|
||||
self.find_rolls(4, &mut cheese);
|
||||
self.find_rolls::<3, 6>(&mut cheese);
|
||||
self.find_rolls::<4, 8>(&mut cheese);
|
||||
|
||||
self.find_tl_tap(0, true, &mut cheese);
|
||||
self.find_tl_tap(1, true, &mut cheese);
|
||||
self.find_tl_tap(0, false, &mut cheese);
|
||||
self.find_tl_tap(1, false, &mut cheese);
|
||||
self.find_tl_tap::<0, true>(&mut cheese);
|
||||
self.find_tl_tap::<1, true>(&mut cheese);
|
||||
self.find_tl_tap::<0, false>(&mut cheese);
|
||||
self.find_tl_tap::<1, false>(&mut cheese);
|
||||
|
||||
cheese
|
||||
}
|
||||
|
||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]) {
|
||||
let mut history = LimitedQueue::new(2 * pattern_len);
|
||||
fn find_rolls<const PATTERN_LEN: usize, const DOUBLE_PATTERN_LEN: usize>(
|
||||
&self,
|
||||
cheese: &mut [bool],
|
||||
) {
|
||||
let mut history: LimitedQueue<u8, DOUBLE_PATTERN_LEN> = LimitedQueue::new();
|
||||
|
||||
let mut index_before_last_repeat = -1;
|
||||
let mut last_mark_end = 0;
|
||||
@@ -38,7 +44,7 @@ impl StaminaCheeseDetector for Beatmap {
|
||||
continue;
|
||||
}
|
||||
|
||||
let contains = contains_pattern_repeat(&history, pattern_len);
|
||||
let contains = contains_pattern_repeat::<PATTERN_LEN, DOUBLE_PATTERN_LEN>(&history);
|
||||
|
||||
if !contains {
|
||||
index_before_last_repeat = (i + 1 - history.len()) as isize;
|
||||
@@ -58,12 +64,12 @@ impl StaminaCheeseDetector for Beatmap {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]) {
|
||||
fn find_tl_tap<const PARITY: usize, const IS_RIN: bool>(&self, cheese: &mut [bool]) {
|
||||
let mut tl_len = -2;
|
||||
let mut last_mark_end = 0;
|
||||
|
||||
for (i, &sound) in self.sounds.iter().enumerate().skip(parity).step_by(2) {
|
||||
if sound.is_rim() == is_rin {
|
||||
for (i, &sound) in self.sounds.iter().enumerate().skip(PARITY).step_by(2) {
|
||||
if sound.is_rim() == IS_RIN {
|
||||
tl_len += 2;
|
||||
} else {
|
||||
tl_len = -2;
|
||||
@@ -73,11 +79,8 @@ impl StaminaCheeseDetector for Beatmap {
|
||||
continue;
|
||||
}
|
||||
|
||||
mark_as_cheese(
|
||||
(i as isize + 1 - tl_len).max(last_mark_end as isize) as usize,
|
||||
i,
|
||||
cheese,
|
||||
);
|
||||
let start = (i as isize + 1 - tl_len).max(last_mark_end as isize);
|
||||
mark_as_cheese(start as usize, i, cheese);
|
||||
|
||||
last_mark_end = i;
|
||||
}
|
||||
@@ -94,8 +97,10 @@ fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn contains_pattern_repeat(history: &LimitedQueue<u8>, pattern_len: usize) -> bool {
|
||||
for (&curr, &to_compare) in history.iter().zip(history.iter().skip(pattern_len)) {
|
||||
fn contains_pattern_repeat<const PATTERN_LEN: usize, const DOUBLE_PATTERN_LEN: usize>(
|
||||
history: &LimitedQueue<u8, DOUBLE_PATTERN_LEN>,
|
||||
) -> bool {
|
||||
for (&curr, &to_compare) in history.iter().zip(history.iter().skip(PATTERN_LEN)) {
|
||||
if curr.is_rim() != to_compare.is_rim() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user