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
- osu & fruits:
- Fixed certain slider patterns
- Fixed specific slider patterns
- Optimized Bezier, Catmull, and other small things
- fruits:
- Fixed tick timing for reverse sliders
- taiko:
- Micro optimizations
# v0.2.1
- 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
}
#[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]
pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
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 control_point;
mod error;
@@ -458,7 +464,7 @@ macro_rules! parse_hitobjects_body {
match path_type {
PathType::Linear if curve_points.len() % 2 == 0 => {
// 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) {
curve_points.remove(i);
}
@@ -467,7 +473,7 @@ macro_rules! parse_hitobjects_body {
}
}
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;
}
},
@@ -775,22 +781,6 @@ fn split_colon(line: &str) -> Option<(&str, &str)> {
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.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PathType {
+11 -2
View File
@@ -4,38 +4,47 @@ 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,
},
@@ -43,6 +52,7 @@ static COMMON_RHYTHMS: [HitObjectRhythm; 9] = [
#[derive(Copy, Clone, Debug)]
pub(crate) struct HitObjectRhythm {
id: u8,
ratio: f32,
pub(crate) difficulty: f32,
}
@@ -50,8 +60,7 @@ pub(crate) struct HitObjectRhythm {
impl PartialEq for HitObjectRhythm {
#[inline]
fn eq(&self, other: &Self) -> bool {
(self.ratio - other.ratio).abs() <= f32::EPSILON
&& (self.difficulty - other.difficulty).abs() <= f32::EPSILON
self.id == other.id
}
}
+44 -14
View File
@@ -1,5 +1,7 @@
use std::cmp::Ordering;
use std::iter::{Cycle, Skip, Take};
use std::ops::Index;
use std::slice::Iter;
pub(crate) struct LimitedQueue<T> {
queue: Vec<T>,
@@ -8,7 +10,6 @@ pub(crate) struct LimitedQueue<T> {
}
impl<T> LimitedQueue<T> {
/// Panics if `capacity` is zero.
#[inline]
pub(crate) fn new(capacity: usize) -> Self {
Self {
@@ -25,7 +26,7 @@ impl<T> LimitedQueue<T> {
if self.queue.len() == capacity {
self.start = (self.start + 1) % capacity;
self.queue[self.end as usize] = elem;
self.queue[self.end] = elem;
} else {
self.queue.push(elem);
}
@@ -38,7 +39,7 @@ impl<T> LimitedQueue<T> {
#[inline]
pub(crate) fn last(&self) -> Option<&T> {
self.queue.get(self.end as usize)
self.queue.get(self.end)
}
#[inline]
@@ -52,21 +53,30 @@ impl<T> LimitedQueue<T> {
pub(crate) fn full(&self) -> bool {
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> {
pub(crate) fn min(&self) -> Option<&T> {
let mut iter = self.queue.iter();
let first = iter.next()?;
let min = iter.fold(first, |min, next| match min.partial_cmp(next) {
Some(Ordering::Less) => min,
Some(Ordering::Equal) => min,
Some(Ordering::Greater) => next,
None => min,
});
Some(min)
self.queue
.iter()
.reduce(|min, next| match min.partial_cmp(next) {
Some(Ordering::Less) => min,
Some(Ordering::Equal) => min,
Some(Ordering::Greater) => next,
None => min,
})
}
}
@@ -78,3 +88,23 @@ impl<T> Index<usize> for LimitedQueue<T> {
&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 std::ops::Index;
const RHYTHM_STRAIN_DECAY: f32 = 0.96;
const MOST_RECENT_PATTERNS_TO_COMPARE: usize = 2;
@@ -102,9 +104,10 @@ impl SkillKind {
for start in iter {
let different_pattern = (0..MOST_RECENT_PATTERNS_TO_COMPARE).any(|i| {
mono_history[start + i]
!= mono_history
[mono_history.len() + i - MOST_RECENT_PATTERNS_TO_COMPARE]
let to_compare =
mono_history.len() + i - MOST_RECENT_PATTERNS_TO_COMPARE;
mono_history.index(start + i) != mono_history.index(to_compare)
});
if different_pattern {
@@ -168,17 +171,18 @@ impl SkillKind {
for start in iter {
let different_pattern = (0..most_recent_patterns_to_compare).any(|i| {
rhythm_history[start + i].1
!= rhythm_history
[rhythm_history.len() + i - most_recent_patterns_to_compare]
.1
let to_compare =
rhythm_history.len() + i - most_recent_patterns_to_compare;
rhythm_history.index(start + i).1 != rhythm_history.index(to_compare).1
});
if different_pattern {
continue;
}
reps_penalty *= repetition_penalty(current.idx - rhythm_history[start].0);
reps_penalty *=
repetition_penalty(current.idx - rhythm_history.index(start).0);
break;
}
+7 -8
View File
@@ -1,5 +1,4 @@
use super::{LimitedQueue, Rim};
use crate::{parse::HitObject, Beatmap};
const ROLL_MIN_REPETITIONS: usize = 12;
@@ -12,7 +11,6 @@ pub(crate) trait StaminaCheeseDetector {
}
impl StaminaCheeseDetector for Beatmap {
// TODO: Optimize
fn find_cheese(&self) -> Vec<bool> {
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 last_mark_end = 0;
for i in 0..self.hit_objects.len() {
history.push(&self.hit_objects[i]);
for (i, h) in self.hit_objects.iter().enumerate() {
history.push(h);
if !history.full() {
continue;
@@ -44,6 +42,7 @@ impl StaminaCheeseDetector for Beatmap {
if !contains {
index_before_last_repeat = (i + 1 - history.len()) as isize;
continue;
}
@@ -63,8 +62,8 @@ impl StaminaCheeseDetector for Beatmap {
let mut tl_len = -2;
let mut last_mark_end = 0;
for i in (parity..self.hit_objects.len()).step_by(2) {
if self.hit_objects[i].is_rim() == is_rin {
for (i, h) in self.hit_objects.iter().enumerate().skip(parity).step_by(2) {
if h.is_rim() == is_rin {
tl_len += 2;
} else {
tl_len = -2;
@@ -96,8 +95,8 @@ fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
#[inline]
fn contains_pattern_repeat(history: &LimitedQueue<&HitObject>, pattern_len: usize) -> bool {
for j in 0..pattern_len {
if history[j].is_rim() != history[j + pattern_len].is_rim() {
for (&curr, &to_compare) in history.iter().zip(history.iter().skip(pattern_len)) {
if curr.is_rim() != to_compare.is_rim() {
return false;
}
}