fixed & extended test cases

This commit is contained in:
MaxOhn
2022-10-21 11:07:29 +02:00
parent 17ce0ef2c9
commit 335f0a7dde
27 changed files with 1677 additions and 898 deletions
+45 -47
View File
@@ -49,7 +49,9 @@ use super::{
pub struct OsuGradualDifficultyAttributes {
pub(crate) idx: usize,
mods: u32,
attributes: OsuDifficultyAttributes,
attrs: OsuDifficultyAttributes,
// Unused but `diff_objects`' lifetimes secretly depend on it
#[allow(unused)]
hit_objects: Vec<OsuObject>,
diff_objects: Vec<OsuDifficultyObject<'static>>,
skills: [Box<dyn Skill>; 4],
@@ -60,8 +62,8 @@ impl Debug for OsuGradualDifficultyAttributes {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("OsuGradualDifficultyAttributes")
.field("idx", &self.idx)
.field("attributes", &self.attributes)
.field("hit_objects", &self.hit_objects)
.field("attrs", &self.attrs)
.field("diff_objects", &self.diff_objects)
.field("skills", &"<cannot be displayed>")
.finish()
}
@@ -75,7 +77,7 @@ impl OsuGradualDifficultyAttributes {
let scaling_factor = ScalingFactor::new(map_attrs.cs);
let hr = mods.hr();
let hit_window = 2.0 * map_attrs.hit_windows.od;
let time_preempt = map_attrs.hit_windows.ar;
let time_preempt = (map_attrs.hit_windows.ar * clock_rate) as f32 as f64;
// * Preempt time can go below 450ms. Normally, this is achieved via the DT mod
// * which uniformly speeds up all animations game wide regardless of AR.
@@ -100,7 +102,7 @@ impl OsuGradualDifficultyAttributes {
let mut params = ObjectParameters {
map,
attributes: &mut attrs,
attrs: &mut attrs,
ticks: Vec::new(),
curve_bufs: CurveBuffers::default(),
};
@@ -140,7 +142,7 @@ impl OsuGradualDifficultyAttributes {
return Self {
idx: 0,
mods,
attributes: attrs,
attrs,
hit_objects: Vec::new(),
diff_objects: Vec::new(),
skills,
@@ -149,6 +151,8 @@ impl OsuGradualDifficultyAttributes {
}
};
Self::increment_combo(last, &mut attrs);
let mut last_last = None;
// Prepare `lazy_travel_dist` and `lazy_end_pos` for `last` manually
@@ -182,13 +186,26 @@ impl OsuGradualDifficultyAttributes {
Self {
idx: 0,
mods,
attributes: attrs,
attrs,
diff_objects: extend_lifetime(diff_objects),
hit_objects,
skills,
hit_window,
}
}
fn increment_combo(h: &OsuObject, attrs: &mut OsuDifficultyAttributes) {
attrs.max_combo += 1;
match &h.kind {
OsuObjectKind::Circle => attrs.n_circles += 1,
OsuObjectKind::Slider(slider) => {
attrs.n_sliders += 1;
attrs.max_combo += slider.nested_len();
}
OsuObjectKind::Spinner { .. } => attrs.n_spinners += 1,
}
}
}
fn extend_lifetime(
@@ -210,18 +227,7 @@ impl Iterator for OsuGradualDifficultyAttributes {
skill.process(curr, &self.diff_objects, self.hit_window);
}
let mut attrs = self.attributes.clone();
attrs.max_combo += 1;
match &curr.base.kind {
OsuObjectKind::Circle => attrs.n_circles += 1,
OsuObjectKind::Slider(slider) => {
attrs.n_sliders += 1;
attrs.max_combo += slider.nested_len();
}
OsuObjectKind::Spinner { .. } => attrs.n_spinners += 1,
}
Self::increment_combo(curr.base, &mut self.attrs);
let [aim, aim_no_sliders, speed, flashlight] = &self.skills;
@@ -289,6 +295,7 @@ impl Iterator for OsuGradualDifficultyAttributes {
0.0
};
let mut attrs = self.attrs.clone();
attrs.aim = aim_rating;
attrs.speed = speed_rating;
attrs.flashlight = flashlight_rating;
@@ -301,41 +308,32 @@ impl Iterator for OsuGradualDifficultyAttributes {
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.hit_objects.len() - self.idx;
let len = self.len();
(len, Some(len))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let skip = n.min(self.len()).saturating_sub(1);
for _ in 0..skip {
let curr = self.diff_objects.get(self.idx)?;
self.idx += 1;
for skill in self.skills.iter_mut() {
skill.process(curr, &self.diff_objects, self.hit_window);
}
Self::increment_combo(curr.base, &mut self.attrs);
}
self.next()
}
}
impl ExactSizeIterator for OsuGradualDifficultyAttributes {
#[inline]
fn len(&self) -> usize {
self.hit_objects.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_map() {
let map = Beatmap::default();
let mut attributes = OsuGradualDifficultyAttributes::new(&map, 0);
assert!(attributes.next().is_none());
}
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
#[test]
fn iter_end_eq_regular() {
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
let mods = 64;
let regular = crate::OsuStars::new(&map).mods(mods).calculate();
let iter_end = OsuGradualDifficultyAttributes::new(&map, mods)
.last()
.expect("empty iter");
assert_eq!(regular, iter_end);
self.diff_objects.len() - self.idx
}
}
+5 -106
View File
@@ -93,7 +93,7 @@ impl OsuScoreState {
/// // Then comes a miss.
/// // Note that state's max combo won't be incremented for
/// // the next few objects because the combo is reset.
/// state.misses += 1;
/// state.n_misses += 1;
/// # /*
/// let performance = gradual_perf.process_next_object(state.clone()).unwrap();
/// println!("PP: {}", performance.pp);
@@ -126,7 +126,7 @@ impl OsuScoreState {
/// state.n300 = ...
/// state.n100 = ...
/// state.n50 = ...
/// state.misses = ...
/// state.n_misses = ...
/// let final_performance = gradual_perf.process_next_n_objects(state.clone(), usize::MAX).unwrap();
/// println!("PP: {}", performance.pp);
/// # */
@@ -174,118 +174,17 @@ impl<'map> OsuGradualPerformanceAttributes<'map> {
state: OsuScoreState,
n: usize,
) -> Option<OsuPerformanceAttributes> {
let n = n.min(self.difficulty.len()).saturating_sub(1);
let difficulty = self.difficulty.nth(n)?;
let sub = (self.difficulty.idx == 0) as usize;
let difficulty = self.difficulty.nth(n.saturating_sub(sub))?;
let performance = self
.performance
.clone()
.attributes(difficulty)
.state(state)
.passed_objects(self.difficulty.idx)
.passed_objects(self.difficulty.idx + 1)
.calculate();
Some(performance)
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
#[test]
fn correct_empty() {
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
let mods = 64;
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
let state = OsuScoreState::default();
assert!(gradual
.process_next_n_objects(state.clone(), usize::MAX)
.is_some());
assert!(gradual.process_next_object(state).is_none());
}
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
#[test]
fn next_and_next_n() {
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
let mods = 64;
let state = OsuScoreState::default();
let mut gradual1 = OsuGradualPerformanceAttributes::new(&map, mods);
let mut gradual2 = OsuGradualPerformanceAttributes::new(&map, mods);
for _ in 0..20 {
let _ = gradual1.process_next_object(state.clone());
let _ = gradual2.process_next_object(state.clone());
}
let n = 80;
for _ in 1..n {
let _ = gradual1.process_next_object(state.clone());
}
let state = OsuScoreState {
max_combo: 122,
n300: 88,
n100: 8,
n50: 2,
n_misses: 2,
};
let next = gradual1.process_next_object(state.clone());
let next_n = gradual2.process_next_n_objects(state, n);
assert_eq!(next_n, next);
}
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
#[test]
fn gradual_end_eq_regular() {
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
let mods = 64;
let regular = OsuPP::new(&map).mods(mods).calculate();
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
let state = OsuScoreState {
max_combo: 909,
n300: 601,
n100: 0,
n50: 0,
n_misses: 0,
};
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
assert_eq!(regular, gradual_end);
}
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
#[test]
fn gradual_eq_regular_passed() {
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
let mods = 64;
let n = 100;
let regular = OsuPP::new(&map).mods(mods).passed_objects(n).calculate();
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
let state = OsuScoreState {
max_combo: 122,
n300: 100,
n100: 0,
n50: 0,
n_misses: 0,
};
let gradual = gradual.process_next_n_objects(state, n).unwrap();
assert_eq!(regular, gradual);
}
}
+4 -4
View File
@@ -260,7 +260,7 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
400.0 * (time_preempt / PREEMPT_MIN).min(1.0)
};
let mut attributes = OsuDifficultyAttributes {
let mut attrs = OsuDifficultyAttributes {
ar: map_attrs.ar,
hp: map_attrs.hp,
od: map_attrs.od,
@@ -269,7 +269,7 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
let mut params = ObjectParameters {
map,
attributes: &mut attributes,
attrs: &mut attrs,
ticks: Vec::new(),
curve_bufs: CurveBuffers::default(),
};
@@ -301,7 +301,7 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
let last = match hit_objects.next() {
Some(prev) => prev,
None => return (skills, attributes),
None => return (skills, attrs),
};
let mut last_last = None;
@@ -340,7 +340,7 @@ fn calculate_skills(params: OsuStars<'_>) -> ([Box<dyn Skill>; 4], OsuDifficulty
}
}
(skills, attributes)
(skills, attrs)
}
fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
+7 -7
View File
@@ -86,7 +86,7 @@ pub(crate) enum NestedObjectKind {
pub(crate) struct ObjectParameters<'a> {
pub(crate) map: &'a Beatmap,
pub(crate) attributes: &'a mut OsuDifficultyAttributes,
pub(crate) attrs: &'a mut OsuDifficultyAttributes,
pub(crate) ticks: Vec<(Pos2, f64)>,
pub(crate) curve_bufs: CurveBuffers,
}
@@ -95,17 +95,17 @@ impl OsuObject {
pub(crate) fn new(h: &HitObject, params: &mut ObjectParameters<'_>) -> Option<Self> {
let ObjectParameters {
map,
attributes,
attrs,
ticks,
curve_bufs,
} = params;
attributes.max_combo += 1; // hitcircle, slider head, or spinner
attrs.max_combo += 1; // hitcircle, slider head, or spinner
let pos = h.pos;
let obj = match &h.kind {
HitObjectKind::Circle => {
attributes.n_circles += 1;
attrs.n_circles += 1;
Self {
start_time: h.start_time,
@@ -121,7 +121,7 @@ impl OsuObject {
control_points,
..
} => {
attributes.n_sliders += 1;
attrs.n_sliders += 1;
let timing_point = map.timing_point_at(h.start_time);
let difficulty_point = map.difficulty_point_at(h.start_time).unwrap_or_default();
@@ -283,7 +283,7 @@ impl OsuObject {
_ => nested_objects.push(legacy_last_tick),
};
attributes.max_combo += nested_objects.len();
attrs.max_combo += nested_objects.len();
let last_time = nested_objects
.last()
@@ -318,7 +318,7 @@ impl OsuObject {
}
}
HitObjectKind::Spinner { end_time } => {
attributes.n_spinners += 1;
attrs.n_spinners += 1;
Self {
start_time: h.start_time,
+29 -49
View File
@@ -21,15 +21,15 @@ use crate::{
/// let pp_result = OsuPP::new(&map)
/// .mods(8 + 64) // HDDT
/// .combo(1234)
/// .misses(1)
/// .n_misses(1)
/// .accuracy(98.5) // should be set last
/// .calculate();
///
/// println!("PP: {} | Stars: {}", pp_result.pp(), pp_result.stars());
///
/// let next_result = OsuPP::new(&map)
/// .attributes(pp_result) // reusing previous results for performance
/// .mods(8 + 64) // has to be the same to reuse attributes
/// .attributes(pp_result) // reusing previous results for performance
/// .mods(8 + 64) // has to be the same to reuse attributes
/// .accuracy(99.5)
/// .calculate();
///
@@ -739,13 +739,17 @@ impl OsuAttributeProvider for PerformanceAttributes {
}
}
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
#[cfg(test)]
mod test {
use super::*;
use crate::Beatmap;
fn test_attrs() -> OsuDifficultyAttributes {
OsuDifficultyAttributes {
fn test_data() -> (Beatmap, OsuDifficultyAttributes) {
let path = "./maps/2785319.osu";
let map = Beatmap::from_path(path).unwrap();
let attrs = OsuDifficultyAttributes {
aim: 2.8693628443424104,
speed: 2.533869745015772,
flashlight: 2.288770487900865,
@@ -759,38 +763,14 @@ mod test {
n_spinners: 1,
stars: 5.669858729379631,
max_combo: 909,
}
}
};
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
fn test_map() -> (Beatmap, OsuDifficultyAttributes) {
let path = "./maps/2785319.osu";
let map = Beatmap::from_path(path).unwrap();
(map, test_attrs())
}
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
async fn test_map() -> (Beatmap, OsuDifficultyAttributes) {
let path = "./maps/2785319.osu";
let map = Beatmap::from_path(path).await.unwrap();
(map, test_attrs())
}
#[rustfmt::skip]
macro_rules! test_data {
() => {{
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
{ test_map() }
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
{ test_map().await }
}};
(map, attrs)
}
#[test]
fn osu_hitresults_n300_n100_n_misses_best() {
let (map, attrs) = test_data!();
fn hitresults_n300_n100_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -814,8 +794,8 @@ mod test {
}
#[test]
fn osu_hitresults_n300_n50_n_misses_best() {
let (map, attrs) = test_data!();
fn hitresults_n300_n50_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -839,8 +819,8 @@ mod test {
}
#[test]
fn osu_hitresults_n50_n_misses_worst() {
let (map, attrs) = test_data!();
fn hitresults_n50_n_misses_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -863,8 +843,8 @@ mod test {
}
#[test]
fn osu_hitresults_n300_n100_n50_n_misses_worst() {
let (map, attrs) = test_data!();
fn hitresults_n300_n100_n50_n_misses_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -889,8 +869,8 @@ mod test {
}
#[test]
fn osu_hitresults_acc_n_misses_best() {
let (map, attrs) = test_data!();
fn hitresults_acc_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -919,8 +899,8 @@ mod test {
}
#[test]
fn osu_hitresults_acc_n100_n_misses_best() {
let (map, attrs) = test_data!();
fn hitresults_acc_n100_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -950,8 +930,8 @@ mod test {
}
#[test]
fn osu_hitresults_acc_n50_n_misses_best() {
let (map, attrs) = test_data!();
fn hitresults_acc_n50_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -981,8 +961,8 @@ mod test {
}
#[test]
fn osu_hitresults_acc_best() {
let (map, attrs) = test_data!();
fn hitresults_acc_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
@@ -1010,8 +990,8 @@ mod test {
}
#[test]
fn osu_hitresults_acc_worst() {
let (map, attrs) = test_data!();
fn hitresults_acc_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)