Merge branch 'next' into main
This commit is contained in:
+9
-1
@@ -1,6 +1,14 @@
|
||||
## Upcoming
|
||||
|
||||
# v0.1.1
|
||||
- No changes so far
|
||||
|
||||
# v0.2.0
|
||||
|
||||
- Async beatmap parsing through features `async_tokio` or `async_std`
|
||||
- Hide various parsing related types further inwards, i.e. `rosu_pp::parse::some_type` instead of `rosu_pp::some_type`
|
||||
- Affected types: `DifficultyPoint`, `HitObject`, `Pos2`, `TimingPoint`, `HitObjectKind`, `PathType`, `HitSound`
|
||||
|
||||
## v0.1.1
|
||||
|
||||
- parse:
|
||||
- Efficiently handle huge amounts of curvepoints
|
||||
|
||||
+22
-6
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rosu-pp"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
@@ -8,20 +8,36 @@ readme = "README.md"
|
||||
repository = "https://github.com/MaxOhn/rosu-pp"
|
||||
documentation = "https://docs.rs/rosu-pp/"
|
||||
description = "osu! difficulty and pp calculation for all modes"
|
||||
keywords = ["osu", "pp", "stars", "async", "async-std"]
|
||||
keywords = ["osu", "pp", "stars", "async"]
|
||||
|
||||
[features]
|
||||
default = ["osu", "taiko", "fruits", "mania", "no_leniency", "async_std"]
|
||||
default = ["osu", "taiko", "fruits", "mania", "no_leniency"]
|
||||
|
||||
# game modes
|
||||
osu = []
|
||||
taiko = []
|
||||
fruits = []
|
||||
mania = []
|
||||
|
||||
# osu!standard version
|
||||
all_included = []
|
||||
no_leniency = []
|
||||
no_sliders_no_leniency = []
|
||||
async_std = ["async-std"]
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1.9.0", optional = true }
|
||||
# async version
|
||||
async_std = ["async-std"]
|
||||
async_tokio = ["tokio"]
|
||||
|
||||
[dependencies.async-std]
|
||||
version = "1.9"
|
||||
optional = true
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.2"
|
||||
optional = true
|
||||
default-features = false
|
||||
features = ["io-util"]
|
||||
|
||||
[dev-dependencies.tokio]
|
||||
version = "1.2"
|
||||
features = ["fs", "rt-multi-thread"]
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes.
|
||||
|
||||
Conversions are generally not supported.
|
||||
Async is supported.
|
||||
Conversions between gamemodes are generally not supported.
|
||||
|
||||
Async is supported through features, see below.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -60,29 +61,30 @@ println!("Stars: {} | Max PP: {}", stars, max_pp);
|
||||
```
|
||||
|
||||
### With async
|
||||
Need to enable `async_std` in Cargo.toml to use async parsing `Beatmap::parse_async`.
|
||||
If either the `async_tokio` or `async_std` feature is enabled, beatmap parsing will be async.
|
||||
|
||||
```rust
|
||||
use async_std::fs::File;
|
||||
// use tokio::fs::File;
|
||||
|
||||
// Async read the map
|
||||
let file = match File::open("/path/to/file.osu").await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
// Async Parse the map
|
||||
let map = match Beatmap::parse_async(file).await {
|
||||
// Parse the map asynchronously
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
// The rest stays the same
|
||||
let result = map.pp()
|
||||
.mods(24) // HDHR
|
||||
.combo(1234)
|
||||
.misses(2)
|
||||
.accuracy(99.2)
|
||||
.calculate_async().await;
|
||||
.calculate();
|
||||
|
||||
println!("PP: {}", result.pp());
|
||||
```
|
||||
@@ -107,7 +109,26 @@ println!("PP: {}", result.pp());
|
||||
| `no_leniency` | When calculating difficulty attributes in osu!standard, ignore stack leniency but consider sliders. Solid middleground between performance and precision, hence the default version. |
|
||||
| `no_sliders_no_leniency` | When calculating difficulty attributes in osu!standard, ignore stack leniency and sliders. Best performance but slightly less precision than `no_leniency`. |
|
||||
| `all_included` | When calculating difficulty attributes in osu!standard, consider both stack leniency and sliders. Best precision but significantly worse performance than `no_leniency`. |
|
||||
| `async_std` | Enable async beatmap parsing with `async-std` |
|
||||
| `async_tokio` | Beatmap parsing will be async through a [tokio](https://github.com/tokio-rs/tokio) runtime |
|
||||
| `async_std` | Beatmap parsing will be async through an [async-std](https://github.com/async-rs/async-std) runtime |
|
||||
|
||||
### Benchmarks
|
||||
|
||||
Comparing the PP calculation speed between [osu-perf](https://gitlab.com/JackRedstonia/osu-perf/), an [oppai-ng](https://github.com/Francesco149/oppai-ng) rust binding, and rosu-pp's `no_sliders_no_leniency`:
|
||||
|
||||
<img src="./benchmark_results/crates_pp_calc.svg">
|
||||
|
||||
Comparing the PP calculation speed between rosu-pp's `all_included`, `no_leniency`, and `no_sliders_no_leniency` versions:
|
||||
|
||||
<img src="./benchmark_results/rosu_pp_calc.svg">
|
||||
|
||||
Comparing the PP (in)accuracy between rosu-pp's `all_included`, `no_leniency`, and `no_sliders_no_leniency` versions:
|
||||
|
||||
<img src="./benchmark_results/pp_inaccuracy.svg">
|
||||
|
||||
Comparing the stars (in)accuracy between rosu-pp's `all_included`, `no_leniency`, and `no_sliders_no_leniency` versions:
|
||||
|
||||
<img src="./benchmark_results/stars_inaccuracy.svg">
|
||||
|
||||
### Roadmap
|
||||
|
||||
@@ -125,4 +146,5 @@ println!("PP: {}", result.pp());
|
||||
- \[x\] mania pp
|
||||
---
|
||||
- \[x\] refactoring
|
||||
- \[x\] benchmarking
|
||||
- \[x\] benchmarking
|
||||
- \[x\] async
|
||||
@@ -1,5 +0,0 @@
|
||||
[](https://crates.io/crates/rosu-pp) [](https://docs.rs/rosu-pp)
|
||||
|
||||
# {{crate}}
|
||||
|
||||
{{readme}}
|
||||
@@ -1,6 +1,9 @@
|
||||
#![cfg(any(feature = "osu", feature = "fruits"))]
|
||||
|
||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
||||
use crate::{
|
||||
parse::{DifficultyPoint, TimingPoint},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use std::slice::Iter;
|
||||
|
||||
@@ -85,7 +88,10 @@ impl<'p> Iterator for ControlPointIter<'p> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Beatmap, ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint};
|
||||
use crate::{
|
||||
parse::{DifficultyPoint, TimingPoint},
|
||||
Beatmap, ControlPoint, ControlPointIter,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn control_point_iter() {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||
))]
|
||||
|
||||
use crate::{math_util, Pos2};
|
||||
use crate::{math_util, parse::Pos2};
|
||||
|
||||
const SLIDER_QUALITY: f32 = 50.0;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::Pos2;
|
||||
use crate::parse::Pos2;
|
||||
|
||||
const PLAYFIELD_WIDTH: f32 = 512.0;
|
||||
const BASE_SPEED: f32 = 1.0;
|
||||
|
||||
+5
-33
@@ -12,7 +12,11 @@ use movement::Movement;
|
||||
pub use pp::*;
|
||||
use slider_state::SliderState;
|
||||
|
||||
use crate::{curve::Curve, Beatmap, HitObjectKind, Mods, PathType, Pos2, StarResult, Strains};
|
||||
use crate::{
|
||||
curve::Curve,
|
||||
parse::{HitObjectKind, PathType, Pos2},
|
||||
Beatmap, Mods, StarResult, Strains,
|
||||
};
|
||||
|
||||
use std::convert::identity;
|
||||
|
||||
@@ -626,35 +630,3 @@ pub struct DifficultyAttributes {
|
||||
pub n_droplets: usize,
|
||||
pub n_tiny_droplets: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn fruits_single() {
|
||||
let file = match File::open("./maps/1587421.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let result = FruitsPP::new(&map)
|
||||
.mods(0)
|
||||
.combo(266)
|
||||
.fruits(644)
|
||||
.droplets(33)
|
||||
.tiny_droplet_misses(12)
|
||||
.misses(10)
|
||||
.calculate();
|
||||
|
||||
println!("Stars: {}", result.stars());
|
||||
println!("PP: {}", result.pp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,11 +315,6 @@ impl<'m> FruitsPP<'m> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn calculate_async(self) -> PpResult {
|
||||
self.calculate()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn combo_hits(&self) -> usize {
|
||||
self.n_fruits.unwrap_or(0) + self.n_droplets.unwrap_or(0) + self.n_misses
|
||||
|
||||
@@ -44,7 +44,10 @@ impl<'p> SliderState<'p> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
||||
use crate::{
|
||||
parse::{DifficultyPoint, TimingPoint},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::SliderState;
|
||||
|
||||
|
||||
+49
-7
@@ -2,14 +2,17 @@
|
||||
|
||||
//! A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes.
|
||||
//!
|
||||
//! Conversions are generally not supported.
|
||||
//! Conversions between gamemodes are generally not supported.
|
||||
//!
|
||||
//! Async is supported through features, see below.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! ```no_run
|
||||
//! use std::fs::File;
|
||||
//! use rosu_pp::{Beatmap, BeatmapExt};
|
||||
//!
|
||||
//! # /*
|
||||
//! let file = match File::open("/path/to/file.osu") {
|
||||
//! Ok(file) => file,
|
||||
//! Err(why) => panic!("Could not open file: {}", why),
|
||||
@@ -20,6 +23,7 @@
|
||||
//! Ok(map) => map,
|
||||
//! Err(why) => panic!("Error while parsing map: {}", why),
|
||||
//! };
|
||||
//! # */ let map = Beatmap::default();
|
||||
//!
|
||||
//! // If `BeatmapExt` is included, you can make use of
|
||||
//! // some methods on `Beatmap` to make your life simpler.
|
||||
@@ -56,6 +60,40 @@
|
||||
//! println!("Stars: {} | Max PP: {}", stars, max_pp);
|
||||
//! ```
|
||||
//!
|
||||
//! ## With async
|
||||
//! If either the `async_tokio` or `async_std` feature is enabled, beatmap parsing will be async.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use rosu_pp::{Beatmap, BeatmapExt};
|
||||
//! # /*
|
||||
//! use async_std::fs::File;
|
||||
//! # */
|
||||
//! // use tokio::fs::File;
|
||||
//!
|
||||
//! # /*
|
||||
//! let file = match File::open("/path/to/file.osu").await {
|
||||
//! Ok(file) => file,
|
||||
//! Err(why) => panic!("Could not open file: {}", why),
|
||||
//! };
|
||||
//!
|
||||
//! // Parse the map asynchronously
|
||||
//! let map = match Beatmap::parse(file).await {
|
||||
//! Ok(map) => map,
|
||||
//! Err(why) => panic!("Error while parsing map: {}", why),
|
||||
//! };
|
||||
//! # */ let map = Beatmap::default();
|
||||
//!
|
||||
//! // The rest stays the same
|
||||
//! let result = map.pp()
|
||||
//! .mods(24) // HDHR
|
||||
//! .combo(1234)
|
||||
//! .misses(2)
|
||||
//! .accuracy(99.2)
|
||||
//! .calculate();
|
||||
//!
|
||||
//! println!("PP: {}", result.pp());
|
||||
//! ```
|
||||
//!
|
||||
//! ## osu!standard versions
|
||||
//!
|
||||
//! - `all_included`: Both stack leniency & slider paths are considered so that the difficulty and pp calculation immitates osu! as close as possible. Pro: Most precise; Con: Least performant.
|
||||
@@ -76,6 +114,8 @@
|
||||
//! | `no_leniency` | When calculating difficulty attributes in osu!standard, ignore stack leniency but consider sliders. Solid middleground between performance and precision, hence the default version. |
|
||||
//! | `no_sliders_no_leniency` | When calculating difficulty attributes in osu!standard, ignore stack leniency and sliders. Best performance but slightly less precision than `no_leniency`. |
|
||||
//! | `all_included` | When calculating difficulty attributes in osu!standard, consider both stack leniency and sliders. Best precision but significantly worse performance than `no_leniency`. |
|
||||
//! | `async_tokio` | Beatmap parsing will be async through a [tokio](https://github.com/tokio-rs/tokio) runtime |
|
||||
//! | `async_std` | Beatmap parsing will be async through an [async-std](https://github.com/async-rs/async-std) runtime |
|
||||
//!
|
||||
//! ## Roadmap
|
||||
//!
|
||||
@@ -94,6 +134,7 @@
|
||||
//! ---
|
||||
//! - \[x\] refactoring
|
||||
//! - \[x\] benchmarking
|
||||
//! - \[x\] async parsing
|
||||
|
||||
#[cfg(feature = "fruits")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "fruits")))]
|
||||
@@ -111,7 +152,7 @@ pub mod osu;
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "taiko")))]
|
||||
pub mod taiko;
|
||||
|
||||
mod parse;
|
||||
pub mod parse;
|
||||
|
||||
mod pp;
|
||||
pub use pp::{AnyPP, AttributeProvider};
|
||||
@@ -139,10 +180,7 @@ pub use osu::OsuPP;
|
||||
pub use taiko::TaikoPP;
|
||||
|
||||
pub use mods::Mods;
|
||||
pub use parse::{
|
||||
Beatmap, BeatmapAttributes, DifficultyPoint, GameMode, HitObject, HitObjectKind, HitSound,
|
||||
ParseError, ParseResult, PathType, Pos2, TimingPoint,
|
||||
};
|
||||
pub use parse::{Beatmap, BeatmapAttributes, GameMode, ParseError, ParseResult};
|
||||
|
||||
pub trait BeatmapExt {
|
||||
/// Calculate the stars and other attributes of a beatmap which are required for pp calculation.
|
||||
@@ -261,6 +299,7 @@ impl BeatmapExt for Beatmap {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pp(&self) -> AnyPP {
|
||||
AnyPP::new(self)
|
||||
}
|
||||
@@ -434,3 +473,6 @@ compile_error!("Only one of the features `no_leniency`, `no_sliders_no_leniency`
|
||||
)
|
||||
))]
|
||||
compile_error!("The features `no_leniency`, `no_sliders_no_leniency`, and `all_included` should only be enabled in combination with the `osu` feature");
|
||||
|
||||
#[cfg(all(feature = "async_tokio", feature = "async_std"))]
|
||||
compile_error!("Only one of the async features `async_tokio` and `async_std` should be enabled");
|
||||
|
||||
+1
-26
@@ -6,7 +6,7 @@ mod strain;
|
||||
pub use pp::*;
|
||||
use strain::Strain;
|
||||
|
||||
use crate::{Beatmap, GameMode, HitObject, Mods, StarResult, Strains};
|
||||
use crate::{parse::HitObject, Beatmap, GameMode, Mods, StarResult, Strains};
|
||||
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
const STAR_SCALING_FACTOR: f32 = 0.018;
|
||||
@@ -168,28 +168,3 @@ impl<'o> DifficultyHitObject<'o> {
|
||||
pub struct DifficultyAttributes {
|
||||
pub stars: f32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn mania_single() {
|
||||
let file = match File::open("./maps/1355822.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let result = ManiaPP::new(&map).mods(256).calculate();
|
||||
|
||||
println!("Stars: {}", result.stars());
|
||||
println!("PP: {}", result.pp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +136,6 @@ impl<'m> ManiaPP<'m> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn calculate_async(self) -> PpResult {
|
||||
self.calculate()
|
||||
}
|
||||
|
||||
fn compute_strain(&self, score: f32, stars: f32) -> f32 {
|
||||
let mut strain_value = (5.0 * (stars / 0.2).max(1.0) - 4.0).powf(2.2) / 135.0;
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||
))]
|
||||
|
||||
use crate::Pos2;
|
||||
use crate::parse::Pos2;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
||||
|
||||
@@ -261,11 +261,6 @@ impl<'m> OsuPP<'m> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn calculate_async(self) -> PpResult {
|
||||
self.calculate()
|
||||
}
|
||||
|
||||
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
||||
/// containing stars and other attributes.
|
||||
///
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#![cfg(feature = "all_included")]
|
||||
|
||||
use super::super::DifficultyAttributes;
|
||||
use crate::Pos2;
|
||||
use crate::parse::Pos2;
|
||||
|
||||
mod difficulty_object;
|
||||
mod osu_object;
|
||||
@@ -439,30 +439,3 @@ const OSU_AR_MIN: f32 = 1800.0;
|
||||
fn difficulty_range_ar(ar: f32) -> f32 {
|
||||
crate::difficulty_range(ar, OSU_AR_MAX, OSU_AR_AVG, OSU_AR_MIN)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::OsuPP;
|
||||
use crate::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn all_included_single() {
|
||||
// TOCHECK: 114708, 1208872, 1322261
|
||||
let file = match File::open("./maps/1295309.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let result = OsuPP::new(&map).mods(0).calculate();
|
||||
|
||||
println!("Stars: {}", result.stars());
|
||||
println!("PP: {}", result.pp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use super::super::super::DifficultyAttributes;
|
||||
use super::slider_state::SliderState;
|
||||
|
||||
use crate::{curve::Curve, Beatmap, HitObject, HitObjectKind, PathType, Pos2};
|
||||
use crate::{
|
||||
curve::Curve,
|
||||
parse::{HitObject, HitObjectKind, PathType, Pos2},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ impl<'p> SliderState<'p> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
||||
use crate::{
|
||||
parse::{DifficultyPoint, TimingPoint},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::SliderState;
|
||||
|
||||
|
||||
@@ -270,35 +270,3 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
||||
strains,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::OsuPP;
|
||||
use crate::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn no_leniency_single() {
|
||||
let file = match File::open("./maps/2573164.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let result = OsuPP::new(&map)
|
||||
.mods(0)
|
||||
// .n300(1206)
|
||||
// .n100(15)
|
||||
// .n50(0)
|
||||
// .combo(1643)
|
||||
.calculate();
|
||||
|
||||
println!("Stars: {}", result.stars());
|
||||
println!("PP: {}", result.pp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use super::super::super::DifficultyAttributes;
|
||||
use super::slider_state::SliderState;
|
||||
|
||||
use crate::{curve::Curve, Beatmap, HitObject, HitObjectKind, PathType, Pos2};
|
||||
use crate::{
|
||||
curve::Curve,
|
||||
parse::{HitObject, HitObjectKind, PathType, Pos2},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ impl<'p> SliderState<'p> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
||||
use crate::{
|
||||
parse::{DifficultyPoint, TimingPoint},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::SliderState;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::HitObject;
|
||||
use crate::parse::HitObject;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@ use skill::Skill;
|
||||
use skill_kind::SkillKind;
|
||||
use slider_state::SliderState;
|
||||
|
||||
use crate::{Beatmap, HitObject, HitObjectKind, Mods, StarResult, Strains};
|
||||
use crate::{
|
||||
parse::{HitObject, HitObjectKind},
|
||||
Beatmap, Mods, StarResult, Strains,
|
||||
};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
@@ -282,29 +285,3 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
||||
strains,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::OsuPP;
|
||||
use crate::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn no_sliders_single() {
|
||||
let file = match File::open("./maps/1851299.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let result = OsuPP::new(&map).mods(2).calculate();
|
||||
|
||||
println!("Stars: {}", result.stars());
|
||||
println!("PP: {}", result.pp());
|
||||
}
|
||||
}
|
||||
|
||||
+662
-947
File diff suppressed because it is too large
Load Diff
@@ -55,20 +55,6 @@ impl<'m> AnyPP<'m> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn calculate_async(self) -> PpResult {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => f.calculate_async().await,
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(m) => m.calculate_async().await,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => o.calculate_async().await,
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => t.calculate_async().await,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`AttributeProvider`] is implemented by [`StarResult`](crate::StarResult)
|
||||
/// and [`PpResult`](crate::PpResult) meaning you can give the result of a \
|
||||
/// star calculation or the result of a pp calculation.
|
||||
@@ -126,8 +112,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// be sure to call this last before calling `calculate`.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn accuracy(self, acc: f32) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
@@ -144,8 +130,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// Specify the amount of misses of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn misses(self, misses: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
@@ -162,8 +148,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// Specify the max combo of the play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn combo(self, combo: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
@@ -180,8 +166,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// Specify the amount of 300s of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn n300(self, n300: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
@@ -198,8 +184,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// Specify the amount of 100s of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn n100(self, n100: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
@@ -216,8 +202,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// Specify the amount of 50s of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania and osu!taiko.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu"))]
|
||||
pub fn n50(self, n50: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
@@ -235,10 +221,11 @@ impl<'m> AnyPP<'m> {
|
||||
///
|
||||
/// This value is only relevant for osu!ctb for which it represent
|
||||
/// the amount of tiny droplet misses.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(feature = "fruits")]
|
||||
pub fn n_katu(self, n_katu: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.tiny_droplet_misses(n_katu)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
@@ -254,8 +241,8 @@ impl<'m> AnyPP<'m> {
|
||||
/// This value is only relevant for osu!mania.
|
||||
///
|
||||
/// On `NoMod` its between 0 and 1,000,000, on `Easy` between 0 and 500,000, etc.
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
#[cfg(feature = "mania")]
|
||||
pub fn score(self, score: u32) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{closest_rhythm, HitObjectRhythm};
|
||||
use crate::HitObject;
|
||||
use crate::parse::HitObject;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct DifficultyObject<'o> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::HitObject;
|
||||
use crate::parse::HitObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
|
||||
@@ -265,28 +265,3 @@ fn norm(p: f32, a: f32, b: f32, c: f32) -> f32 {
|
||||
pub struct DifficultyAttributes {
|
||||
pub stars: f32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn taiko_single() {
|
||||
let file = match File::open("./maps/168450.osu") {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file: {}", why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file) {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
let result = TaikoPP::new(&map).mods(64).calculate();
|
||||
|
||||
println!("Stars: {}", result.stars());
|
||||
println!("PP: {}", result.pp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,11 +181,6 @@ impl<'m> TaikoPP<'m> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn calculate_async(self) -> PpResult {
|
||||
self.calculate()
|
||||
}
|
||||
|
||||
fn compute_strain_value(&self, stars: f32) -> f32 {
|
||||
let exp_base = 5.0 * (stars / 0.0075).max(1.0) - 4.0;
|
||||
let mut strain = exp_base * exp_base / 100_000.0;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::{HitObject, HitSound};
|
||||
use crate::parse::{HitObject, HitSound};
|
||||
|
||||
pub(crate) trait Rim {
|
||||
fn is_rim(&self) -> bool;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{LimitedQueue, Rim};
|
||||
|
||||
use crate::{Beatmap, HitObject};
|
||||
use crate::{parse::HitObject, Beatmap};
|
||||
|
||||
const ROLL_MIN_REPETITIONS: usize = 12;
|
||||
const TL_MIN_REPETITIONS: isize = 16;
|
||||
|
||||
+65
-63
@@ -3,7 +3,6 @@
|
||||
extern crate rosu_pp;
|
||||
|
||||
use rosu_pp::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
struct MapResult {
|
||||
map_id: u32,
|
||||
@@ -12,8 +11,28 @@ struct MapResult {
|
||||
pp: f32,
|
||||
}
|
||||
|
||||
macro_rules! assert_result {
|
||||
($kind:expr => $result:expr, $margin:expr, $expected:ident, $map_id:ident, $mods:ident) => {
|
||||
assert!(
|
||||
($result - $expected).abs() < $margin * $expected,
|
||||
"\n{kind}:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
kind = $kind,
|
||||
calculated = $result,
|
||||
expected = $expected,
|
||||
margin = ($result - $expected).abs(),
|
||||
allowed = $margin * $expected,
|
||||
map = $map_id,
|
||||
mods = $mods
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
|
||||
#[test]
|
||||
fn fruits() {
|
||||
fn fruits_sync() {
|
||||
let star_margin = 0.0001;
|
||||
let pp_margin = 0.0001;
|
||||
|
||||
@@ -25,7 +44,7 @@ fn fruits() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)) {
|
||||
let file = match std::fs::File::open(format!("./maps/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
@@ -37,42 +56,50 @@ fn fruits() {
|
||||
|
||||
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
#[test]
|
||||
fn fruits_async_tokio() {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("could not start runtime")
|
||||
.block_on(async {
|
||||
let star_margin = 0.0001;
|
||||
let pp_margin = 0.0001;
|
||||
|
||||
for result in RESULTS {
|
||||
let MapResult {
|
||||
map_id,
|
||||
mods,
|
||||
stars,
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
#[test]
|
||||
fn fruits_async() {
|
||||
use async_std::{fs::File, task};
|
||||
|
||||
task::block_on(async {
|
||||
fn fruits_async_std() {
|
||||
async_std::task::block_on(async {
|
||||
let star_margin = 0.0001;
|
||||
let pp_margin = 0.0001;
|
||||
|
||||
@@ -84,45 +111,20 @@ fn fruits_async() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
let file = match async_std::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse_async(file).await {
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+65
-63
@@ -3,7 +3,6 @@
|
||||
extern crate rosu_pp;
|
||||
|
||||
use rosu_pp::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
struct MapResult {
|
||||
map_id: u32,
|
||||
@@ -12,8 +11,28 @@ struct MapResult {
|
||||
pp: f32,
|
||||
}
|
||||
|
||||
macro_rules! assert_result {
|
||||
($kind:expr => $result:expr, $margin:expr, $expected:ident, $map_id:ident, $mods:ident) => {
|
||||
assert!(
|
||||
($result - $expected).abs() < $margin * $expected,
|
||||
"\n{kind}:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
kind = $kind,
|
||||
calculated = $result,
|
||||
expected = $expected,
|
||||
margin = ($result - $expected).abs(),
|
||||
allowed = $margin * $expected,
|
||||
map = $map_id,
|
||||
mods = $mods
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
|
||||
#[test]
|
||||
fn mania() {
|
||||
fn mania_sync() {
|
||||
let star_margin = 0.00001;
|
||||
let pp_margin = 0.00001;
|
||||
|
||||
@@ -25,7 +44,7 @@ fn mania() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)) {
|
||||
let file = match std::fs::File::open(format!("./maps/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
@@ -37,42 +56,50 @@ fn mania() {
|
||||
|
||||
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
#[test]
|
||||
fn mania_async_tokio() {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("could not start runtime")
|
||||
.block_on(async {
|
||||
let star_margin = 0.00001;
|
||||
let pp_margin = 0.00001;
|
||||
|
||||
for result in RESULTS {
|
||||
let MapResult {
|
||||
map_id,
|
||||
mods,
|
||||
stars,
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
#[test]
|
||||
fn mania_async() {
|
||||
use async_std::{fs::File, task};
|
||||
|
||||
task::block_on(async {
|
||||
fn mania_async_std() {
|
||||
async_std::task::block_on(async {
|
||||
let star_margin = 0.00001;
|
||||
let pp_margin = 0.00001;
|
||||
|
||||
@@ -84,45 +111,20 @@ fn mania_async() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
let file = match async_std::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse_async(file).await {
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+75
-75
@@ -3,7 +3,6 @@
|
||||
extern crate rosu_pp;
|
||||
|
||||
use rosu_pp::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
struct MapResult {
|
||||
map_id: u32,
|
||||
@@ -12,9 +11,27 @@ struct MapResult {
|
||||
pp: f32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osu() {
|
||||
let margin = if cfg!(feature = "no_sliders_no_leniency") {
|
||||
macro_rules! assert_result {
|
||||
($kind:expr => $result:expr, $margin:expr, $expected:ident, $map_id:ident, $mods:ident) => {
|
||||
assert!(
|
||||
($result - $expected).abs() < $margin * $expected,
|
||||
"\n{kind}:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
kind = $kind,
|
||||
calculated = $result,
|
||||
expected = $expected,
|
||||
margin = ($result - $expected).abs(),
|
||||
allowed = $margin * $expected,
|
||||
map = $map_id,
|
||||
mods = $mods
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn margin() -> f32 {
|
||||
if cfg!(feature = "no_sliders_no_leniency") {
|
||||
0.0075
|
||||
} else if cfg!(feature = "no_leniency") {
|
||||
0.0025
|
||||
@@ -22,7 +39,13 @@ fn osu() {
|
||||
0.001
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
|
||||
#[test]
|
||||
fn osu_sync() {
|
||||
let margin = margin();
|
||||
|
||||
let star_margin = margin;
|
||||
let pp_margin = margin;
|
||||
@@ -35,7 +58,7 @@ fn osu() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)) {
|
||||
let file = match std::fs::File::open(format!("./maps/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
@@ -47,51 +70,53 @@ fn osu() {
|
||||
|
||||
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
#[test]
|
||||
fn osu_async_tokio() {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("could not start runtime")
|
||||
.block_on(async {
|
||||
let margin = margin();
|
||||
|
||||
let star_margin = margin;
|
||||
let pp_margin = margin;
|
||||
|
||||
for result in RESULTS {
|
||||
let MapResult {
|
||||
map_id,
|
||||
mods,
|
||||
stars,
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
#[test]
|
||||
fn osu_async() {
|
||||
use async_std::{fs::File, task};
|
||||
|
||||
task::block_on(async {
|
||||
let margin = if cfg!(feature = "no_sliders_no_leniency") {
|
||||
0.0075
|
||||
} else if cfg!(feature = "no_leniency") {
|
||||
0.0025
|
||||
} else if cfg!(feature = "all_included") {
|
||||
0.001
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
fn osu_async_std() {
|
||||
async_std::task::block_on(async {
|
||||
let margin = margin();
|
||||
|
||||
let star_margin = margin;
|
||||
let pp_margin = margin;
|
||||
@@ -104,45 +129,20 @@ fn osu_async() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
let file = match async_std::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse_async(file).await {
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+65
-63
@@ -3,7 +3,6 @@
|
||||
extern crate rosu_pp;
|
||||
|
||||
use rosu_pp::Beatmap;
|
||||
use std::fs::File;
|
||||
|
||||
struct MapResult {
|
||||
map_id: u32,
|
||||
@@ -12,8 +11,28 @@ struct MapResult {
|
||||
pp: f32,
|
||||
}
|
||||
|
||||
macro_rules! assert_result {
|
||||
($kind:expr => $result:expr, $margin:expr, $expected:ident, $map_id:ident, $mods:ident) => {
|
||||
assert!(
|
||||
($result - $expected).abs() < $margin * $expected,
|
||||
"\n{kind}:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
kind = $kind,
|
||||
calculated = $result,
|
||||
expected = $expected,
|
||||
margin = ($result - $expected).abs(),
|
||||
allowed = $margin * $expected,
|
||||
map = $map_id,
|
||||
mods = $mods
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
|
||||
#[test]
|
||||
fn taiko() {
|
||||
fn taiko_sync() {
|
||||
let star_margin = 0.0001;
|
||||
let pp_margin = 0.0001;
|
||||
|
||||
@@ -25,7 +44,7 @@ fn taiko() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)) {
|
||||
let file = match std::fs::File::open(format!("./maps/{}.osu", map_id)) {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
@@ -37,42 +56,50 @@ fn taiko() {
|
||||
|
||||
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
#[test]
|
||||
fn taiko_async_tokio() {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("could not start runtime")
|
||||
.block_on(async {
|
||||
let star_margin = 0.0001;
|
||||
let pp_margin = 0.0001;
|
||||
|
||||
for result in RESULTS {
|
||||
let MapResult {
|
||||
map_id,
|
||||
mods,
|
||||
stars,
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
#[test]
|
||||
fn taiko_async() {
|
||||
use async_std::{fs::File, task};
|
||||
|
||||
task::block_on(async {
|
||||
fn taiko_async_std() {
|
||||
async_std::task::block_on(async {
|
||||
let star_margin = 0.0001;
|
||||
let pp_margin = 0.0001;
|
||||
|
||||
@@ -84,45 +111,20 @@ fn taiko_async() {
|
||||
pp,
|
||||
} = result;
|
||||
|
||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
let file = match async_std::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||
Ok(file) => file,
|
||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||
};
|
||||
|
||||
let map = match Beatmap::parse_async(file).await {
|
||||
let map = match Beatmap::parse(file).await {
|
||||
Ok(map) => map,
|
||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||
};
|
||||
|
||||
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
||||
|
||||
assert!(
|
||||
(result.stars() - stars).abs() < star_margin * stars,
|
||||
"\nStars:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.stars(),
|
||||
expected = stars,
|
||||
margin = (result.stars() - stars).abs(),
|
||||
allowed = star_margin * stars,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
|
||||
assert!(
|
||||
(result.pp() - pp).abs() < pp_margin * pp,
|
||||
"\nPP:\n\
|
||||
Calculated: {calculated} | Expected: {expected}\n \
|
||||
=> {margin} margin ({allowed} allowed)\n\
|
||||
[map {map} | mods {mods}]\n",
|
||||
calculated = result.pp(),
|
||||
expected = pp,
|
||||
margin = (result.pp() - pp).abs(),
|
||||
allowed = pp_margin * pp,
|
||||
map = map_id,
|
||||
mods = mods
|
||||
);
|
||||
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user