optimized LimitedQueue implementation
This commit is contained in:
@@ -20,6 +20,8 @@
|
|||||||
- Added a new field `kiai: bool` to both `TimingPoint` and `DifficultyPoint` to denote whether the current timing section is in kiai mode
|
- Added a new field `kiai: bool` to both `TimingPoint` and `DifficultyPoint` to denote whether the current timing section is in kiai mode
|
||||||
- Added a new field `breaks: Vec<Break>` to `Beatmap` that contains all breaks throughout the map
|
- Added a new field `breaks: Vec<Break>` to `Beatmap` that contains all breaks throughout the map
|
||||||
- Added a new field `edge_sounds: Vec<u8>` to the `Slider` variant of `HitObjectKind` to denote the sample played on slider heads, ends, and repeats
|
- Added a new field `edge_sounds: Vec<u8>` to the `Slider` variant of `HitObjectKind` to denote the sample played on slider heads, ends, and repeats
|
||||||
|
- __Other:__
|
||||||
|
- Small performance improvements for osu!taiko calculations
|
||||||
|
|
||||||
# v0.5.2 (2022-06-14)
|
# v0.5.2 (2022-06-14)
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ impl Beatmap {
|
|||||||
|
|
||||||
map.cs = target_columns;
|
map.cs = target_columns;
|
||||||
|
|
||||||
let mut prev_note_times = LimitedQueue::new(MAX_NOTES_FOR_DENSITY);
|
let mut prev_note_times: LimitedQueue<f64, MAX_NOTES_FOR_DENSITY> = LimitedQueue::new();
|
||||||
let mut density = i32::MAX as f64;
|
let mut density = i32::MAX as f64;
|
||||||
|
|
||||||
let mut compute_density = |new_note_time: f64, d: &mut f64| {
|
let mut compute_density = |new_note_time: f64, d: &mut f64| {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ impl<'h> DistanceObjectPatternGenerator<'h> {
|
|||||||
let conversion_diff = self.conversion_difficulty();
|
let conversion_diff = self.conversion_difficulty();
|
||||||
|
|
||||||
if self.total_columns == 1 {
|
if self.total_columns == 1 {
|
||||||
return Pattern::new_slider_note(self, 0, self.start_time, self.end_time);
|
Pattern::new_slider_note(self, 0, self.start_time, self.end_time)
|
||||||
} else if self.span_count > 1 {
|
} else if self.span_count > 1 {
|
||||||
if self.segment_duration <= 90 {
|
if self.segment_duration <= 90 {
|
||||||
self.generate_random_hold_notes(self.start_time, 1)
|
self.generate_random_hold_notes(self.start_time, 1)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ use crate::{
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum GradualDifficultyAttributes<'map> {
|
pub enum GradualDifficultyAttributes<'map> {
|
||||||
/// Gradual osu!catch difficulty attributes.
|
/// Gradual osu!catch difficulty attributes.
|
||||||
Catch(CatchGradualDifficultyAttributes<'map>),
|
Catch(CatchGradualDifficultyAttributes<'map>),
|
||||||
|
|||||||
+111
-39
@@ -1,75 +1,101 @@
|
|||||||
use std::cmp::Ordering;
|
use std::{
|
||||||
use std::iter::{Cycle, Skip, Take};
|
cmp::Ordering,
|
||||||
use std::ops::Index;
|
iter::{Cycle, Skip, Take},
|
||||||
use std::slice::Iter;
|
ops::Index,
|
||||||
|
slice::Iter,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: make generic over const size
|
/// Efficient counterpart to osu!'s [`LimitedCapacityQueue`]
|
||||||
|
/// i.e. an indexed queue with limited capacity.
|
||||||
|
///
|
||||||
|
/// [`LimitedQueue`] will use an internal array as queue which
|
||||||
|
/// is stored on the stack. Hence, if the size is very large,
|
||||||
|
/// e.g. `size_of<T>() * N`, consider using a different type
|
||||||
|
/// since heap allocation might be favorable.
|
||||||
|
///
|
||||||
|
/// [`LimitedCapacityQueue`]: https://github.com/ppy/osu/blob/b49a1aab8ac6e16e48dffd03f55635cdc1771adf/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct LimitedQueue<T> {
|
pub(crate) struct LimitedQueue<T, const N: usize> {
|
||||||
queue: Vec<T>,
|
queue: [T; N],
|
||||||
start: usize,
|
/// If the queue is not empty, `end` is the index of the last element.
|
||||||
|
/// Otherwise, it has no meaning.
|
||||||
end: usize,
|
end: usize,
|
||||||
|
/// Amount of elements in the queue. This is equal to `end + 1`
|
||||||
|
/// if the queue is not full, or `N` otherwise.
|
||||||
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> LimitedQueue<T> {
|
impl<T, const N: usize> Default for LimitedQueue<T, N>
|
||||||
|
where
|
||||||
|
T: Copy + Clone + Default,
|
||||||
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn new(capacity: usize) -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
end: capacity - 1,
|
end: N - 1,
|
||||||
start: 0,
|
queue: [T::default(); N],
|
||||||
queue: Vec::with_capacity(capacity),
|
len: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
impl<T, const N: usize> LimitedQueue<T, N>
|
||||||
|
where
|
||||||
|
T: Copy + Clone + Default,
|
||||||
|
{
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> LimitedQueue<T, N> {
|
||||||
pub(crate) fn push(&mut self, elem: T) {
|
pub(crate) fn push(&mut self, elem: T) {
|
||||||
let capacity = self.queue.capacity();
|
self.end = (self.end + 1) % N;
|
||||||
self.end = (self.end + 1) % capacity;
|
|
||||||
|
|
||||||
if self.queue.len() == capacity {
|
|
||||||
self.start = (self.start + 1) % capacity;
|
|
||||||
self.queue[self.end] = elem;
|
self.queue[self.end] = elem;
|
||||||
} else {
|
self.len += (self.len < N) as usize;
|
||||||
self.queue.push(elem);
|
}
|
||||||
}
|
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.len == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn len(&self) -> usize {
|
pub(crate) fn len(&self) -> usize {
|
||||||
self.queue.len()
|
self.len
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn last(&self) -> Option<&T> {
|
pub(crate) fn last(&self) -> Option<&T> {
|
||||||
self.queue.get(self.end)
|
if self.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.queue[self.end])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn clear(&mut self) {
|
pub(crate) fn clear(&mut self) {
|
||||||
self.start = 0;
|
self.end = N - 1;
|
||||||
self.end = self.queue.capacity() - 1;
|
self.len = 0;
|
||||||
self.queue.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn full(&self) -> bool {
|
pub(crate) fn full(&self) -> bool {
|
||||||
self.queue.len() == self.queue.capacity()
|
self.len == N
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn iter(&self) -> LimitedQueueIter<'_, T> {
|
pub(crate) fn iter(&self) -> LimitedQueueIter<'_, T> {
|
||||||
self.queue
|
self.queue
|
||||||
.iter()
|
.iter()
|
||||||
.cycle()
|
.cycle()
|
||||||
.skip(self.start)
|
.skip((self.len == N) as usize * (self.end + 1))
|
||||||
.take(self.queue.len())
|
.take(self.len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialOrd> LimitedQueue<T> {
|
pub(crate) type LimitedQueueIter<'a, T> = Take<Skip<Cycle<Iter<'a, T>>>>;
|
||||||
|
|
||||||
|
impl<T: PartialOrd, const N: usize> LimitedQueue<T, N> {
|
||||||
pub(crate) fn min(&self) -> Option<&T> {
|
pub(crate) fn min(&self) -> Option<&T> {
|
||||||
self.queue
|
self.queue
|
||||||
.iter()
|
.iter()
|
||||||
|
.take(self.len)
|
||||||
.reduce(|min, next| match min.partial_cmp(next) {
|
.reduce(|min, next| match min.partial_cmp(next) {
|
||||||
Some(Ordering::Less) => min,
|
Some(Ordering::Less) => min,
|
||||||
Some(Ordering::Equal) => min,
|
Some(Ordering::Equal) => min,
|
||||||
@@ -79,13 +105,59 @@ impl<T: PartialOrd> LimitedQueue<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Index<usize> for LimitedQueue<T> {
|
impl<T, const N: usize> Index<usize> for LimitedQueue<T, N> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn index(&self, idx: usize) -> &Self::Output {
|
fn index(&self, idx: usize) -> &Self::Output {
|
||||||
&self.queue[(self.start + idx) % self.queue.capacity()]
|
assert!(
|
||||||
|
idx < self.len,
|
||||||
|
"index out of bounds: the len is {} but the index is {idx}",
|
||||||
|
self.len
|
||||||
|
);
|
||||||
|
|
||||||
|
let idx = (idx + (self.len == N) as usize * (self.end + 1)) % N;
|
||||||
|
|
||||||
|
&self.queue[idx]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type LimitedQueueIter<'a, T> = Take<Skip<Cycle<Iter<'a, T>>>>;
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::LimitedQueue;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
let queue = LimitedQueue::<u8, 4>::default();
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
assert_eq!(queue.last(), None);
|
||||||
|
assert_eq!(queue.iter().count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_push() {
|
||||||
|
let mut queue = LimitedQueue::<u8, 4>::default();
|
||||||
|
let elem = 42;
|
||||||
|
queue.push(elem);
|
||||||
|
assert!(!queue.is_empty());
|
||||||
|
assert_eq!(queue.len(), 1);
|
||||||
|
assert_eq!(queue.last(), Some(&elem));
|
||||||
|
assert!(queue.iter().eq(vec![elem].iter()));
|
||||||
|
assert_eq!(queue[0], elem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overfull() {
|
||||||
|
let mut queue = LimitedQueue::<u8, 4>::default();
|
||||||
|
|
||||||
|
for i in 1..=5 {
|
||||||
|
queue.push(i as u8);
|
||||||
|
assert_eq!(i.min(4), queue.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(queue.last(), Some(&5));
|
||||||
|
assert!(queue.iter().eq(vec![2, 3, 4, 5].iter()));
|
||||||
|
assert_eq!(queue[0], 2);
|
||||||
|
assert_eq!(queue[3], 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ pub(crate) struct HitObjectRhythm {
|
|||||||
pub(crate) difficulty: f64,
|
pub(crate) difficulty: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HitObjectRhythm {
|
||||||
|
pub(crate) fn static_ref() -> &'static Self {
|
||||||
|
&COMMON_RHYTHMS[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for HitObjectRhythm {
|
impl PartialEq for HitObjectRhythm {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
|||||||
+34
-13
@@ -2,8 +2,6 @@ use crate::limited_queue::LimitedQueue;
|
|||||||
|
|
||||||
use super::{DifficultyObject, HitObjectRhythm, Rim};
|
use super::{DifficultyObject, HitObjectRhythm, Rim};
|
||||||
|
|
||||||
use std::ops::Index;
|
|
||||||
|
|
||||||
const RHYTHM_STRAIN_DECAY: f64 = 0.96;
|
const RHYTHM_STRAIN_DECAY: f64 = 0.96;
|
||||||
const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2;
|
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 RHYTHM_HISTORY_MAX_LEN: usize = 8;
|
||||||
const STAMINA_HISTORY_MAX_LEN: usize = 2;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum SkillKind {
|
pub(crate) enum SkillKind {
|
||||||
Color {
|
Color {
|
||||||
mono_history: LimitedQueue<usize>,
|
mono_history: LimitedQueue<usize, MONO_HISTORY_MAX_LEN>,
|
||||||
prev_is_rim: Option<bool>,
|
prev_is_rim: Option<bool>,
|
||||||
current_mono_len: usize,
|
current_mono_len: usize,
|
||||||
},
|
},
|
||||||
Rhythm {
|
Rhythm {
|
||||||
rhythm_history: LimitedQueue<(usize, &'static HitObjectRhythm)>, // (idx, rhythm)
|
rhythm_history: LimitedQueue<RhythmHistoryElement, RHYTHM_HISTORY_MAX_LEN>,
|
||||||
notes_since_rhythm_change: usize,
|
notes_since_rhythm_change: usize,
|
||||||
current_strain: f64,
|
current_strain: f64,
|
||||||
},
|
},
|
||||||
Stamina {
|
Stamina {
|
||||||
note_pair_duration_history: LimitedQueue<f64>,
|
note_pair_duration_history: LimitedQueue<f64, STAMINA_HISTORY_MAX_LEN>,
|
||||||
hand: u8,
|
hand: u8,
|
||||||
off_hand_object_duration: f64,
|
off_hand_object_duration: f64,
|
||||||
},
|
},
|
||||||
@@ -34,7 +56,7 @@ impl SkillKind {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn color() -> Self {
|
pub(crate) fn color() -> Self {
|
||||||
Self::Color {
|
Self::Color {
|
||||||
mono_history: LimitedQueue::new(MONO_HISTORY_MAX_LEN),
|
mono_history: LimitedQueue::new(),
|
||||||
prev_is_rim: None,
|
prev_is_rim: None,
|
||||||
current_mono_len: 0,
|
current_mono_len: 0,
|
||||||
}
|
}
|
||||||
@@ -43,7 +65,7 @@ impl SkillKind {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn rhythm() -> Self {
|
pub(crate) fn rhythm() -> Self {
|
||||||
Self::Rhythm {
|
Self::Rhythm {
|
||||||
rhythm_history: LimitedQueue::new(RHYTHM_HISTORY_MAX_LEN),
|
rhythm_history: LimitedQueue::new(),
|
||||||
notes_since_rhythm_change: 0,
|
notes_since_rhythm_change: 0,
|
||||||
current_strain: 0.0,
|
current_strain: 0.0,
|
||||||
}
|
}
|
||||||
@@ -52,7 +74,7 @@ impl SkillKind {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn stamina(right_hand: bool) -> Self {
|
pub(crate) fn stamina(right_hand: bool) -> Self {
|
||||||
Self::Stamina {
|
Self::Stamina {
|
||||||
note_pair_duration_history: LimitedQueue::new(STAMINA_HISTORY_MAX_LEN),
|
note_pair_duration_history: LimitedQueue::new(),
|
||||||
hand: right_hand as u8,
|
hand: right_hand as u8,
|
||||||
off_hand_object_duration: f64::MAX,
|
off_hand_object_duration: f64::MAX,
|
||||||
}
|
}
|
||||||
@@ -114,7 +136,7 @@ impl SkillKind {
|
|||||||
let to_compare =
|
let to_compare =
|
||||||
mono_history.len() + i - MOST_RECENT_PATTERNS_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 {
|
if different_pattern {
|
||||||
@@ -166,7 +188,7 @@ impl SkillKind {
|
|||||||
|
|
||||||
let mut strain = current.rhythm.difficulty;
|
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;
|
let mut reps_penalty = 1.0;
|
||||||
|
|
||||||
@@ -181,15 +203,14 @@ impl SkillKind {
|
|||||||
let to_compare =
|
let to_compare =
|
||||||
rhythm_history.len() + i - most_recent_patterns_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 {
|
if different_pattern {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
reps_penalty *=
|
reps_penalty *= repetition_penalty(current.idx - rhythm_history[start].idx);
|
||||||
repetition_penalty(current.idx - rhythm_history.index(start).0);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-21
@@ -6,27 +6,33 @@ const TL_MIN_REPETITIONS: isize = 16;
|
|||||||
|
|
||||||
pub(crate) trait StaminaCheeseDetector {
|
pub(crate) trait StaminaCheeseDetector {
|
||||||
fn find_cheese(&self) -> Vec<bool>;
|
fn find_cheese(&self) -> Vec<bool>;
|
||||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]);
|
fn find_rolls<const PATTERN_LEN: usize, const DOUBLE_PATTERN_LEN: usize>(
|
||||||
fn find_tl_tap(&self, parity: usize, is_rin: bool, cheese: &mut [bool]);
|
&self,
|
||||||
|
cheese: &mut [bool],
|
||||||
|
);
|
||||||
|
fn find_tl_tap<const PARITY: usize, const IS_RIN: bool>(&self, cheese: &mut [bool]);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaminaCheeseDetector for Beatmap {
|
impl StaminaCheeseDetector for Beatmap {
|
||||||
fn find_cheese(&self) -> Vec<bool> {
|
fn find_cheese(&self) -> Vec<bool> {
|
||||||
let mut cheese = vec![false; self.hit_objects.len()];
|
let mut cheese = vec![false; self.hit_objects.len()];
|
||||||
|
|
||||||
self.find_rolls(3, &mut cheese);
|
self.find_rolls::<3, 6>(&mut cheese);
|
||||||
self.find_rolls(4, &mut cheese);
|
self.find_rolls::<4, 8>(&mut cheese);
|
||||||
|
|
||||||
self.find_tl_tap(0, true, &mut cheese);
|
self.find_tl_tap::<0, true>(&mut cheese);
|
||||||
self.find_tl_tap(1, true, &mut cheese);
|
self.find_tl_tap::<1, true>(&mut cheese);
|
||||||
self.find_tl_tap(0, false, &mut cheese);
|
self.find_tl_tap::<0, false>(&mut cheese);
|
||||||
self.find_tl_tap(1, false, &mut cheese);
|
self.find_tl_tap::<1, false>(&mut cheese);
|
||||||
|
|
||||||
cheese
|
cheese
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_rolls(&self, pattern_len: usize, cheese: &mut [bool]) {
|
fn find_rolls<const PATTERN_LEN: usize, const DOUBLE_PATTERN_LEN: usize>(
|
||||||
let mut history = LimitedQueue::new(2 * pattern_len);
|
&self,
|
||||||
|
cheese: &mut [bool],
|
||||||
|
) {
|
||||||
|
let mut history: LimitedQueue<u8, DOUBLE_PATTERN_LEN> = LimitedQueue::new();
|
||||||
|
|
||||||
let mut index_before_last_repeat = -1;
|
let mut index_before_last_repeat = -1;
|
||||||
let mut last_mark_end = 0;
|
let mut last_mark_end = 0;
|
||||||
@@ -38,7 +44,7 @@ impl StaminaCheeseDetector for Beatmap {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let contains = contains_pattern_repeat(&history, pattern_len);
|
let contains = contains_pattern_repeat::<PATTERN_LEN, DOUBLE_PATTERN_LEN>(&history);
|
||||||
|
|
||||||
if !contains {
|
if !contains {
|
||||||
index_before_last_repeat = (i + 1 - history.len()) as isize;
|
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 tl_len = -2;
|
||||||
let mut last_mark_end = 0;
|
let mut last_mark_end = 0;
|
||||||
|
|
||||||
for (i, &sound) in self.sounds.iter().enumerate().skip(parity).step_by(2) {
|
for (i, &sound) in self.sounds.iter().enumerate().skip(PARITY).step_by(2) {
|
||||||
if sound.is_rim() == is_rin {
|
if sound.is_rim() == IS_RIN {
|
||||||
tl_len += 2;
|
tl_len += 2;
|
||||||
} else {
|
} else {
|
||||||
tl_len = -2;
|
tl_len = -2;
|
||||||
@@ -73,11 +79,8 @@ impl StaminaCheeseDetector for Beatmap {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mark_as_cheese(
|
let start = (i as isize + 1 - tl_len).max(last_mark_end as isize);
|
||||||
(i as isize + 1 - tl_len).max(last_mark_end as isize) as usize,
|
mark_as_cheese(start as usize, i, cheese);
|
||||||
i,
|
|
||||||
cheese,
|
|
||||||
);
|
|
||||||
|
|
||||||
last_mark_end = i;
|
last_mark_end = i;
|
||||||
}
|
}
|
||||||
@@ -94,8 +97,10 @@ fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn contains_pattern_repeat(history: &LimitedQueue<u8>, pattern_len: usize) -> bool {
|
fn contains_pattern_repeat<const PATTERN_LEN: usize, const DOUBLE_PATTERN_LEN: usize>(
|
||||||
for (&curr, &to_compare) in history.iter().zip(history.iter().skip(pattern_len)) {
|
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() {
|
if curr.is_rim() != to_compare.is_rim() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user