taiko: micro optimizations
This commit is contained in:
+5
-1
@@ -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:
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user