Merge branch 'next' into main
This commit is contained in:
commit
041032ee14
35 files changed
+1092
-1458
No files matched your search
+9
-1
@@ -1,6 +1,14 @@
|
|||||||
## Upcoming
|
## 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:
|
- parse:
|
||||||
- Efficiently handle huge amounts of curvepoints
|
- Efficiently handle huge amounts of curvepoints
|
||||||
|
|||||||
+22
-6
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rosu-pp"
|
name = "rosu-pp"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
authors = ["MaxOhn <[email protected]>"]
|
authors = ["MaxOhn <[email protected]>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -8,20 +8,36 @@ readme = "README.md"
|
|||||||
repository = "https://github.com/MaxOhn/rosu-pp"
|
repository = "https://github.com/MaxOhn/rosu-pp"
|
||||||
documentation = "https://docs.rs/rosu-pp/"
|
documentation = "https://docs.rs/rosu-pp/"
|
||||||
description = "osu! difficulty and pp calculation for all modes"
|
description = "osu! difficulty and pp calculation for all modes"
|
||||||
keywords = ["osu", "pp", "stars", "async", "async-std"]
|
keywords = ["osu", "pp", "stars", "async"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["osu", "taiko", "fruits", "mania", "no_leniency", "async_std"]
|
default = ["osu", "taiko", "fruits", "mania", "no_leniency"]
|
||||||
|
|
||||||
|
# game modes
|
||||||
osu = []
|
osu = []
|
||||||
taiko = []
|
taiko = []
|
||||||
fruits = []
|
fruits = []
|
||||||
mania = []
|
mania = []
|
||||||
|
|
||||||
|
# osu!standard version
|
||||||
all_included = []
|
all_included = []
|
||||||
no_leniency = []
|
no_leniency = []
|
||||||
no_sliders_no_leniency = []
|
no_sliders_no_leniency = []
|
||||||
async_std = ["async-std"]
|
|
||||||
|
|
||||||
[dependencies]
|
# async version
|
||||||
async-std = { version = "1.9.0", optional = true }
|
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.
|
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.
|
|
||||||
|
Async is supported through features, see below.
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
@@ -60,29 +61,30 @@ println!("Stars: {} | Max PP: {}", stars, max_pp);
|
|||||||
```
|
```
|
||||||
|
|
||||||
### With async
|
### 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
|
```rust
|
||||||
use async_std::fs::File;
|
use async_std::fs::File;
|
||||||
|
// use tokio::fs::File;
|
||||||
|
|
||||||
// Async read the map
|
|
||||||
let file = match File::open("/path/to/file.osu").await {
|
let file = match File::open("/path/to/file.osu").await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file: {}", why),
|
Err(why) => panic!("Could not open file: {}", why),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Async Parse the map
|
// Parse the map asynchronously
|
||||||
let map = match Beatmap::parse_async(file).await {
|
let map = match Beatmap::parse(file).await {
|
||||||
Ok(map) => map,
|
Ok(map) => map,
|
||||||
Err(why) => panic!("Error while parsing map: {}", why),
|
Err(why) => panic!("Error while parsing map: {}", why),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The rest stays the same
|
||||||
let result = map.pp()
|
let result = map.pp()
|
||||||
.mods(24) // HDHR
|
.mods(24) // HDHR
|
||||||
.combo(1234)
|
.combo(1234)
|
||||||
.misses(2)
|
.misses(2)
|
||||||
.accuracy(99.2)
|
.accuracy(99.2)
|
||||||
.calculate_async().await;
|
.calculate();
|
||||||
|
|
||||||
println!("PP: {}", result.pp());
|
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_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`. |
|
| `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`. |
|
| `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
|
### Roadmap
|
||||||
|
|
||||||
@@ -126,3 +147,4 @@ println!("PP: {}", result.pp());
|
|||||||
---
|
---
|
||||||
- \[x\] refactoring
|
- \[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"))]
|
#![cfg(any(feature = "osu", feature = "fruits"))]
|
||||||
|
|
||||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
use crate::{
|
||||||
|
parse::{DifficultyPoint, TimingPoint},
|
||||||
|
Beatmap,
|
||||||
|
};
|
||||||
|
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
@@ -85,7 +88,10 @@ impl<'p> Iterator for ControlPointIter<'p> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{Beatmap, ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint};
|
use crate::{
|
||||||
|
parse::{DifficultyPoint, TimingPoint},
|
||||||
|
Beatmap, ControlPoint, ControlPointIter,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn control_point_iter() {
|
fn control_point_iter() {
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
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;
|
const SLIDER_QUALITY: f32 = 50.0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::Pos2;
|
use crate::parse::Pos2;
|
||||||
|
|
||||||
const PLAYFIELD_WIDTH: f32 = 512.0;
|
const PLAYFIELD_WIDTH: f32 = 512.0;
|
||||||
const BASE_SPEED: f32 = 1.0;
|
const BASE_SPEED: f32 = 1.0;
|
||||||
|
|||||||
+5
-33
@@ -12,7 +12,11 @@ use movement::Movement;
|
|||||||
pub use pp::*;
|
pub use pp::*;
|
||||||
use slider_state::SliderState;
|
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;
|
use std::convert::identity;
|
||||||
|
|
||||||
@@ -626,35 +630,3 @@ pub struct DifficultyAttributes {
|
|||||||
pub n_droplets: usize,
|
pub n_droplets: usize,
|
||||||
pub n_tiny_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]
|
#[inline]
|
||||||
fn combo_hits(&self) -> usize {
|
fn combo_hits(&self) -> usize {
|
||||||
self.n_fruits.unwrap_or(0) + self.n_droplets.unwrap_or(0) + self.n_misses
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
use crate::{
|
||||||
|
parse::{DifficultyPoint, TimingPoint},
|
||||||
|
Beatmap,
|
||||||
|
};
|
||||||
|
|
||||||
use super::SliderState;
|
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.
|
//! 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
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```no_run
|
||||||
//! use std::fs::File;
|
//! use std::fs::File;
|
||||||
//! use rosu_pp::{Beatmap, BeatmapExt};
|
//! use rosu_pp::{Beatmap, BeatmapExt};
|
||||||
//!
|
//!
|
||||||
|
//! # /*
|
||||||
//! let file = match File::open("/path/to/file.osu") {
|
//! let file = match File::open("/path/to/file.osu") {
|
||||||
//! Ok(file) => file,
|
//! Ok(file) => file,
|
||||||
//! Err(why) => panic!("Could not open file: {}", why),
|
//! Err(why) => panic!("Could not open file: {}", why),
|
||||||
@@ -20,6 +23,7 @@
|
|||||||
//! Ok(map) => map,
|
//! Ok(map) => map,
|
||||||
//! Err(why) => panic!("Error while parsing map: {}", why),
|
//! Err(why) => panic!("Error while parsing map: {}", why),
|
||||||
//! };
|
//! };
|
||||||
|
//! # */ let map = Beatmap::default();
|
||||||
//!
|
//!
|
||||||
//! // If `BeatmapExt` is included, you can make use of
|
//! // If `BeatmapExt` is included, you can make use of
|
||||||
//! // some methods on `Beatmap` to make your life simpler.
|
//! // some methods on `Beatmap` to make your life simpler.
|
||||||
@@ -56,6 +60,40 @@
|
|||||||
//! println!("Stars: {} | Max PP: {}", stars, max_pp);
|
//! 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
|
//! ## 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.
|
//! - `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_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`. |
|
//! | `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`. |
|
//! | `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
|
//! ## Roadmap
|
||||||
//!
|
//!
|
||||||
@@ -94,6 +134,7 @@
|
|||||||
//! ---
|
//! ---
|
||||||
//! - \[x\] refactoring
|
//! - \[x\] refactoring
|
||||||
//! - \[x\] benchmarking
|
//! - \[x\] benchmarking
|
||||||
|
//! - \[x\] async parsing
|
||||||
|
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "fruits")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "fruits")))]
|
||||||
@@ -111,7 +152,7 @@ pub mod osu;
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "taiko")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "taiko")))]
|
||||||
pub mod taiko;
|
pub mod taiko;
|
||||||
|
|
||||||
mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
mod pp;
|
mod pp;
|
||||||
pub use pp::{AnyPP, AttributeProvider};
|
pub use pp::{AnyPP, AttributeProvider};
|
||||||
@@ -139,10 +180,7 @@ pub use osu::OsuPP;
|
|||||||
pub use taiko::TaikoPP;
|
pub use taiko::TaikoPP;
|
||||||
|
|
||||||
pub use mods::Mods;
|
pub use mods::Mods;
|
||||||
pub use parse::{
|
pub use parse::{Beatmap, BeatmapAttributes, GameMode, ParseError, ParseResult};
|
||||||
Beatmap, BeatmapAttributes, DifficultyPoint, GameMode, HitObject, HitObjectKind, HitSound,
|
|
||||||
ParseError, ParseResult, PathType, Pos2, TimingPoint,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait BeatmapExt {
|
pub trait BeatmapExt {
|
||||||
/// Calculate the stars and other attributes of a beatmap which are required for pp calculation.
|
/// 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 {
|
fn pp(&self) -> AnyPP {
|
||||||
AnyPP::new(self)
|
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");
|
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::*;
|
pub use pp::*;
|
||||||
use strain::Strain;
|
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 SECTION_LEN: f32 = 400.0;
|
||||||
const STAR_SCALING_FACTOR: f32 = 0.018;
|
const STAR_SCALING_FACTOR: f32 = 0.018;
|
||||||
@@ -168,28 +168,3 @@ impl<'o> DifficultyHitObject<'o> {
|
|||||||
pub struct DifficultyAttributes {
|
pub struct DifficultyAttributes {
|
||||||
pub stars: f32,
|
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 {
|
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;
|
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"))
|
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||||
))]
|
))]
|
||||||
|
|
||||||
use crate::Pos2;
|
use crate::parse::Pos2;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
pub(crate) fn cpn(mut p: i32, n: i32) -> f32 {
|
||||||
|
|||||||
@@ -261,11 +261,6 @@ impl<'m> OsuPP<'m> {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn calculate_async(self) -> PpResult {
|
|
||||||
self.calculate()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
||||||
/// containing stars and other attributes.
|
/// containing stars and other attributes.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#![cfg(feature = "all_included")]
|
#![cfg(feature = "all_included")]
|
||||||
|
|
||||||
use super::super::DifficultyAttributes;
|
use super::super::DifficultyAttributes;
|
||||||
use crate::Pos2;
|
use crate::parse::Pos2;
|
||||||
|
|
||||||
mod difficulty_object;
|
mod difficulty_object;
|
||||||
mod osu_object;
|
mod osu_object;
|
||||||
@@ -439,30 +439,3 @@ const OSU_AR_MIN: f32 = 1800.0;
|
|||||||
fn difficulty_range_ar(ar: f32) -> f32 {
|
fn difficulty_range_ar(ar: f32) -> f32 {
|
||||||
crate::difficulty_range(ar, OSU_AR_MAX, OSU_AR_AVG, OSU_AR_MIN)
|
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::super::super::DifficultyAttributes;
|
||||||
use super::slider_state::SliderState;
|
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;
|
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ impl<'p> SliderState<'p> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
use crate::{
|
||||||
|
parse::{DifficultyPoint, TimingPoint},
|
||||||
|
Beatmap,
|
||||||
|
};
|
||||||
|
|
||||||
use super::SliderState;
|
use super::SliderState;
|
||||||
|
|
||||||
|
|||||||
@@ -270,35 +270,3 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
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::super::super::DifficultyAttributes;
|
||||||
use super::slider_state::SliderState;
|
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;
|
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ impl<'p> SliderState<'p> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{Beatmap, DifficultyPoint, TimingPoint};
|
use crate::{
|
||||||
|
parse::{DifficultyPoint, TimingPoint},
|
||||||
|
Beatmap,
|
||||||
|
};
|
||||||
|
|
||||||
use super::SliderState;
|
use super::SliderState;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::HitObject;
|
use crate::parse::HitObject;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ use skill::Skill;
|
|||||||
use skill_kind::SkillKind;
|
use skill_kind::SkillKind;
|
||||||
use slider_state::SliderState;
|
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;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
@@ -282,29 +285,3 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+654
-939
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)
|
/// [`AttributeProvider`] is implemented by [`StarResult`](crate::StarResult)
|
||||||
/// and [`PpResult`](crate::PpResult) meaning you can give the result of a \
|
/// and [`PpResult`](crate::PpResult) meaning you can give the result of a \
|
||||||
/// star calculation or the result of a pp calculation.
|
/// 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`.
|
/// be sure to call this last before calling `calculate`.
|
||||||
///
|
///
|
||||||
/// Irrelevant for osu!mania.
|
/// Irrelevant for osu!mania.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
|
||||||
pub fn accuracy(self, acc: f32) -> Self {
|
pub fn accuracy(self, acc: f32) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
@@ -144,8 +130,8 @@ impl<'m> AnyPP<'m> {
|
|||||||
/// Specify the amount of misses of a play.
|
/// Specify the amount of misses of a play.
|
||||||
///
|
///
|
||||||
/// Irrelevant for osu!mania.
|
/// Irrelevant for osu!mania.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
|
||||||
pub fn misses(self, misses: usize) -> Self {
|
pub fn misses(self, misses: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
@@ -162,8 +148,8 @@ impl<'m> AnyPP<'m> {
|
|||||||
/// Specify the max combo of the play.
|
/// Specify the max combo of the play.
|
||||||
///
|
///
|
||||||
/// Irrelevant for osu!mania.
|
/// Irrelevant for osu!mania.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
|
||||||
pub fn combo(self, combo: usize) -> Self {
|
pub fn combo(self, combo: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
@@ -180,8 +166,8 @@ impl<'m> AnyPP<'m> {
|
|||||||
/// Specify the amount of 300s of a play.
|
/// Specify the amount of 300s of a play.
|
||||||
///
|
///
|
||||||
/// Irrelevant for osu!mania.
|
/// Irrelevant for osu!mania.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
|
||||||
pub fn n300(self, n300: usize) -> Self {
|
pub fn n300(self, n300: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
@@ -198,8 +184,8 @@ impl<'m> AnyPP<'m> {
|
|||||||
/// Specify the amount of 100s of a play.
|
/// Specify the amount of 100s of a play.
|
||||||
///
|
///
|
||||||
/// Irrelevant for osu!mania.
|
/// Irrelevant for osu!mania.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
|
||||||
pub fn n100(self, n100: usize) -> Self {
|
pub fn n100(self, n100: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
@@ -216,8 +202,8 @@ impl<'m> AnyPP<'m> {
|
|||||||
/// Specify the amount of 50s of a play.
|
/// Specify the amount of 50s of a play.
|
||||||
///
|
///
|
||||||
/// Irrelevant for osu!mania and osu!taiko.
|
/// Irrelevant for osu!mania and osu!taiko.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(any(feature = "fruits", feature = "osu"))]
|
|
||||||
pub fn n50(self, n50: usize) -> Self {
|
pub fn n50(self, n50: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
@@ -235,10 +221,11 @@ impl<'m> AnyPP<'m> {
|
|||||||
///
|
///
|
||||||
/// This value is only relevant for osu!ctb for which it represent
|
/// This value is only relevant for osu!ctb for which it represent
|
||||||
/// the amount of tiny droplet misses.
|
/// the amount of tiny droplet misses.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "fruits")]
|
|
||||||
pub fn n_katu(self, n_katu: usize) -> Self {
|
pub fn n_katu(self, n_katu: usize) -> Self {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(feature = "fruits")]
|
||||||
Self::Fruits(f) => Self::Fruits(f.tiny_droplet_misses(n_katu)),
|
Self::Fruits(f) => Self::Fruits(f.tiny_droplet_misses(n_katu)),
|
||||||
#[cfg(feature = "mania")]
|
#[cfg(feature = "mania")]
|
||||||
Self::Mania(_) => self,
|
Self::Mania(_) => self,
|
||||||
@@ -254,8 +241,8 @@ impl<'m> AnyPP<'m> {
|
|||||||
/// This value is only relevant for osu!mania.
|
/// 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.
|
/// On `NoMod` its between 0 and 1,000,000, on `Easy` between 0 and 500,000, etc.
|
||||||
|
#[allow(unused_variables)]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "mania")]
|
|
||||||
pub fn score(self, score: u32) -> Self {
|
pub fn score(self, score: u32) -> Self {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(feature = "fruits")]
|
#[cfg(feature = "fruits")]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{closest_rhythm, HitObjectRhythm};
|
use super::{closest_rhythm, HitObjectRhythm};
|
||||||
use crate::HitObject;
|
use crate::parse::HitObject;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct DifficultyObject<'o> {
|
pub(crate) struct DifficultyObject<'o> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::HitObject;
|
use crate::parse::HitObject;
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
|||||||
@@ -265,28 +265,3 @@ fn norm(p: f32, a: f32, b: f32, c: f32) -> f32 {
|
|||||||
pub struct DifficultyAttributes {
|
pub struct DifficultyAttributes {
|
||||||
pub stars: f32,
|
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 {
|
fn compute_strain_value(&self, stars: f32) -> f32 {
|
||||||
let exp_base = 5.0 * (stars / 0.0075).max(1.0) - 4.0;
|
let exp_base = 5.0 * (stars / 0.0075).max(1.0) - 4.0;
|
||||||
let mut strain = exp_base * exp_base / 100_000.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 {
|
pub(crate) trait Rim {
|
||||||
fn is_rim(&self) -> bool;
|
fn is_rim(&self) -> bool;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::{LimitedQueue, Rim};
|
use super::{LimitedQueue, Rim};
|
||||||
|
|
||||||
use crate::{Beatmap, HitObject};
|
use crate::{parse::HitObject, Beatmap};
|
||||||
|
|
||||||
const ROLL_MIN_REPETITIONS: usize = 12;
|
const ROLL_MIN_REPETITIONS: usize = 12;
|
||||||
const TL_MIN_REPETITIONS: isize = 16;
|
const TL_MIN_REPETITIONS: isize = 16;
|
||||||
|
|||||||
+65
-63
@@ -3,7 +3,6 @@
|
|||||||
extern crate rosu_pp;
|
extern crate rosu_pp;
|
||||||
|
|
||||||
use rosu_pp::Beatmap;
|
use rosu_pp::Beatmap;
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
struct MapResult {
|
struct MapResult {
|
||||||
map_id: u32,
|
map_id: u32,
|
||||||
@@ -12,8 +11,28 @@ struct MapResult {
|
|||||||
pp: f32,
|
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]
|
#[test]
|
||||||
fn fruits() {
|
fn fruits_sync() {
|
||||||
let star_margin = 0.0001;
|
let star_margin = 0.0001;
|
||||||
let pp_margin = 0.0001;
|
let pp_margin = 0.0001;
|
||||||
|
|
||||||
@@ -25,7 +44,7 @@ fn fruits() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = 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,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||||
};
|
};
|
||||||
@@ -37,42 +56,17 @@ fn fruits() {
|
|||||||
|
|
||||||
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async_std")]
|
#[cfg(feature = "async_tokio")]
|
||||||
#[test]
|
#[test]
|
||||||
fn fruits_async() {
|
fn fruits_async_tokio() {
|
||||||
use async_std::{fs::File, task};
|
tokio::runtime::Runtime::new()
|
||||||
|
.expect("could not start runtime")
|
||||||
task::block_on(async {
|
.block_on(async {
|
||||||
let star_margin = 0.0001;
|
let star_margin = 0.0001;
|
||||||
let pp_margin = 0.0001;
|
let pp_margin = 0.0001;
|
||||||
|
|
||||||
@@ -84,45 +78,53 @@ fn fruits_async() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
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,
|
Ok(map) => map,
|
||||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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!(
|
#[cfg(feature = "async_std")]
|
||||||
(result.pp() - pp).abs() < pp_margin * pp,
|
#[test]
|
||||||
"\nPP:\n\
|
fn fruits_async_std() {
|
||||||
Calculated: {calculated} | Expected: {expected}\n \
|
async_std::task::block_on(async {
|
||||||
=> {margin} margin ({allowed} allowed)\n\
|
let star_margin = 0.0001;
|
||||||
[map {map} | mods {mods}]\n",
|
let pp_margin = 0.0001;
|
||||||
calculated = result.pp(),
|
|
||||||
expected = pp,
|
for result in RESULTS {
|
||||||
margin = (result.pp() - pp).abs(),
|
let MapResult {
|
||||||
allowed = pp_margin * pp,
|
map_id,
|
||||||
map = map_id,
|
mods,
|
||||||
mods = mods
|
stars,
|
||||||
);
|
pp,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
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(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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-63
@@ -3,7 +3,6 @@
|
|||||||
extern crate rosu_pp;
|
extern crate rosu_pp;
|
||||||
|
|
||||||
use rosu_pp::Beatmap;
|
use rosu_pp::Beatmap;
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
struct MapResult {
|
struct MapResult {
|
||||||
map_id: u32,
|
map_id: u32,
|
||||||
@@ -12,8 +11,28 @@ struct MapResult {
|
|||||||
pp: f32,
|
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]
|
#[test]
|
||||||
fn mania() {
|
fn mania_sync() {
|
||||||
let star_margin = 0.00001;
|
let star_margin = 0.00001;
|
||||||
let pp_margin = 0.00001;
|
let pp_margin = 0.00001;
|
||||||
|
|
||||||
@@ -25,7 +44,7 @@ fn mania() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = 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,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||||
};
|
};
|
||||||
@@ -37,42 +56,17 @@ fn mania() {
|
|||||||
|
|
||||||
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async_std")]
|
#[cfg(feature = "async_tokio")]
|
||||||
#[test]
|
#[test]
|
||||||
fn mania_async() {
|
fn mania_async_tokio() {
|
||||||
use async_std::{fs::File, task};
|
tokio::runtime::Runtime::new()
|
||||||
|
.expect("could not start runtime")
|
||||||
task::block_on(async {
|
.block_on(async {
|
||||||
let star_margin = 0.00001;
|
let star_margin = 0.00001;
|
||||||
let pp_margin = 0.00001;
|
let pp_margin = 0.00001;
|
||||||
|
|
||||||
@@ -84,45 +78,53 @@ fn mania_async() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
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,
|
Ok(map) => map,
|
||||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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!(
|
#[cfg(feature = "async_std")]
|
||||||
(result.pp() - pp).abs() < pp_margin * pp,
|
#[test]
|
||||||
"\nPP:\n\
|
fn mania_async_std() {
|
||||||
Calculated: {calculated} | Expected: {expected}\n \
|
async_std::task::block_on(async {
|
||||||
=> {margin} margin ({allowed} allowed)\n\
|
let star_margin = 0.00001;
|
||||||
[map {map} | mods {mods}]\n",
|
let pp_margin = 0.00001;
|
||||||
calculated = result.pp(),
|
|
||||||
expected = pp,
|
for result in RESULTS {
|
||||||
margin = (result.pp() - pp).abs(),
|
let MapResult {
|
||||||
allowed = pp_margin * pp,
|
map_id,
|
||||||
map = map_id,
|
mods,
|
||||||
mods = mods
|
stars,
|
||||||
);
|
pp,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
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(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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-75
@@ -3,7 +3,6 @@
|
|||||||
extern crate rosu_pp;
|
extern crate rosu_pp;
|
||||||
|
|
||||||
use rosu_pp::Beatmap;
|
use rosu_pp::Beatmap;
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
struct MapResult {
|
struct MapResult {
|
||||||
map_id: u32,
|
map_id: u32,
|
||||||
@@ -12,9 +11,27 @@ struct MapResult {
|
|||||||
pp: f32,
|
pp: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
macro_rules! assert_result {
|
||||||
fn osu() {
|
($kind:expr => $result:expr, $margin:expr, $expected:ident, $map_id:ident, $mods:ident) => {
|
||||||
let margin = if cfg!(feature = "no_sliders_no_leniency") {
|
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
|
0.0075
|
||||||
} else if cfg!(feature = "no_leniency") {
|
} else if cfg!(feature = "no_leniency") {
|
||||||
0.0025
|
0.0025
|
||||||
@@ -22,7 +39,13 @@ fn osu() {
|
|||||||
0.001
|
0.001
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
|
||||||
|
#[test]
|
||||||
|
fn osu_sync() {
|
||||||
|
let margin = margin();
|
||||||
|
|
||||||
let star_margin = margin;
|
let star_margin = margin;
|
||||||
let pp_margin = margin;
|
let pp_margin = margin;
|
||||||
@@ -35,7 +58,7 @@ fn osu() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = 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,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||||
};
|
};
|
||||||
@@ -47,51 +70,18 @@ fn osu() {
|
|||||||
|
|
||||||
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async_std")]
|
#[cfg(feature = "async_tokio")]
|
||||||
#[test]
|
#[test]
|
||||||
fn osu_async() {
|
fn osu_async_tokio() {
|
||||||
use async_std::{fs::File, task};
|
tokio::runtime::Runtime::new()
|
||||||
|
.expect("could not start runtime")
|
||||||
task::block_on(async {
|
.block_on(async {
|
||||||
let margin = if cfg!(feature = "no_sliders_no_leniency") {
|
let margin = margin();
|
||||||
0.0075
|
|
||||||
} else if cfg!(feature = "no_leniency") {
|
|
||||||
0.0025
|
|
||||||
} else if cfg!(feature = "all_included") {
|
|
||||||
0.001
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
|
|
||||||
let star_margin = margin;
|
let star_margin = margin;
|
||||||
let pp_margin = margin;
|
let pp_margin = margin;
|
||||||
@@ -104,45 +94,55 @@ fn osu_async() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
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,
|
Ok(map) => map,
|
||||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::OsuPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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!(
|
#[cfg(feature = "async_std")]
|
||||||
(result.pp() - pp).abs() < pp_margin * pp,
|
#[test]
|
||||||
"\nPP:\n\
|
fn osu_async_std() {
|
||||||
Calculated: {calculated} | Expected: {expected}\n \
|
async_std::task::block_on(async {
|
||||||
=> {margin} margin ({allowed} allowed)\n\
|
let margin = margin();
|
||||||
[map {map} | mods {mods}]\n",
|
|
||||||
calculated = result.pp(),
|
let star_margin = margin;
|
||||||
expected = pp,
|
let pp_margin = margin;
|
||||||
margin = (result.pp() - pp).abs(),
|
|
||||||
allowed = pp_margin * pp,
|
for result in RESULTS {
|
||||||
map = map_id,
|
let MapResult {
|
||||||
mods = mods
|
map_id,
|
||||||
);
|
mods,
|
||||||
|
stars,
|
||||||
|
pp,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
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(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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-63
@@ -3,7 +3,6 @@
|
|||||||
extern crate rosu_pp;
|
extern crate rosu_pp;
|
||||||
|
|
||||||
use rosu_pp::Beatmap;
|
use rosu_pp::Beatmap;
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
struct MapResult {
|
struct MapResult {
|
||||||
map_id: u32,
|
map_id: u32,
|
||||||
@@ -12,8 +11,28 @@ struct MapResult {
|
|||||||
pp: f32,
|
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]
|
#[test]
|
||||||
fn taiko() {
|
fn taiko_sync() {
|
||||||
let star_margin = 0.0001;
|
let star_margin = 0.0001;
|
||||||
let pp_margin = 0.0001;
|
let pp_margin = 0.0001;
|
||||||
|
|
||||||
@@ -25,7 +44,7 @@ fn taiko() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = 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,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
||||||
};
|
};
|
||||||
@@ -37,42 +56,17 @@ fn taiko() {
|
|||||||
|
|
||||||
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async_std")]
|
#[cfg(feature = "async_tokio")]
|
||||||
#[test]
|
#[test]
|
||||||
fn taiko_async() {
|
fn taiko_async_tokio() {
|
||||||
use async_std::{fs::File, task};
|
tokio::runtime::Runtime::new()
|
||||||
|
.expect("could not start runtime")
|
||||||
task::block_on(async {
|
.block_on(async {
|
||||||
let star_margin = 0.0001;
|
let star_margin = 0.0001;
|
||||||
let pp_margin = 0.0001;
|
let pp_margin = 0.0001;
|
||||||
|
|
||||||
@@ -84,45 +78,53 @@ fn taiko_async() {
|
|||||||
pp,
|
pp,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
let file = match File::open(format!("./maps/{}.osu", map_id)).await {
|
let file = match tokio::fs::File::open(format!("./maps/{}.osu", map_id)).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(why) => panic!("Could not open file {}.osu: {}", map_id, why),
|
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,
|
Ok(map) => map,
|
||||||
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert_result!("Stars" => result.stars(), star_margin, stars, map_id, mods);
|
||||||
(result.stars() - stars).abs() < star_margin * stars,
|
assert_result!("PP" => result.pp(), pp_margin, pp, map_id, mods);
|
||||||
"\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!(
|
#[cfg(feature = "async_std")]
|
||||||
(result.pp() - pp).abs() < pp_margin * pp,
|
#[test]
|
||||||
"\nPP:\n\
|
fn taiko_async_std() {
|
||||||
Calculated: {calculated} | Expected: {expected}\n \
|
async_std::task::block_on(async {
|
||||||
=> {margin} margin ({allowed} allowed)\n\
|
let star_margin = 0.0001;
|
||||||
[map {map} | mods {mods}]\n",
|
let pp_margin = 0.0001;
|
||||||
calculated = result.pp(),
|
|
||||||
expected = pp,
|
for result in RESULTS {
|
||||||
margin = (result.pp() - pp).abs(),
|
let MapResult {
|
||||||
allowed = pp_margin * pp,
|
map_id,
|
||||||
map = map_id,
|
mods,
|
||||||
mods = mods
|
stars,
|
||||||
);
|
pp,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
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(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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user