remove all mode features

minor adjustments
This commit is contained in:
MaxOhn
2022-01-25 15:09:16 +01:00
parent 543a4302e9
commit 34c3a195da
14 changed files with 142 additions and 667 deletions
+2 -1
View File
@@ -1,8 +1,9 @@
## Upcoming ## Upcoming
- osu: Fix panic on maps with 0 objects - osu: Fix panic on maps with 0 objects
- [BREAKING] Store `HitObject::sound` in `Beatmap::sounds` instead to reduce the struct size
- Added `AttributeProvider` impl for `{Mode}PerformanceAttributes` - Added `AttributeProvider` impl for `{Mode}PerformanceAttributes`
- [BREAKING] Store `HitObject::sound` in `Beatmap::sounds` instead to reduce the struct size
- [BREAKING] Removed the mode features `osu`, `fruits`, `taiko`, and `mania`. Now all modes are always supported.
# v0.4.0 # v0.4.0
+1 -11
View File
@@ -11,20 +11,10 @@ description = "osu! difficulty and pp calculation for all modes"
keywords = ["osu", "pp", "stars", "async"] keywords = ["osu", "pp", "stars", "async"]
[features] [features]
default = ["osu", "taiko", "fruits", "mania"] default = []
# game modes
osu = ["sliders"]
taiko = []
fruits = ["sliders"]
mania = []
# async version
async_std = ["async-std"] async_std = ["async-std"]
async_tokio = ["tokio"] async_tokio = ["tokio"]
# auxiliary, no need to set yourself
sliders = []
[dependencies.async-std] [dependencies.async-std]
version = "1.9" version = "1.9"
-3
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "sliders")]
use crate::{ use crate::{
parse::{DifficultyPoint, TimingPoint}, parse::{DifficultyPoint, TimingPoint},
Beatmap, Beatmap,
@@ -44,7 +42,6 @@ pub(crate) enum ControlPoint {
Difficulty { time: f64, slider_velocity: f64 }, Difficulty { time: f64, slider_velocity: f64 },
} }
#[cfg(feature = "sliders")]
impl ControlPoint { impl ControlPoint {
#[inline] #[inline]
pub(crate) fn time(&self) -> f64 { pub(crate) fn time(&self) -> f64 {
-2
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "sliders")]
use std::{borrow::Cow, cmp::Ordering, convert::identity, f32::consts::PI, iter}; use std::{borrow::Cow, cmp::Ordering, convert::identity, f32::consts::PI, iter};
use crate::parse::{PathControlPoint, PathType, Pos2}; use crate::parse::{PathControlPoint, PathType, Pos2};
-2
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "fruits")]
mod catch_object; mod catch_object;
mod difficulty_object; mod difficulty_object;
mod fruit_or_juice; mod fruit_or_juice;
+8 -51
View File
@@ -1,19 +1,11 @@
use crate::{Beatmap, DifficultyAttributes, GameMode, Mods, PerformanceAttributes}; use crate::{
fruits::{
#[cfg(feature = "fruits")] FruitsGradualDifficultyAttributes, FruitsGradualPerformanceAttributes, FruitsScoreState,
use crate::fruits::{ },
FruitsGradualDifficultyAttributes, FruitsGradualPerformanceAttributes, FruitsScoreState, mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes},
}; osu::{OsuGradualDifficultyAttributes, OsuGradualPerformanceAttributes, OsuScoreState},
taiko::{TaikoGradualDifficultyAttributes, TaikoGradualPerformanceAttributes, TaikoScoreState},
#[cfg(feature = "mania")] Beatmap, DifficultyAttributes, GameMode, Mods, PerformanceAttributes,
use crate::mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes};
#[cfg(feature = "osu")]
use crate::osu::{OsuGradualDifficultyAttributes, OsuGradualPerformanceAttributes, OsuScoreState};
#[cfg(feature = "taiko")]
use crate::taiko::{
TaikoGradualDifficultyAttributes, TaikoGradualPerformanceAttributes, TaikoScoreState,
}; };
/// Gradually calculate the difficulty attributes on maps of any mode. /// Gradually calculate the difficulty attributes on maps of any mode.
@@ -48,16 +40,12 @@ use crate::taiko::{
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum GradualDifficultyAttributes<'map> { pub enum GradualDifficultyAttributes<'map> {
#[cfg(feature = "fruits")]
/// Gradual osu!fruits difficulty attributes. /// Gradual osu!fruits difficulty attributes.
Fruits(FruitsGradualDifficultyAttributes<'map>), Fruits(FruitsGradualDifficultyAttributes<'map>),
#[cfg(feature = "mania")]
/// Gradual osu!mania difficulty attributes. /// Gradual osu!mania difficulty attributes.
Mania(ManiaGradualDifficultyAttributes<'map>), Mania(ManiaGradualDifficultyAttributes<'map>),
#[cfg(feature = "osu")]
/// Gradual osu!standard difficulty attributes. /// Gradual osu!standard difficulty attributes.
Osu(OsuGradualDifficultyAttributes), Osu(OsuGradualDifficultyAttributes),
#[cfg(feature = "taiko")]
/// Gradual osu!taiko difficulty attributes. /// Gradual osu!taiko difficulty attributes.
Taiko(TaikoGradualDifficultyAttributes<'map>), Taiko(TaikoGradualDifficultyAttributes<'map>),
} }
@@ -66,16 +54,10 @@ impl<'map> GradualDifficultyAttributes<'map> {
/// Create a new gradual difficulty calculator for maps of any mode. /// Create a new gradual difficulty calculator for maps of any mode.
pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self { pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self {
match map.mode { match map.mode {
#[cfg(feature = "osu")]
GameMode::STD => Self::Osu(OsuGradualDifficultyAttributes::new(map, mods)), GameMode::STD => Self::Osu(OsuGradualDifficultyAttributes::new(map, mods)),
#[cfg(feature = "taiko")]
GameMode::TKO => Self::Taiko(TaikoGradualDifficultyAttributes::new(map, mods)), GameMode::TKO => Self::Taiko(TaikoGradualDifficultyAttributes::new(map, mods)),
#[cfg(feature = "fruits")]
GameMode::CTB => Self::Fruits(FruitsGradualDifficultyAttributes::new(map, mods)), GameMode::CTB => Self::Fruits(FruitsGradualDifficultyAttributes::new(map, mods)),
#[cfg(feature = "mania")]
GameMode::MNA => Self::Mania(ManiaGradualDifficultyAttributes::new(map, mods)), GameMode::MNA => Self::Mania(ManiaGradualDifficultyAttributes::new(map, mods)),
#[allow(unreachable_patterns)]
_ => panic!("feature for mode {:?} is not enabled", map.mode),
} }
} }
} }
@@ -86,13 +68,9 @@ impl Iterator for GradualDifficultyAttributes<'_> {
#[inline] #[inline]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match self { match self {
#[cfg(feature = "fruits")]
GradualDifficultyAttributes::Fruits(f) => f.next().map(DifficultyAttributes::Fruits), GradualDifficultyAttributes::Fruits(f) => f.next().map(DifficultyAttributes::Fruits),
#[cfg(feature = "mania")]
GradualDifficultyAttributes::Mania(m) => m.next().map(DifficultyAttributes::Mania), GradualDifficultyAttributes::Mania(m) => m.next().map(DifficultyAttributes::Mania),
#[cfg(feature = "osu")]
GradualDifficultyAttributes::Osu(o) => o.next().map(DifficultyAttributes::Osu), GradualDifficultyAttributes::Osu(o) => o.next().map(DifficultyAttributes::Osu),
#[cfg(feature = "taiko")]
GradualDifficultyAttributes::Taiko(t) => t.next().map(DifficultyAttributes::Taiko), GradualDifficultyAttributes::Taiko(t) => t.next().map(DifficultyAttributes::Taiko),
} }
} }
@@ -100,13 +78,9 @@ impl Iterator for GradualDifficultyAttributes<'_> {
#[inline] #[inline]
fn size_hint(&self) -> (usize, Option<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
match self { match self {
#[cfg(feature = "fruits")]
GradualDifficultyAttributes::Fruits(f) => f.size_hint(), GradualDifficultyAttributes::Fruits(f) => f.size_hint(),
#[cfg(feature = "mania")]
GradualDifficultyAttributes::Mania(m) => m.size_hint(), GradualDifficultyAttributes::Mania(m) => m.size_hint(),
#[cfg(feature = "osu")]
GradualDifficultyAttributes::Osu(o) => o.size_hint(), GradualDifficultyAttributes::Osu(o) => o.size_hint(),
#[cfg(feature = "taiko")]
GradualDifficultyAttributes::Taiko(t) => t.size_hint(), GradualDifficultyAttributes::Taiko(t) => t.size_hint(),
} }
} }
@@ -159,7 +133,6 @@ impl ScoreState {
} }
} }
#[cfg(feature = "fruits")]
impl From<ScoreState> for FruitsScoreState { impl From<ScoreState> for FruitsScoreState {
#[inline] #[inline]
fn from(state: ScoreState) -> Self { fn from(state: ScoreState) -> Self {
@@ -174,7 +147,6 @@ impl From<ScoreState> for FruitsScoreState {
} }
} }
#[cfg(feature = "osu")]
impl From<ScoreState> for OsuScoreState { impl From<ScoreState> for OsuScoreState {
#[inline] #[inline]
fn from(state: ScoreState) -> Self { fn from(state: ScoreState) -> Self {
@@ -188,7 +160,6 @@ impl From<ScoreState> for OsuScoreState {
} }
} }
#[cfg(feature = "taiko")]
impl From<ScoreState> for TaikoScoreState { impl From<ScoreState> for TaikoScoreState {
#[inline] #[inline]
fn from(state: ScoreState) -> Self { fn from(state: ScoreState) -> Self {
@@ -300,16 +271,12 @@ impl From<ScoreState> for TaikoScoreState {
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum GradualPerformanceAttributes<'map> { pub enum GradualPerformanceAttributes<'map> {
#[cfg(feature = "fruits")]
/// Gradual osu!ctb performance attributes. /// Gradual osu!ctb performance attributes.
Fruits(FruitsGradualPerformanceAttributes<'map>), Fruits(FruitsGradualPerformanceAttributes<'map>),
#[cfg(feature = "mania")]
/// Gradual osu!mania performance attributes. /// Gradual osu!mania performance attributes.
Mania(ManiaGradualPerformanceAttributes<'map>), Mania(ManiaGradualPerformanceAttributes<'map>),
#[cfg(feature = "osu")]
/// Gradual osu!standard performance attributes. /// Gradual osu!standard performance attributes.
Osu(OsuGradualPerformanceAttributes<'map>), Osu(OsuGradualPerformanceAttributes<'map>),
#[cfg(feature = "taiko")]
/// Gradual osu!taiko performance attributes. /// Gradual osu!taiko performance attributes.
Taiko(TaikoGradualPerformanceAttributes<'map>), Taiko(TaikoGradualPerformanceAttributes<'map>),
} }
@@ -318,16 +285,10 @@ impl<'map> GradualPerformanceAttributes<'map> {
/// Create a new gradual performance calculator for maps of any mode. /// Create a new gradual performance calculator for maps of any mode.
pub fn new(map: &'map Beatmap, mods: u32) -> Self { pub fn new(map: &'map Beatmap, mods: u32) -> Self {
match map.mode { match map.mode {
#[cfg(feature = "osu")]
GameMode::STD => Self::Osu(OsuGradualPerformanceAttributes::new(map, mods)), GameMode::STD => Self::Osu(OsuGradualPerformanceAttributes::new(map, mods)),
#[cfg(feature = "taiko")]
GameMode::TKO => Self::Taiko(TaikoGradualPerformanceAttributes::new(map, mods)), GameMode::TKO => Self::Taiko(TaikoGradualPerformanceAttributes::new(map, mods)),
#[cfg(feature = "fruits")]
GameMode::CTB => Self::Fruits(FruitsGradualPerformanceAttributes::new(map, mods)), GameMode::CTB => Self::Fruits(FruitsGradualPerformanceAttributes::new(map, mods)),
#[cfg(feature = "mania")]
GameMode::MNA => Self::Mania(ManiaGradualPerformanceAttributes::new(map, mods)), GameMode::MNA => Self::Mania(ManiaGradualPerformanceAttributes::new(map, mods)),
#[allow(unreachable_patterns)]
_ => panic!("feature for mode {:?} is not enabled", map.mode),
} }
} }
@@ -349,19 +310,15 @@ impl<'map> GradualPerformanceAttributes<'map> {
n: usize, n: usize,
) -> Option<PerformanceAttributes> { ) -> Option<PerformanceAttributes> {
match self { match self {
#[cfg(feature = "fruits")]
GradualPerformanceAttributes::Fruits(f) => f GradualPerformanceAttributes::Fruits(f) => f
.process_next_n_objects(state.into(), n) .process_next_n_objects(state.into(), n)
.map(PerformanceAttributes::Fruits), .map(PerformanceAttributes::Fruits),
#[cfg(feature = "mania")]
GradualPerformanceAttributes::Mania(m) => m GradualPerformanceAttributes::Mania(m) => m
.process_next_n_objects(state.score, n) .process_next_n_objects(state.score, n)
.map(PerformanceAttributes::Mania), .map(PerformanceAttributes::Mania),
#[cfg(feature = "osu")]
GradualPerformanceAttributes::Osu(o) => o GradualPerformanceAttributes::Osu(o) => o
.process_next_n_objects(state.into(), n) .process_next_n_objects(state.into(), n)
.map(PerformanceAttributes::Osu), .map(PerformanceAttributes::Osu),
#[cfg(feature = "taiko")]
GradualPerformanceAttributes::Taiko(t) => t GradualPerformanceAttributes::Taiko(t) => t
.process_next_n_objects(state.into(), n) .process_next_n_objects(state.into(), n)
.map(PerformanceAttributes::Taiko), .map(PerformanceAttributes::Taiko),
+8 -170
View File
@@ -179,23 +179,15 @@
missing_docs missing_docs
)] )]
#[cfg(feature = "fruits")]
#[cfg_attr(docsrs, doc(cfg(feature = "fruits")))]
/// Everything about osu!ctb. /// Everything about osu!ctb.
pub mod fruits; pub mod fruits;
#[cfg(feature = "mania")]
#[cfg_attr(docsrs, doc(cfg(feature = "mania")))]
/// Everything about osu!mania. /// Everything about osu!mania.
pub mod mania; pub mod mania;
#[cfg(feature = "osu")]
#[cfg_attr(docsrs, doc(cfg(feature = "osu")))]
/// Everything about osu!standard. /// Everything about osu!standard.
pub mod osu; pub mod osu;
#[cfg(feature = "taiko")]
#[cfg_attr(docsrs, doc(cfg(feature = "taiko")))]
/// Everything about osu!taiko. /// Everything about osu!taiko.
pub mod taiko; pub mod taiko;
@@ -211,22 +203,13 @@ pub use pp::{AnyPP, AttributeProvider};
mod curve; mod curve;
mod mods; mod mods;
#[cfg(feature = "sliders")]
pub(crate) mod control_point_iter; pub(crate) mod control_point_iter;
#[cfg(feature = "sliders")]
pub(crate) use control_point_iter::{ControlPoint, ControlPointIter}; pub(crate) use control_point_iter::{ControlPoint, ControlPointIter};
#[cfg(feature = "fruits")]
pub use fruits::FruitsPP; pub use fruits::FruitsPP;
#[cfg(feature = "mania")]
pub use mania::ManiaPP; pub use mania::ManiaPP;
#[cfg(feature = "osu")]
pub use osu::OsuPP; pub use osu::OsuPP;
#[cfg(feature = "taiko")]
pub use taiko::TaikoPP; pub use taiko::TaikoPP;
pub use mods::Mods; pub use mods::Mods;
@@ -270,32 +253,10 @@ impl BeatmapExt for Beatmap {
#[inline] #[inline]
fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> DifficultyAttributes { fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> DifficultyAttributes {
match self.mode { match self.mode {
GameMode::STD => { GameMode::STD => DifficultyAttributes::Osu(osu::stars(self, mods, passed_objects)),
#[cfg(not(feature = "osu"))] GameMode::MNA => DifficultyAttributes::Mania(mania::stars(self, mods, passed_objects)),
panic!("`osu` feature is not enabled"); GameMode::TKO => DifficultyAttributes::Taiko(taiko::stars(self, mods, passed_objects)),
#[cfg(feature = "osu")]
DifficultyAttributes::Osu(osu::stars(self, mods, passed_objects))
}
GameMode::MNA => {
#[cfg(not(feature = "mania"))]
panic!("`mania` feature is not enabled");
#[cfg(feature = "mania")]
DifficultyAttributes::Mania(mania::stars(self, mods, passed_objects))
}
GameMode::TKO => {
#[cfg(not(feature = "taiko"))]
panic!("`taiko` feature is not enabled");
#[cfg(feature = "taiko")]
DifficultyAttributes::Taiko(taiko::stars(self, mods, passed_objects))
}
GameMode::CTB => { GameMode::CTB => {
#[cfg(not(feature = "fruits"))]
panic!("`fruits` feature is not enabled");
#[cfg(feature = "fruits")]
DifficultyAttributes::Fruits(fruits::stars(self, mods, passed_objects)) DifficultyAttributes::Fruits(fruits::stars(self, mods, passed_objects))
} }
} }
@@ -304,32 +265,14 @@ impl BeatmapExt for Beatmap {
#[inline] #[inline]
fn max_pp(&self, mods: u32) -> PerformanceAttributes { fn max_pp(&self, mods: u32) -> PerformanceAttributes {
match self.mode { match self.mode {
GameMode::STD => { GameMode::STD => PerformanceAttributes::Osu(OsuPP::new(self).mods(mods).calculate()),
#[cfg(not(feature = "osu"))]
panic!("`osu` feature is not enabled");
#[cfg(feature = "osu")]
PerformanceAttributes::Osu(OsuPP::new(self).mods(mods).calculate())
}
GameMode::MNA => { GameMode::MNA => {
#[cfg(not(feature = "mania"))]
panic!("`mania` feature is not enabled");
#[cfg(feature = "mania")]
PerformanceAttributes::Mania(ManiaPP::new(self).mods(mods).calculate()) PerformanceAttributes::Mania(ManiaPP::new(self).mods(mods).calculate())
} }
GameMode::TKO => { GameMode::TKO => {
#[cfg(not(feature = "taiko"))]
panic!("`taiko` feature is not enabled");
#[cfg(feature = "taiko")]
PerformanceAttributes::Taiko(TaikoPP::new(self).mods(mods).calculate()) PerformanceAttributes::Taiko(TaikoPP::new(self).mods(mods).calculate())
} }
GameMode::CTB => { GameMode::CTB => {
#[cfg(not(feature = "fruits"))]
panic!("`fruits` feature is not enabled");
#[cfg(feature = "fruits")]
PerformanceAttributes::Fruits(FruitsPP::new(self).mods(mods).calculate()) PerformanceAttributes::Fruits(FruitsPP::new(self).mods(mods).calculate())
} }
} }
@@ -343,34 +286,10 @@ impl BeatmapExt for Beatmap {
#[inline] #[inline]
fn strains(&self, mods: impl Mods) -> Strains { fn strains(&self, mods: impl Mods) -> Strains {
match self.mode { match self.mode {
GameMode::STD => { GameMode::STD => osu::strains(self, mods),
#[cfg(not(feature = "osu"))] GameMode::MNA => mania::strains(self, mods),
panic!("`osu` feature is not enabled"); GameMode::TKO => taiko::strains(self, mods),
GameMode::CTB => fruits::strains(self, mods),
#[cfg(feature = "osu")]
osu::strains(self, mods)
}
GameMode::MNA => {
#[cfg(not(feature = "mania"))]
panic!("`mania` feature is not enabled");
#[cfg(feature = "mania")]
mania::strains(self, mods)
}
GameMode::TKO => {
#[cfg(not(feature = "taiko"))]
panic!("`taiko` feature is not enabled");
#[cfg(feature = "taiko")]
taiko::strains(self, mods)
}
GameMode::CTB => {
#[cfg(not(feature = "fruits"))]
panic!("`fruits` feature is not enabled");
#[cfg(feature = "fruits")]
fruits::strains(self, mods)
}
} }
} }
@@ -398,16 +317,12 @@ pub struct Strains {
/// The result of a difficulty calculation based on the mode. /// The result of a difficulty calculation based on the mode.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum DifficultyAttributes { pub enum DifficultyAttributes {
#[cfg(feature = "fruits")]
/// osu!ctb difficulty calculation reseult. /// osu!ctb difficulty calculation reseult.
Fruits(fruits::FruitsDifficultyAttributes), Fruits(fruits::FruitsDifficultyAttributes),
#[cfg(feature = "mania")]
/// osu!mania difficulty calculation reseult. /// osu!mania difficulty calculation reseult.
Mania(mania::ManiaDifficultyAttributes), Mania(mania::ManiaDifficultyAttributes),
#[cfg(feature = "osu")]
/// osu!standard difficulty calculation reseult. /// osu!standard difficulty calculation reseult.
Osu(osu::OsuDifficultyAttributes), Osu(osu::OsuDifficultyAttributes),
#[cfg(feature = "taiko")]
/// osu!taiko difficulty calculation reseult. /// osu!taiko difficulty calculation reseult.
Taiko(taiko::TaikoDifficultyAttributes), Taiko(taiko::TaikoDifficultyAttributes),
} }
@@ -417,13 +332,9 @@ impl DifficultyAttributes {
#[inline] #[inline]
pub fn stars(&self) -> f64 { pub fn stars(&self) -> f64 {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(attributes) => attributes.stars, Self::Fruits(attributes) => attributes.stars,
#[cfg(feature = "mania")]
Self::Mania(attributes) => attributes.stars, Self::Mania(attributes) => attributes.stars,
#[cfg(feature = "osu")]
Self::Osu(attributes) => attributes.stars, Self::Osu(attributes) => attributes.stars,
#[cfg(feature = "taiko")]
Self::Taiko(attributes) => attributes.stars, Self::Taiko(attributes) => attributes.stars,
} }
} }
@@ -432,35 +343,16 @@ impl DifficultyAttributes {
/// ///
/// This will only be `None` for attributes of osu!mania maps. /// This will only be `None` for attributes of osu!mania maps.
#[inline] #[inline]
#[cfg(feature = "mania")]
pub fn max_combo(&self) -> Option<usize> { pub fn max_combo(&self) -> Option<usize> {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(attributes) => Some(attributes.max_combo()), Self::Fruits(attributes) => Some(attributes.max_combo()),
Self::Mania(_) => None, Self::Mania(_) => None,
#[cfg(feature = "osu")]
Self::Osu(attributes) => Some(attributes.max_combo), Self::Osu(attributes) => Some(attributes.max_combo),
#[cfg(feature = "taiko")]
Self::Taiko(attributes) => Some(attributes.max_combo), Self::Taiko(attributes) => Some(attributes.max_combo),
} }
} }
#[cfg(not(feature = "mania"))]
#[inline]
/// The maximum combo of the map.
pub fn max_combo(&self) -> usize {
match self {
#[cfg(feature = "fruits")]
Self::Fruits(attributes) => attributes.max_combo,
#[cfg(feature = "osu")]
Self::Osu(attributes) => attributes.max_combo,
#[cfg(feature = "taiko")]
Self::Taiko(attributes) => attributes.max_combo,
}
}
} }
#[cfg(feature = "fruits")]
impl From<fruits::FruitsDifficultyAttributes> for DifficultyAttributes { impl From<fruits::FruitsDifficultyAttributes> for DifficultyAttributes {
#[inline] #[inline]
fn from(attributes: fruits::FruitsDifficultyAttributes) -> Self { fn from(attributes: fruits::FruitsDifficultyAttributes) -> Self {
@@ -468,7 +360,6 @@ impl From<fruits::FruitsDifficultyAttributes> for DifficultyAttributes {
} }
} }
#[cfg(feature = "mania")]
impl From<mania::ManiaDifficultyAttributes> for DifficultyAttributes { impl From<mania::ManiaDifficultyAttributes> for DifficultyAttributes {
#[inline] #[inline]
fn from(attributes: mania::ManiaDifficultyAttributes) -> Self { fn from(attributes: mania::ManiaDifficultyAttributes) -> Self {
@@ -476,7 +367,6 @@ impl From<mania::ManiaDifficultyAttributes> for DifficultyAttributes {
} }
} }
#[cfg(feature = "osu")]
impl From<osu::OsuDifficultyAttributes> for DifficultyAttributes { impl From<osu::OsuDifficultyAttributes> for DifficultyAttributes {
#[inline] #[inline]
fn from(attributes: osu::OsuDifficultyAttributes) -> Self { fn from(attributes: osu::OsuDifficultyAttributes) -> Self {
@@ -484,7 +374,6 @@ impl From<osu::OsuDifficultyAttributes> for DifficultyAttributes {
} }
} }
#[cfg(feature = "taiko")]
impl From<taiko::TaikoDifficultyAttributes> for DifficultyAttributes { impl From<taiko::TaikoDifficultyAttributes> for DifficultyAttributes {
#[inline] #[inline]
fn from(attributes: taiko::TaikoDifficultyAttributes) -> Self { fn from(attributes: taiko::TaikoDifficultyAttributes) -> Self {
@@ -495,16 +384,12 @@ impl From<taiko::TaikoDifficultyAttributes> for DifficultyAttributes {
/// The result of a performance calculation based on the mode. /// The result of a performance calculation based on the mode.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PerformanceAttributes { pub enum PerformanceAttributes {
#[cfg(feature = "fruits")]
/// osu!ctb performance calculation result. /// osu!ctb performance calculation result.
Fruits(fruits::FruitsPerformanceAttributes), Fruits(fruits::FruitsPerformanceAttributes),
#[cfg(feature = "mania")]
/// osu!mania performance calculation result. /// osu!mania performance calculation result.
Mania(mania::ManiaPerformanceAttributes), Mania(mania::ManiaPerformanceAttributes),
#[cfg(feature = "osu")]
/// osu!standard performance calculation result. /// osu!standard performance calculation result.
Osu(osu::OsuPerformanceAttributes), Osu(osu::OsuPerformanceAttributes),
#[cfg(feature = "taiko")]
/// osu!taiko performance calculation result. /// osu!taiko performance calculation result.
Taiko(taiko::TaikoPerformanceAttributes), Taiko(taiko::TaikoPerformanceAttributes),
} }
@@ -514,13 +399,9 @@ impl PerformanceAttributes {
#[inline] #[inline]
pub fn pp(&self) -> f64 { pub fn pp(&self) -> f64 {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(attributes) => attributes.pp, Self::Fruits(attributes) => attributes.pp,
#[cfg(feature = "mania")]
Self::Mania(attributes) => attributes.pp, Self::Mania(attributes) => attributes.pp,
#[cfg(feature = "osu")]
Self::Osu(attributes) => attributes.pp, Self::Osu(attributes) => attributes.pp,
#[cfg(feature = "taiko")]
Self::Taiko(attributes) => attributes.pp, Self::Taiko(attributes) => attributes.pp,
} }
} }
@@ -529,13 +410,9 @@ impl PerformanceAttributes {
#[inline] #[inline]
pub fn stars(&self) -> f64 { pub fn stars(&self) -> f64 {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(attributes) => attributes.stars(), Self::Fruits(attributes) => attributes.stars(),
#[cfg(feature = "mania")]
Self::Mania(attributes) => attributes.stars(), Self::Mania(attributes) => attributes.stars(),
#[cfg(feature = "osu")]
Self::Osu(attributes) => attributes.stars(), Self::Osu(attributes) => attributes.stars(),
#[cfg(feature = "taiko")]
Self::Taiko(attributes) => attributes.stars(), Self::Taiko(attributes) => attributes.stars(),
} }
} }
@@ -544,65 +421,38 @@ impl PerformanceAttributes {
#[inline] #[inline]
pub fn difficulty_attributes(&self) -> DifficultyAttributes { pub fn difficulty_attributes(&self) -> DifficultyAttributes {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(attributes) => DifficultyAttributes::Fruits(attributes.difficulty.clone()), Self::Fruits(attributes) => DifficultyAttributes::Fruits(attributes.difficulty.clone()),
#[cfg(feature = "mania")]
Self::Mania(attributes) => DifficultyAttributes::Mania(attributes.difficulty), Self::Mania(attributes) => DifficultyAttributes::Mania(attributes.difficulty),
#[cfg(feature = "osu")]
Self::Osu(attributes) => DifficultyAttributes::Osu(attributes.difficulty.clone()), Self::Osu(attributes) => DifficultyAttributes::Osu(attributes.difficulty.clone()),
#[cfg(feature = "taiko")]
Self::Taiko(attributes) => DifficultyAttributes::Taiko(attributes.difficulty), Self::Taiko(attributes) => DifficultyAttributes::Taiko(attributes.difficulty),
} }
} }
#[cfg(feature = "mania")]
#[inline] #[inline]
/// The maximum combo of the map. /// The maximum combo of the map.
/// ///
/// This will only be `None` for attributes of osu!mania maps. /// This will only be `None` for attributes of osu!mania maps.
pub fn max_combo(&self) -> Option<usize> { pub fn max_combo(&self) -> Option<usize> {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Some(f.difficulty.max_combo()), Self::Fruits(f) => Some(f.difficulty.max_combo()),
Self::Mania(_) => None, Self::Mania(_) => None,
#[cfg(feature = "osu")]
Self::Osu(o) => Some(o.difficulty.max_combo), Self::Osu(o) => Some(o.difficulty.max_combo),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Some(t.difficulty.max_combo), Self::Taiko(t) => Some(t.difficulty.max_combo),
} }
} }
#[cfg(not(feature = "mania"))]
#[inline]
/// The maximum combo of the map.
pub fn max_combo(&self) -> usize {
match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => f.difficulty.max_combo,
#[cfg(feature = "osu")]
Self::Osu(o) => o.difficulty.max_combo,
#[cfg(feature = "taiko")]
Self::Taiko(t) => t.difficulty.max_combo,
}
}
} }
impl From<PerformanceAttributes> for DifficultyAttributes { impl From<PerformanceAttributes> for DifficultyAttributes {
fn from(attributes: PerformanceAttributes) -> Self { fn from(attributes: PerformanceAttributes) -> Self {
match attributes { match attributes {
#[cfg(feature = "fruits")]
PerformanceAttributes::Fruits(attributes) => Self::Fruits(attributes.difficulty), PerformanceAttributes::Fruits(attributes) => Self::Fruits(attributes.difficulty),
#[cfg(feature = "mania")]
PerformanceAttributes::Mania(attributes) => Self::Mania(attributes.difficulty), PerformanceAttributes::Mania(attributes) => Self::Mania(attributes.difficulty),
#[cfg(feature = "osu")]
PerformanceAttributes::Osu(attributes) => Self::Osu(attributes.difficulty), PerformanceAttributes::Osu(attributes) => Self::Osu(attributes.difficulty),
#[cfg(feature = "taiko")]
PerformanceAttributes::Taiko(attributes) => Self::Taiko(attributes.difficulty), PerformanceAttributes::Taiko(attributes) => Self::Taiko(attributes.difficulty),
} }
} }
} }
#[cfg(feature = "fruits")]
impl From<fruits::FruitsPerformanceAttributes> for PerformanceAttributes { impl From<fruits::FruitsPerformanceAttributes> for PerformanceAttributes {
#[inline] #[inline]
fn from(attributes: fruits::FruitsPerformanceAttributes) -> Self { fn from(attributes: fruits::FruitsPerformanceAttributes) -> Self {
@@ -610,7 +460,6 @@ impl From<fruits::FruitsPerformanceAttributes> for PerformanceAttributes {
} }
} }
#[cfg(feature = "mania")]
impl From<mania::ManiaPerformanceAttributes> for PerformanceAttributes { impl From<mania::ManiaPerformanceAttributes> for PerformanceAttributes {
#[inline] #[inline]
fn from(attributes: mania::ManiaPerformanceAttributes) -> Self { fn from(attributes: mania::ManiaPerformanceAttributes) -> Self {
@@ -618,7 +467,6 @@ impl From<mania::ManiaPerformanceAttributes> for PerformanceAttributes {
} }
} }
#[cfg(feature = "osu")]
impl From<osu::OsuPerformanceAttributes> for PerformanceAttributes { impl From<osu::OsuPerformanceAttributes> for PerformanceAttributes {
#[inline] #[inline]
fn from(attributes: osu::OsuPerformanceAttributes) -> Self { fn from(attributes: osu::OsuPerformanceAttributes) -> Self {
@@ -626,7 +474,6 @@ impl From<osu::OsuPerformanceAttributes> for PerformanceAttributes {
} }
} }
#[cfg(feature = "taiko")]
impl From<taiko::TaikoPerformanceAttributes> for PerformanceAttributes { impl From<taiko::TaikoPerformanceAttributes> for PerformanceAttributes {
#[inline] #[inline]
fn from(attributes: taiko::TaikoPerformanceAttributes) -> Self { fn from(attributes: taiko::TaikoPerformanceAttributes) -> Self {
@@ -634,7 +481,6 @@ impl From<taiko::TaikoPerformanceAttributes> for PerformanceAttributes {
} }
} }
#[cfg(any(feature = "osu", feature = "taiko"))]
#[inline] #[inline]
fn difficulty_range(val: f64, max: f64, avg: f64, min: f64) -> f64 { fn difficulty_range(val: f64, max: f64, avg: f64, min: f64) -> f64 {
if val > 5.0 { if val > 5.0 {
@@ -646,13 +492,5 @@ fn difficulty_range(val: f64, max: f64, avg: f64, min: f64) -> f64 {
} }
} }
#[cfg(not(any(
feature = "osu",
feature = "taiko",
feature = "fruits",
feature = "mania"
)))]
compile_error!("At least one of the features `osu`, `taiko`, `fruits`, `mania` must be enabled");
#[cfg(all(feature = "async_tokio", feature = "async_std"))] #[cfg(all(feature = "async_tokio", feature = "async_std"))]
compile_error!("Only one of the features `async_tokio` and `async_std` should be enabled"); compile_error!("Only one of the features `async_tokio` and `async_std` should be enabled");
-2
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "mania")]
mod gradual_difficulty; mod gradual_difficulty;
mod gradual_performance; mod gradual_performance;
mod pp; mod pp;
-2
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "osu")]
mod difficulty_object; mod difficulty_object;
mod gradual_difficulty; mod gradual_difficulty;
mod gradual_performance; mod gradual_performance;
+6 -41
View File
@@ -1,17 +1,11 @@
use super::OSU_FILE_HEADER; use super::OSU_FILE_HEADER;
#[cfg(not(all( use std::{
feature = "osu", error::Error as StdError,
feature = "taiko", fmt,
feature = "fruits", io::Error as IOError,
feature = "mania" num::{ParseFloatError, ParseIntError},
)))] };
use super::GameMode;
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IOError;
use std::num::{ParseFloatError, ParseIntError};
/// `Result<_, ParseError>` /// `Result<_, ParseError>`
pub type ParseResult<T> = Result<T, ParseError>; pub type ParseResult<T> = Result<T, ParseError>;
@@ -40,15 +34,6 @@ pub enum ParseError {
TooManyRepeats, TooManyRepeats,
/// Failed to recognized specified type for hitobjects. /// Failed to recognized specified type for hitobjects.
UnknownHitObjectKind, UnknownHitObjectKind,
#[cfg(not(all(
feature = "osu",
feature = "taiko",
feature = "fruits",
feature = "mania"
)))]
/// Tried to parse a map with a game mode who's feature was not enabled
UnincludedMode(GameMode),
} }
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
@@ -66,18 +51,6 @@ impl fmt::Display for ParseError {
Self::MissingField(field) => write!(f, "missing field `{}`", field), Self::MissingField(field) => write!(f, "missing field `{}`", field),
Self::TooManyRepeats => f.write_str("repeat count is way too high"), Self::TooManyRepeats => f.write_str("repeat count is way too high"),
Self::UnknownHitObjectKind => f.write_str("unsupported hitobject kind"), Self::UnknownHitObjectKind => f.write_str("unsupported hitobject kind"),
#[cfg(not(all(
feature = "osu",
feature = "taiko",
feature = "fruits",
feature = "mania"
)))]
Self::UnincludedMode(mode) => write!(
f,
"cannot process {:?} map; its mode's feature has not been included",
mode
),
} }
} }
} }
@@ -95,14 +68,6 @@ impl StdError for ParseError {
Self::MissingField(_) => None, Self::MissingField(_) => None,
Self::TooManyRepeats => None, Self::TooManyRepeats => None,
Self::UnknownHitObjectKind => None, Self::UnknownHitObjectKind => None,
#[cfg(not(all(
feature = "osu",
feature = "taiko",
feature = "fruits",
feature = "mania"
)))]
Self::UnincludedMode(_) => None,
} }
} }
} }
-9
View File
@@ -58,7 +58,6 @@ impl PartialOrd for HitObject {
pub enum HitObjectKind { pub enum HitObjectKind {
/// A circle object. /// A circle object.
Circle, Circle,
#[cfg(feature = "sliders")]
/// A full slider object. /// A full slider object.
Slider { Slider {
/// Total length of the slider in pixels. /// Total length of the slider in pixels.
@@ -68,14 +67,6 @@ pub enum HitObjectKind {
/// The control points of the slider. /// The control points of the slider.
control_points: Vec<super::PathControlPoint>, control_points: Vec<super::PathControlPoint>,
}, },
#[cfg(not(feature = "sliders"))]
/// A partial slider object.
Slider {
/// Total length of the slider in pixels.
pixel_len: f64,
/// The amount of spans of the slider.
span_count: usize,
},
/// A spinner object. /// A spinner object.
Spinner { Spinner {
/// The end time of the spinner. /// The end time of the spinner.
+87 -238
View File
@@ -38,8 +38,7 @@ use async_std::{
path::Path, path::Path,
}; };
#[cfg(feature = "sliders")] pub use slider_parsing::*;
pub use osu_fruits::*;
fn sort_unstable<T: PartialOrd>(slice: &mut [T]) { fn sort_unstable<T: PartialOrd>(slice: &mut [T]) {
slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal)); slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal));
@@ -116,8 +115,6 @@ macro_rules! parse_general_body {
($self:ident, $reader:ident, $buf:ident, $section:ident) => {{ ($self:ident, $reader:ident, $buf:ident, $section:ident) => {{
let mut mode = None; let mut mode = None;
let mut empty = true; let mut empty = true;
#[cfg(feature = "osu")]
let mut stack_leniency = None; let mut stack_leniency = None;
while read_line!($reader, $buf)? != 0 { while read_line!($reader, $buf)? != 0 {
@@ -142,7 +139,6 @@ macro_rules! parse_general_body {
}; };
} }
#[cfg(feature = "osu")]
if key == "StackLeniency" { if key == "StackLeniency" {
stack_leniency = Some(value.parse()?); stack_leniency = Some(value.parse()?);
} }
@@ -151,31 +147,7 @@ macro_rules! parse_general_body {
} }
$self.mode = mode.unwrap_or(GameMode::STD); $self.mode = mode.unwrap_or(GameMode::STD);
$self.stack_leniency = stack_leniency.unwrap_or(0.7);
#[cfg(not(feature = "osu"))]
if $self.mode == GameMode::STD {
return Err(ParseError::UnincludedMode(GameMode::STD));
}
#[cfg(not(feature = "taiko"))]
if $self.mode == GameMode::TKO {
return Err(ParseError::UnincludedMode(GameMode::TKO));
}
#[cfg(not(feature = "fruits"))]
if $self.mode == GameMode::CTB {
return Err(ParseError::UnincludedMode(GameMode::CTB));
}
#[cfg(not(feature = "mania"))]
if $self.mode == GameMode::MNA {
return Err(ParseError::UnincludedMode(GameMode::MNA));
}
#[cfg(feature = "osu")]
{
$self.stack_leniency = stack_leniency.unwrap_or(0.7);
}
Ok(empty) Ok(empty)
}}; }};
@@ -277,61 +249,6 @@ macro_rules! parse_difficulty {
} }
macro_rules! parse_timingpoints_body { macro_rules! parse_timingpoints_body {
(short => $self:ident, $reader:ident, $buf:ident, $section:ident) => {{
let mut empty = true;
// Only parse the first timing point to calculate the bpm
if read_line!($reader, $buf)? != 0 {
let line = {
let mut line = $buf.trim_end();
if skip_line(line) {
$buf.clear();
return Ok(empty);
}
if let Some(idx) = line.find("//") {
line = &line[..idx];
}
line
};
if line.starts_with('[') && line.ends_with(']') {
*$section = Section::from_str(&line[1..line.len() - 1]);
empty = false;
$buf.clear();
return Ok(empty);
}
let beat_len = line
.split(',')
.nth(1)
.next_field("beat_len")?
.trim()
.parse()?;
$self.bpm = bpm(beat_len);
$buf.clear();
}
while read_line!($reader, $buf)? != 0 {
let line = line_prepare!($buf);
if line.starts_with('[') && line.ends_with(']') {
*$section = Section::from_str(&line[1..line.len() - 1]);
empty = false;
$buf.clear();
break;
}
$buf.clear();
}
Ok(empty)
}};
($self:ident, $reader:ident, $buf:ident, $section:ident) => {{ ($self:ident, $reader:ident, $buf:ident, $section:ident) => {{
let mut unsorted_timings = false; let mut unsorted_timings = false;
let mut unsorted_difficulties = false; let mut unsorted_difficulties = false;
@@ -408,12 +325,6 @@ macro_rules! parse_timingpoints {
buf: &mut String, buf: &mut String,
section: &mut Section, section: &mut Section,
) -> ParseResult<bool> { ) -> ParseResult<bool> {
#[cfg(not(feature = "sliders"))]
{
parse_timingpoints_body!(short => self, reader, buf, section)
}
#[cfg(feature = "sliders")]
parse_timingpoints_body!(self, reader, buf, section) parse_timingpoints_body!(self, reader, buf, section)
} }
}; };
@@ -425,12 +336,6 @@ macro_rules! parse_timingpoints {
buf: &mut String, buf: &mut String,
section: &mut Section, section: &mut Section,
) -> ParseResult<bool> { ) -> ParseResult<bool> {
#[cfg(not(feature = "sliders"))]
{
parse_timingpoints_body!(short => self, reader, buf, section)
}
#[cfg(feature = "sliders")]
parse_timingpoints_body!(self, reader, buf, section) parse_timingpoints_body!(self, reader, buf, section)
} }
}; };
@@ -446,11 +351,9 @@ macro_rules! parse_hitobjects_body {
// with each element having its lifetime bound to `buf`. // with each element having its lifetime bound to `buf`.
// To circumvent this, `point_split_raw` will contain // To circumvent this, `point_split_raw` will contain
// the actual `&str` elements transmuted into `usize`. // the actual `&str` elements transmuted into `usize`.
#[cfg(feature = "sliders")]
let mut point_split_raw: Vec<usize> = Vec::new(); let mut point_split_raw: Vec<usize> = Vec::new();
// Buffer to re-use for all sliders // Buffer to re-use for all sliders
#[cfg(feature = "sliders")]
let mut vertices = Vec::new(); let mut vertices = Vec::new();
while read_line!($reader, $buf)? != 0 { while read_line!($reader, $buf)? != 0 {
@@ -491,99 +394,85 @@ macro_rules! parse_hitobjects_body {
} else if kind & Self::SLIDER_FLAG > 0 { } else if kind & Self::SLIDER_FLAG > 0 {
$self.n_sliders += 1; $self.n_sliders += 1;
#[cfg(feature = "sliders")] let mut control_points = Vec::new();
{
let mut control_points = Vec::new();
let control_point_iter = split.next().next_field("control points")?.split('|'); let control_point_iter = split.next().next_field("control points")?.split('|');
let mut repeats: usize = split.next().next_field("repeats")?.parse()?; let mut repeats: usize = split.next().next_field("repeats")?.parse()?;
if repeats > 9000 { if repeats > 9000 {
return Err(ParseError::TooManyRepeats); return Err(ParseError::TooManyRepeats);
}
// * osu-stable treated the first span of the slider
// * as a repeat, but no repeats are happening
repeats = repeats.saturating_sub(1);
let mut start_idx = 0;
let mut end_idx = 0;
let mut first = true;
// SAFETY: `Vec<usize>` and `Vec<&str>` have the same size and layout.
let point_split: &mut Vec<&str> =
unsafe { std::mem::transmute(&mut point_split_raw) };
point_split.clear();
point_split.extend(control_point_iter);
#[allow(clippy::blocks_in_if_conditions)]
while {
end_idx += 1;
end_idx < point_split.len()
} {
// * Keep incrementing end_idx while it's not the start of a new segment
// * (indicated by having a type descriptor of length 1).
if point_split[end_idx].len() > 1 {
continue;
}
// * Multi-segmented sliders DON'T contain the end point as part of the
// * current segment as it's assumed to be the start of the next segment.
// * The start of the next segment is the index after the type descriptor.
let end_point = point_split.get(end_idx + 1).copied();
convert_points(
&point_split[start_idx..end_idx],
end_point,
first,
pos,
&mut control_points,
&mut vertices,
)?;
start_idx = end_idx;
first = false;
}
if end_idx > start_idx {
convert_points(
&point_split[start_idx..end_idx],
None,
first,
pos,
&mut control_points,
&mut vertices,
)?;
}
if control_points.is_empty() {
HitObjectKind::Circle
} else {
let pixel_len = split
.next()
.next_field("pixel len")?
.parse::<f64>()?
.max(0.0)
.min(MAX_COORDINATE_VALUE);
HitObjectKind::Slider {
repeats,
pixel_len,
control_points,
}
}
} }
#[cfg(not(feature = "sliders"))] // * osu-stable treated the first span of the slider
{ // * as a repeat, but no repeats are happening
let span_count = split.nth(1).next_field("repeats")?.parse()?; repeats = repeats.saturating_sub(1);
let pixel_len = split.next().next_field("pixel len")?.parse()?;
let mut start_idx = 0;
let mut end_idx = 0;
let mut first = true;
// SAFETY: `Vec<usize>` and `Vec<&str>` have the same size and layout.
let point_split: &mut Vec<&str> =
unsafe { std::mem::transmute(&mut point_split_raw) };
point_split.clear();
point_split.extend(control_point_iter);
#[allow(clippy::blocks_in_if_conditions)]
while {
end_idx += 1;
end_idx < point_split.len()
} {
// * Keep incrementing end_idx while it's not the start of a new segment
// * (indicated by having a type descriptor of length 1).
if point_split[end_idx].len() > 1 {
continue;
}
// * Multi-segmented sliders DON'T contain the end point as part of the
// * current segment as it's assumed to be the start of the next segment.
// * The start of the next segment is the index after the type descriptor.
let end_point = point_split.get(end_idx + 1).copied();
convert_points(
&point_split[start_idx..end_idx],
end_point,
first,
pos,
&mut control_points,
&mut vertices,
)?;
start_idx = end_idx;
first = false;
}
if end_idx > start_idx {
convert_points(
&point_split[start_idx..end_idx],
None,
first,
pos,
&mut control_points,
&mut vertices,
)?;
}
if control_points.is_empty() {
HitObjectKind::Circle
} else {
let pixel_len = split
.next()
.next_field("pixel len")?
.parse::<f64>()?
.max(0.0)
.min(MAX_COORDINATE_VALUE);
HitObjectKind::Slider { HitObjectKind::Slider {
span_count, repeats,
pixel_len, pixel_len,
control_points,
} }
} }
} else if kind & Self::SPINNER_FLAG > 0 { } else if kind & Self::SPINNER_FLAG > 0 {
@@ -818,19 +707,12 @@ pub struct Beatmap {
/// Hitsounds are only used in osu!taiko in which they represent color. /// Hitsounds are only used in osu!taiko in which they represent color.
pub sounds: Vec<u8>, pub sounds: Vec<u8>,
#[cfg(not(feature = "sliders"))]
/// Beats per minute
pub bpm: f64,
#[cfg(feature = "sliders")]
/// Timing points that indicate a new timing section. /// Timing points that indicate a new timing section.
pub timing_points: Vec<TimingPoint>, pub timing_points: Vec<TimingPoint>,
#[cfg(feature = "sliders")]
/// Timing point for the current timing section. /// Timing point for the current timing section.
pub difficulty_points: Vec<DifficultyPoint>, pub difficulty_points: Vec<DifficultyPoint>,
#[cfg(feature = "osu")]
/// The stack leniency that is used to calculate /// The stack leniency that is used to calculate
/// the stack offset for stacked positions. /// the stack offset for stacked positions.
pub stack_leniency: f32, pub stack_leniency: f32,
@@ -853,25 +735,16 @@ impl Beatmap {
} }
/// The beats per minute of the map. /// The beats per minute of the map.
#[cfg(feature = "sliders")]
#[inline] #[inline]
pub fn bpm(&self) -> f64 { pub fn bpm(&self) -> f64 {
match self.timing_points.first() { match self.timing_points.first() {
Some(point) => bpm(point.beat_len), Some(point) => point.beat_len.recip() * 1000.0 * 60.0,
None => 0.0, None => 0.0,
} }
} }
/// The beats per minute of the map.
#[cfg(not(feature = "sliders"))]
#[inline]
pub fn bpm(&self) -> f64 {
self.bpm
}
} }
#[cfg(feature = "sliders")] mod slider_parsing {
mod osu_fruits {
use crate::ParseError; use crate::ParseError;
use super::Pos2; use super::Pos2;
@@ -1053,19 +926,14 @@ impl Beatmap {
from_path!(async Path); from_path!(async Path);
} }
fn bpm(beat_len: f64) -> f64 {
beat_len.recip() * 1000.0 * 60.0
}
fn skip_line(line: &str) -> bool { fn skip_line(line: &str) -> bool {
line.is_empty() || line.starts_with("//") || line.starts_with(' ') || line.starts_with('_') line.is_empty() || line.starts_with("//") || line.starts_with(' ') || line.starts_with('_')
} }
#[inline]
fn split_colon(line: &str) -> Option<(&str, &str)> { fn split_colon(line: &str) -> Option<(&str, &str)> {
let mut split = line.split(':'); let (front, back) = line.split_once(':')?;
Some((split.next()?, split.next()?.trim())) Some((front, back.trim()))
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@@ -1152,25 +1020,12 @@ mod tests {
} }
fn map_ids() -> Vec<i32> { fn map_ids() -> Vec<i32> {
let mut map_ids = Vec::new(); vec![
2785319, // osu
if cfg!(feature = "osu") { 1974394, // mania
map_ids.push(2785319); 2118524, // fruits
} 1028484, // taiko
]
if cfg!(feature = "mania") {
map_ids.push(1974394);
}
if cfg!(feature = "fruits") {
map_ids.push(2118524);
}
if cfg!(feature = "taiko") {
map_ids.push(1028484);
}
map_ids
} }
fn print_info(map: Beatmap) { fn print_info(map: Beatmap) {
@@ -1185,14 +1040,8 @@ mod tests {
println!("slider_mult: {}", map.slider_mult); println!("slider_mult: {}", map.slider_mult);
println!("tick_rate: {}", map.tick_rate); println!("tick_rate: {}", map.tick_rate);
println!("hit_objects: {}", map.hit_objects.len()); println!("hit_objects: {}", map.hit_objects.len());
println!("stack_leniency: {}", map.stack_leniency);
#[cfg(feature = "sliders")] println!("timing_points: {}", map.timing_points.len());
{ println!("difficulty_points: {}", map.difficulty_points.len());
#[cfg(feature = "osu")]
println!("stack_leniency: {}", map.stack_leniency);
println!("timing_points: {}", map.timing_points.len());
println!("difficulty_points: {}", map.difficulty_points.len());
}
} }
} }
+30 -133
View File
@@ -1,16 +1,10 @@
use crate::{Beatmap, DifficultyAttributes, GameMode, PerformanceAttributes, ScoreState}; use crate::{
fruits::{FruitsDifficultyAttributes, FruitsPP, FruitsPerformanceAttributes},
#[cfg(feature = "fruits")] mania::{ManiaDifficultyAttributes, ManiaPP, ManiaPerformanceAttributes},
use crate::fruits::{FruitsDifficultyAttributes, FruitsPP, FruitsPerformanceAttributes}; osu::{OsuDifficultyAttributes, OsuPP, OsuPerformanceAttributes},
taiko::{TaikoDifficultyAttributes, TaikoPP, TaikoPerformanceAttributes},
#[cfg(feature = "mania")] Beatmap, DifficultyAttributes, GameMode, PerformanceAttributes, ScoreState,
use crate::mania::{ManiaDifficultyAttributes, ManiaPP, ManiaPerformanceAttributes}; };
#[cfg(feature = "osu")]
use crate::osu::{OsuDifficultyAttributes, OsuPP, OsuPerformanceAttributes};
#[cfg(feature = "taiko")]
use crate::taiko::{TaikoDifficultyAttributes, TaikoPP, TaikoPerformanceAttributes};
/// Performance calculator on maps of any mode. /// Performance calculator on maps of any mode.
/// ///
@@ -44,16 +38,12 @@ use crate::taiko::{TaikoDifficultyAttributes, TaikoPP, TaikoPerformanceAttribute
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum AnyPP<'map> { pub enum AnyPP<'map> {
#[cfg(feature = "fruits")]
/// osu!ctb performance calculator /// osu!ctb performance calculator
Fruits(FruitsPP<'map>), Fruits(FruitsPP<'map>),
#[cfg(feature = "mania")]
/// osu!mania performance calculator /// osu!mania performance calculator
Mania(ManiaPP<'map>), Mania(ManiaPP<'map>),
#[cfg(feature = "osu")]
/// osu!standard performance calculator /// osu!standard performance calculator
Osu(OsuPP<'map>), Osu(OsuPP<'map>),
#[cfg(feature = "taiko")]
/// osu!taiko performance calculator /// osu!taiko performance calculator
Taiko(TaikoPP<'map>), Taiko(TaikoPP<'map>),
} }
@@ -63,16 +53,10 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn new(map: &'map Beatmap) -> Self { pub fn new(map: &'map Beatmap) -> Self {
match map.mode { match map.mode {
#[cfg(feature = "fruits")]
GameMode::CTB => Self::Fruits(FruitsPP::new(map)), GameMode::CTB => Self::Fruits(FruitsPP::new(map)),
#[cfg(feature = "mania")]
GameMode::MNA => Self::Mania(ManiaPP::new(map)), GameMode::MNA => Self::Mania(ManiaPP::new(map)),
#[cfg(feature = "osu")]
GameMode::STD => Self::Osu(OsuPP::new(map)), GameMode::STD => Self::Osu(OsuPP::new(map)),
#[cfg(feature = "taiko")]
GameMode::TKO => Self::Taiko(TaikoPP::new(map)), GameMode::TKO => Self::Taiko(TaikoPP::new(map)),
#[allow(unreachable_patterns)]
_ => panic!("feature for mode {:?} is not enabled", map.mode),
} }
} }
@@ -81,13 +65,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn calculate(self) -> PerformanceAttributes { pub fn calculate(self) -> PerformanceAttributes {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => PerformanceAttributes::Fruits(f.calculate()), Self::Fruits(f) => PerformanceAttributes::Fruits(f.calculate()),
#[cfg(feature = "mania")]
Self::Mania(m) => PerformanceAttributes::Mania(m.calculate()), Self::Mania(m) => PerformanceAttributes::Mania(m.calculate()),
#[cfg(feature = "osu")]
Self::Osu(o) => PerformanceAttributes::Osu(o.calculate()), Self::Osu(o) => PerformanceAttributes::Osu(o.calculate()),
#[cfg(feature = "taiko")]
Self::Taiko(t) => PerformanceAttributes::Taiko(t.calculate()), Self::Taiko(t) => PerformanceAttributes::Taiko(t.calculate()),
} }
} }
@@ -98,13 +78,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn attributes(self, attributes: impl AttributeProvider) -> Self { pub fn attributes(self, attributes: impl AttributeProvider) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.attributes(attributes.attributes())), Self::Fruits(f) => Self::Fruits(f.attributes(attributes.attributes())),
#[cfg(feature = "mania")]
Self::Mania(m) => Self::Mania(m.attributes(attributes.attributes())), Self::Mania(m) => Self::Mania(m.attributes(attributes.attributes())),
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.attributes(attributes.attributes())), Self::Osu(o) => Self::Osu(o.attributes(attributes.attributes())),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.attributes(attributes.attributes())), Self::Taiko(t) => Self::Taiko(t.attributes(attributes.attributes())),
} }
} }
@@ -115,13 +91,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn mods(self, mods: u32) -> Self { pub fn mods(self, mods: u32) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.mods(mods)), Self::Fruits(f) => Self::Fruits(f.mods(mods)),
#[cfg(feature = "mania")]
Self::Mania(m) => Self::Mania(m.mods(mods)), Self::Mania(m) => Self::Mania(m.mods(mods)),
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.mods(mods)), Self::Osu(o) => Self::Osu(o.mods(mods)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.mods(mods)), Self::Taiko(t) => Self::Taiko(t.mods(mods)),
} }
} }
@@ -134,13 +106,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn passed_objects(self, passed_objects: usize) -> Self { pub fn passed_objects(self, passed_objects: usize) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.passed_objects(passed_objects)), Self::Fruits(f) => Self::Fruits(f.passed_objects(passed_objects)),
#[cfg(feature = "mania")]
Self::Mania(m) => Self::Mania(m.passed_objects(passed_objects)), Self::Mania(m) => Self::Mania(m.passed_objects(passed_objects)),
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.passed_objects(passed_objects)), Self::Osu(o) => Self::Osu(o.passed_objects(passed_objects)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.passed_objects(passed_objects)), Self::Taiko(t) => Self::Taiko(t.passed_objects(passed_objects)),
} }
} }
@@ -149,13 +117,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn state(self, state: ScoreState) -> Self { pub fn state(self, state: ScoreState) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.state(state.into())), Self::Fruits(f) => Self::Fruits(f.state(state.into())),
#[cfg(feature = "mania")]
Self::Mania(m) => Self::Mania(m.score(state.score)), Self::Mania(m) => Self::Mania(m.score(state.score)),
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.state(state.into())), Self::Osu(o) => Self::Osu(o.state(state.into())),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.state(state.into())), Self::Taiko(t) => Self::Taiko(t.state(state.into())),
} }
} }
@@ -170,13 +134,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn accuracy(self, acc: f64) -> Self { pub fn accuracy(self, acc: f64) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.accuracy(acc)), Self::Fruits(f) => Self::Fruits(f.accuracy(acc)),
#[cfg(feature = "mania")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.accuracy(acc)), Self::Osu(o) => Self::Osu(o.accuracy(acc)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.accuracy(acc)), Self::Taiko(t) => Self::Taiko(t.accuracy(acc)),
} }
} }
@@ -188,13 +148,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn misses(self, misses: usize) -> Self { pub fn misses(self, misses: usize) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.misses(misses)), Self::Fruits(f) => Self::Fruits(f.misses(misses)),
#[cfg(feature = "mania")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.misses(misses)), Self::Osu(o) => Self::Osu(o.misses(misses)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.misses(misses)), Self::Taiko(t) => Self::Taiko(t.misses(misses)),
} }
} }
@@ -206,13 +162,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn combo(self, combo: usize) -> Self { pub fn combo(self, combo: usize) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.combo(combo)), Self::Fruits(f) => Self::Fruits(f.combo(combo)),
#[cfg(feature = "mania")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.combo(combo)), Self::Osu(o) => Self::Osu(o.combo(combo)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.combo(combo)), Self::Taiko(t) => Self::Taiko(t.combo(combo)),
} }
} }
@@ -224,13 +176,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn n300(self, n300: usize) -> Self { pub fn n300(self, n300: usize) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.fruits(n300)), Self::Fruits(f) => Self::Fruits(f.fruits(n300)),
#[cfg(feature = "mania")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.n300(n300)), Self::Osu(o) => Self::Osu(o.n300(n300)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.n300(n300)), Self::Taiko(t) => Self::Taiko(t.n300(n300)),
} }
} }
@@ -242,13 +190,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn n100(self, n100: usize) -> Self { pub fn n100(self, n100: usize) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.droplets(n100)), Self::Fruits(f) => Self::Fruits(f.droplets(n100)),
#[cfg(feature = "mania")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.n100(n100)), Self::Osu(o) => Self::Osu(o.n100(n100)),
#[cfg(feature = "taiko")]
Self::Taiko(t) => Self::Taiko(t.n100(n100)), Self::Taiko(t) => Self::Taiko(t.n100(n100)),
} }
} }
@@ -260,13 +204,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
pub fn n50(self, n50: usize) -> Self { pub fn n50(self, n50: usize) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => Self::Fruits(f.tiny_droplets(n50)), Self::Fruits(f) => Self::Fruits(f.tiny_droplets(n50)),
#[cfg(feature = "mania")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(o) => Self::Osu(o.n50(n50)), Self::Osu(o) => Self::Osu(o.n50(n50)),
#[cfg(feature = "taiko")]
Self::Taiko(_) => self, Self::Taiko(_) => self,
} }
} }
@@ -279,13 +219,9 @@ impl<'map> AnyPP<'map> {
#[inline] #[inline]
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")]
Self::Mania(_) => self, Self::Mania(_) => self,
#[cfg(feature = "osu")]
Self::Osu(_) => self, Self::Osu(_) => self,
#[cfg(feature = "taiko")]
Self::Taiko(_) => self, Self::Taiko(_) => self,
} }
} }
@@ -295,17 +231,12 @@ impl<'map> AnyPP<'map> {
/// 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]
pub fn score(self, score: u32) -> Self { pub fn score(self, score: u32) -> Self {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(_) => self, Self::Fruits(_) => self,
#[cfg(feature = "mania")]
Self::Mania(m) => Self::Mania(m.score(score)), Self::Mania(m) => Self::Mania(m.score(score)),
#[cfg(feature = "osu")]
Self::Osu(_) => self, Self::Osu(_) => self,
#[cfg(feature = "taiko")]
Self::Taiko(_) => self, Self::Taiko(_) => self,
} }
} }
@@ -328,70 +259,36 @@ impl AttributeProvider for PerformanceAttributes {
#[inline] #[inline]
fn attributes(self) -> DifficultyAttributes { fn attributes(self) -> DifficultyAttributes {
match self { match self {
#[cfg(feature = "fruits")]
Self::Fruits(f) => DifficultyAttributes::Fruits(f.difficulty), Self::Fruits(f) => DifficultyAttributes::Fruits(f.difficulty),
#[cfg(feature = "mania")]
Self::Mania(m) => DifficultyAttributes::Mania(m.difficulty), Self::Mania(m) => DifficultyAttributes::Mania(m.difficulty),
#[cfg(feature = "osu")]
Self::Osu(o) => DifficultyAttributes::Osu(o.difficulty), Self::Osu(o) => DifficultyAttributes::Osu(o.difficulty),
#[cfg(feature = "taiko")]
Self::Taiko(t) => DifficultyAttributes::Taiko(t.difficulty), Self::Taiko(t) => DifficultyAttributes::Taiko(t.difficulty),
} }
} }
} }
#[cfg(feature = "fruits")] macro_rules! impl_attr_provider {
impl AttributeProvider for FruitsDifficultyAttributes { ($mode:ident: $difficulty:ident, $performance:ident) => {
fn attributes(self) -> DifficultyAttributes { impl AttributeProvider for $difficulty {
DifficultyAttributes::Fruits(self) #[inline]
} fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::$mode(self)
}
}
impl AttributeProvider for $performance {
#[inline]
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::$mode(self.difficulty)
}
}
};
} }
#[cfg(feature = "mania")] impl_attr_provider!(
impl AttributeProvider for ManiaDifficultyAttributes { Fruits: FruitsDifficultyAttributes,
fn attributes(self) -> DifficultyAttributes { FruitsPerformanceAttributes
DifficultyAttributes::Mania(self) );
} impl_attr_provider!(Mania: ManiaDifficultyAttributes, ManiaPerformanceAttributes);
} impl_attr_provider!(Osu: OsuDifficultyAttributes, OsuPerformanceAttributes);
impl_attr_provider!(Taiko: TaikoDifficultyAttributes, TaikoPerformanceAttributes);
#[cfg(feature = "osu")]
impl AttributeProvider for OsuDifficultyAttributes {
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::Osu(self)
}
}
#[cfg(feature = "taiko")]
impl AttributeProvider for TaikoDifficultyAttributes {
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::Taiko(self)
}
}
#[cfg(feature = "fruits")]
impl AttributeProvider for FruitsPerformanceAttributes {
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::Fruits(self.difficulty)
}
}
#[cfg(feature = "mania")]
impl AttributeProvider for ManiaPerformanceAttributes {
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::Mania(self.difficulty)
}
}
#[cfg(feature = "osu")]
impl AttributeProvider for OsuPerformanceAttributes {
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::Osu(self.difficulty)
}
}
#[cfg(feature = "taiko")]
impl AttributeProvider for TaikoPerformanceAttributes {
fn attributes(self) -> DifficultyAttributes {
DifficultyAttributes::Taiko(self.difficulty)
}
}
-2
View File
@@ -1,5 +1,3 @@
#![cfg(feature = "taiko")]
mod difficulty_object; mod difficulty_object;
mod gradual_difficulty; mod gradual_difficulty;
mod gradual_performance; mod gradual_performance;