taiko: micro optimizations

This commit is contained in:
MaxOhn
2021-04-29 01:33:08 +02:00
parent 2c913d5f3b
commit 9f984ac4e2
7 changed files with 103 additions and 51 deletions
+5 -1
View File
@@ -1,11 +1,15 @@
## Upcoming ## Upcoming
- osu & fruits: - osu & fruits:
- Fixed certain slider patterns - Fixed specific slider patterns
- Optimized Bezier, Catmull, and other small things
- fruits: - fruits:
- Fixed tick timing for reverse sliders - Fixed tick timing for reverse sliders
- taiko:
- Micro optimizations
# v0.2.1 # v0.2.1
- parse & osu: - parse & osu:
+16
View File
@@ -96,6 +96,22 @@ pub(crate) fn is_left(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) < 0.0 ((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) < 0.0
} }
#[inline]
pub(crate) fn is_linear(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)).abs() <= f32::EPSILON
}
#[inline]
pub(crate) fn valid_linear(points: &[Pos2]) -> bool {
for (curr, next) in points.iter().skip(1).zip(points.iter().skip(2)).step_by(2) {
if curr != next {
return false;
}
}
true
}
#[inline] #[inline]
pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 { pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
let (sin, cos) = theta.sin_cos(); let (sin, cos) = theta.sin_cos();
+8 -18
View File
@@ -1,3 +1,9 @@
#[cfg(any(
feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))]
use crate::math_util;
mod attributes; mod attributes;
mod control_point; mod control_point;
mod error; mod error;
@@ -458,7 +464,7 @@ macro_rules! parse_hitobjects_body {
match path_type { match path_type {
PathType::Linear if curve_points.len() % 2 == 0 => { PathType::Linear if curve_points.len() % 2 == 0 => {
// Assert that the points are of the form A|B|B|C|C|E // Assert that the points are of the form A|B|B|C|C|E
if valid_linear(&curve_points) { if math_util::valid_linear(&curve_points) {
for i in (2..curve_points.len() - 1).rev().step_by(2) { for i in (2..curve_points.len() - 1).rev().step_by(2) {
curve_points.remove(i); curve_points.remove(i);
} }
@@ -467,7 +473,7 @@ macro_rules! parse_hitobjects_body {
} }
} }
PathType::PerfectCurve if curve_points.len() == 3 => { PathType::PerfectCurve if curve_points.len() == 3 => {
if is_linear(curve_points[0], curve_points[1], curve_points[2]) { if math_util::is_linear(curve_points[0], curve_points[1], curve_points[2]) {
path_type = PathType::Linear; path_type = PathType::Linear;
} }
}, },
@@ -775,22 +781,6 @@ fn split_colon(line: &str) -> Option<(&str, &str)> {
Some((split.next()?, split.next()?.trim())) Some((split.next()?, split.next()?.trim()))
} }
#[inline]
fn valid_linear(points: &[Pos2]) -> bool {
for i in (1..points.len() - 1).step_by(2) {
if points[i] != points[i + 1] {
return false;
}
}
true
}
#[inline]
fn is_linear(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
((p1.y - p0.y) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.y - p0.y)).abs() <= f32::EPSILON
}
/// The type of curve of a slider. /// The type of curve of a slider.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PathType { pub enum PathType {
+11 -2
View File
@@ -4,38 +4,47 @@ use std::cmp::Ordering;
static COMMON_RHYTHMS: [HitObjectRhythm; 9] = [ static COMMON_RHYTHMS: [HitObjectRhythm; 9] = [
HitObjectRhythm { HitObjectRhythm {
id: 0,
ratio: 1.0, ratio: 1.0,
difficulty: 0.0, difficulty: 0.0,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 1,
ratio: 2.0 / 1.0, ratio: 2.0 / 1.0,
difficulty: 0.3, difficulty: 0.3,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 2,
ratio: 1.0 / 2.0, ratio: 1.0 / 2.0,
difficulty: 0.5, difficulty: 0.5,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 3,
ratio: 3.0 / 1.0, ratio: 3.0 / 1.0,
difficulty: 0.3, difficulty: 0.3,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 4,
ratio: 1.0 / 3.0, ratio: 1.0 / 3.0,
difficulty: 0.35, difficulty: 0.35,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 5,
ratio: 3.0 / 2.0, ratio: 3.0 / 2.0,
difficulty: 0.6, difficulty: 0.6,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 6,
ratio: 2.0 / 3.0, ratio: 2.0 / 3.0,
difficulty: 0.4, difficulty: 0.4,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 7,
ratio: 5.0 / 4.0, ratio: 5.0 / 4.0,
difficulty: 0.5, difficulty: 0.5,
}, },
HitObjectRhythm { HitObjectRhythm {
id: 8,
ratio: 4.0 / 5.0, ratio: 4.0 / 5.0,
difficulty: 0.7, difficulty: 0.7,
}, },
@@ -43,6 +52,7 @@ static COMMON_RHYTHMS: [HitObjectRhythm; 9] = [
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub(crate) struct HitObjectRhythm { pub(crate) struct HitObjectRhythm {
id: u8,
ratio: f32, ratio: f32,
pub(crate) difficulty: f32, pub(crate) difficulty: f32,
} }
@@ -50,8 +60,7 @@ pub(crate) struct HitObjectRhythm {
impl PartialEq for HitObjectRhythm { impl PartialEq for HitObjectRhythm {
#[inline] #[inline]
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
(self.ratio - other.ratio).abs() <= f32::EPSILON self.id == other.id
&& (self.difficulty - other.difficulty).abs() <= f32::EPSILON
} }
} }
+44 -14
View File
@@ -1,5 +1,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::iter::{Cycle, Skip, Take};
use std::ops::Index; use std::ops::Index;
use std::slice::Iter;
pub(crate) struct LimitedQueue<T> { pub(crate) struct LimitedQueue<T> {
queue: Vec<T>, queue: Vec<T>,
@@ -8,7 +10,6 @@ pub(crate) struct LimitedQueue<T> {
} }
impl<T> LimitedQueue<T> { impl<T> LimitedQueue<T> {
/// Panics if `capacity` is zero.
#[inline] #[inline]
pub(crate) fn new(capacity: usize) -> Self { pub(crate) fn new(capacity: usize) -> Self {
Self { Self {
@@ -25,7 +26,7 @@ impl<T> LimitedQueue<T> {
if self.queue.len() == capacity { if self.queue.len() == capacity {
self.start = (self.start + 1) % capacity; self.start = (self.start + 1) % capacity;
self.queue[self.end as usize] = elem; self.queue[self.end] = elem;
} else { } else {
self.queue.push(elem); self.queue.push(elem);
} }
@@ -38,7 +39,7 @@ impl<T> LimitedQueue<T> {
#[inline] #[inline]
pub(crate) fn last(&self) -> Option<&T> { pub(crate) fn last(&self) -> Option<&T> {
self.queue.get(self.end as usize) self.queue.get(self.end)
} }
#[inline] #[inline]
@@ -52,21 +53,30 @@ impl<T> LimitedQueue<T> {
pub(crate) fn full(&self) -> bool { pub(crate) fn full(&self) -> bool {
self.queue.len() == self.queue.capacity() self.queue.len() == self.queue.capacity()
} }
#[inline]
pub(crate) fn iter(&self) -> LimitedQueueIter<T> {
let iter = self
.queue
.iter()
.cycle()
.skip(self.start)
.take(self.queue.len());
LimitedQueueIter { iter }
}
} }
impl<T: PartialOrd> LimitedQueue<T> { impl<T: PartialOrd> LimitedQueue<T> {
pub(crate) fn min(&self) -> Option<&T> { pub(crate) fn min(&self) -> Option<&T> {
let mut iter = self.queue.iter(); self.queue
.iter()
let first = iter.next()?; .reduce(|min, next| match min.partial_cmp(next) {
let min = iter.fold(first, |min, next| match min.partial_cmp(next) { Some(Ordering::Less) => min,
Some(Ordering::Less) => min, Some(Ordering::Equal) => min,
Some(Ordering::Equal) => min, Some(Ordering::Greater) => next,
Some(Ordering::Greater) => next, None => min,
None => min, })
});
Some(min)
} }
} }
@@ -78,3 +88,23 @@ impl<T> Index<usize> for LimitedQueue<T> {
&self.queue[(self.start + idx) % self.queue.capacity()] &self.queue[(self.start + idx) % self.queue.capacity()]
} }
} }
pub(crate) struct LimitedQueueIter<'a, T> {
iter: Take<Skip<Cycle<Iter<'a, T>>>>,
}
impl<'a, T> Iterator for LimitedQueueIter<'a, T> {
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a, T> ExactSizeIterator for LimitedQueueIter<'a, T> {}
+12 -8
View File
@@ -1,5 +1,7 @@
use super::{DifficultyObject, HitObjectRhythm, LimitedQueue, Rim}; use super::{DifficultyObject, HitObjectRhythm, LimitedQueue, Rim};
use std::ops::Index;
const RHYTHM_STRAIN_DECAY: f32 = 0.96; const RHYTHM_STRAIN_DECAY: f32 = 0.96;
const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2; const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2;
@@ -102,9 +104,10 @@ impl SkillKind {
for start in iter { for start in iter {
let different_pattern = (0..MOST_RECENT_PATTERNS_TO_COMPARE).any(|i| { let different_pattern = (0..MOST_RECENT_PATTERNS_TO_COMPARE).any(|i| {
mono_history[start + i] let to_compare =
!= mono_history 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)
}); });
if different_pattern { if different_pattern {
@@ -168,17 +171,18 @@ impl SkillKind {
for start in iter { for start in iter {
let different_pattern = (0..most_recent_patterns_to_compare).any(|i| { let different_pattern = (0..most_recent_patterns_to_compare).any(|i| {
rhythm_history[start + i].1 let to_compare =
!= rhythm_history rhythm_history.len() + i - most_recent_patterns_to_compare;
[rhythm_history.len() + i - most_recent_patterns_to_compare]
.1 rhythm_history.index(start + i).1 != rhythm_history.index(to_compare).1
}); });
if different_pattern { if different_pattern {
continue; continue;
} }
reps_penalty *= repetition_penalty(current.idx - rhythm_history[start].0); reps_penalty *=
repetition_penalty(current.idx - rhythm_history.index(start).0);
break; break;
} }
+7 -8
View File
@@ -1,5 +1,4 @@
use super::{LimitedQueue, Rim}; use super::{LimitedQueue, Rim};
use crate::{parse::HitObject, Beatmap}; use crate::{parse::HitObject, Beatmap};
const ROLL_MIN_REPETITIONS: usize = 12; const ROLL_MIN_REPETITIONS: usize = 12;
@@ -12,7 +11,6 @@ pub(crate) trait StaminaCheeseDetector {
} }
impl StaminaCheeseDetector for Beatmap { impl StaminaCheeseDetector for Beatmap {
// TODO: Optimize
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()];
@@ -33,8 +31,8 @@ impl StaminaCheeseDetector for Beatmap {
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;
for i in 0..self.hit_objects.len() { for (i, h) in self.hit_objects.iter().enumerate() {
history.push(&self.hit_objects[i]); history.push(h);
if !history.full() { if !history.full() {
continue; continue;
@@ -44,6 +42,7 @@ impl StaminaCheeseDetector for Beatmap {
if !contains { if !contains {
index_before_last_repeat = (i + 1 - history.len()) as isize; index_before_last_repeat = (i + 1 - history.len()) as isize;
continue; continue;
} }
@@ -63,8 +62,8 @@ impl StaminaCheeseDetector for Beatmap {
let mut tl_len = -2; let mut tl_len = -2;
let mut last_mark_end = 0; let mut last_mark_end = 0;
for i in (parity..self.hit_objects.len()).step_by(2) { for (i, h) in self.hit_objects.iter().enumerate().skip(parity).step_by(2) {
if self.hit_objects[i].is_rim() == is_rin { if h.is_rim() == is_rin {
tl_len += 2; tl_len += 2;
} else { } else {
tl_len = -2; tl_len = -2;
@@ -96,8 +95,8 @@ fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
#[inline] #[inline]
fn contains_pattern_repeat(history: &LimitedQueue<&HitObject>, pattern_len: usize) -> bool { fn contains_pattern_repeat(history: &LimitedQueue<&HitObject>, pattern_len: usize) -> bool {
for j in 0..pattern_len { for (&curr, &to_compare) in history.iter().zip(history.iter().skip(pattern_len)) {
if history[j].is_rim() != history[j + pattern_len].is_rim() { if curr.is_rim() != to_compare.is_rim() {
return false; return false;
} }
} }