From b3d0475d47929b1ff75c316d6661bb84fa099e9c Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 11 Nov 2024 02:51:43 +0100 Subject: [PATCH 1/3] feat!: overhaul convert api --- src/any/difficulty/converted.rs | 114 ----------------- src/any/difficulty/gradual.rs | 54 +++----- src/any/difficulty/mod.rs | 91 ++++++++----- src/any/mod.rs | 3 +- src/any/performance/gradual.rs | 54 +++----- src/any/performance/into.rs | 67 ++++------ src/any/performance/mod.rs | 40 +++--- src/catch/convert.rs | 41 ++---- src/catch/difficulty/gradual.rs | 41 +++--- src/catch/difficulty/mod.rs | 35 +++-- src/catch/mod.rs | 40 +++--- src/catch/object/juice_stream.rs | 15 ++- src/catch/performance/gradual.rs | 29 +++-- src/catch/performance/mod.rs | 72 +++++------ src/catch/strains.rs | 15 ++- src/lib.rs | 5 +- src/mania/convert/mod.rs | 38 +----- src/mania/difficulty/gradual.rs | 59 ++++----- src/mania/difficulty/mod.rs | 32 +++-- src/mania/mod.rs | 42 +++--- src/mania/performance/gradual.rs | 40 +++--- src/mania/performance/mod.rs | 85 ++++++------- src/mania/strains.rs | 15 ++- src/model/beatmap/attributes.rs | 8 +- src/model/beatmap/converted.rs | 211 ------------------------------- src/model/beatmap/mod.rs | 112 ++++++++-------- src/model/mode.rs | 85 +++++++------ src/osu/convert.rs | 34 +---- src/osu/difficulty/gradual.rs | 43 +++---- src/osu/difficulty/mod.rs | 29 +++-- src/osu/mod.rs | 32 ++--- src/osu/object.rs | 19 +-- src/osu/performance/gradual.rs | 38 +++--- src/osu/performance/mod.rs | 102 +++++++++------ src/osu/score_state.rs | 1 + src/osu/strains.rs | 19 +-- src/taiko/convert.rs | 30 +---- src/taiko/difficulty/gradual.rs | 61 ++++----- src/taiko/difficulty/mod.rs | 26 ++-- src/taiko/mod.rs | 40 +++--- src/taiko/performance/gradual.rs | 39 +++--- src/taiko/performance/mod.rs | 75 ++++++----- src/taiko/strains.rs | 13 +- src/util/generic_fmt.rs | 30 ----- src/util/map_or_attrs.rs | 21 ++- src/util/mod.rs | 1 - tests/decode.rs | 10 +- tests/difficulty.rs | 11 +- tests/performance.rs | 14 +- 49 files changed, 835 insertions(+), 1296 deletions(-) delete mode 100644 src/any/difficulty/converted.rs delete mode 100644 src/model/beatmap/converted.rs delete mode 100644 src/util/generic_fmt.rs diff --git a/src/any/difficulty/converted.rs b/src/any/difficulty/converted.rs deleted file mode 100644 index b6d64f23..00000000 --- a/src/any/difficulty/converted.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::{ - fmt::{Debug, Formatter, Result as FmtResult}, - marker::PhantomData, -}; - -use crate::{ - model::{beatmap::Converted, mode::IGameMode}, - util::generic_fmt::GenericFormatter, - Difficulty, -}; - -/// Difficulty calculator on maps of a given mode. -/// -/// # Example -/// -/// ``` -/// use rosu_pp::{Beatmap, Difficulty}; -/// use rosu_pp::catch::{Catch, CatchDifficultyAttributes}; -/// -/// let converted = Beatmap::from_path("./resources/2118524.osu") -/// .unwrap() -/// .unchecked_into_converted(); -/// -/// let attrs: CatchDifficultyAttributes = Difficulty::new() -/// .mods(8 + 1024) // HDFL -/// .with_mode::() // -> `ConvertedDifficulty` -/// .calculate(&converted); -/// ``` -#[must_use] -pub struct ConvertedDifficulty<'a, M> { - inner: &'a Difficulty, - _mode: PhantomData, -} - -impl<'a, M> ConvertedDifficulty<'a, M> { - pub(crate) const fn new(difficulty: &'a Difficulty) -> Self { - Self { - inner: difficulty, - _mode: PhantomData, - } - } - - /// Return the internal [`Difficulty`]. - pub const fn inner(self) -> &'a Difficulty { - self.inner - } - - /// Cast from generic mode `M` to `N`. - pub const fn cast(self) -> ConvertedDifficulty<'a, N> { - ConvertedDifficulty { - inner: self.inner, - _mode: PhantomData, - } - } -} - -impl ConvertedDifficulty<'_, M> { - /// Perform the difficulty calculation for a [`Converted`] beatmap and - /// process the final skill values. - pub fn calculate(self, map: &Converted<'_, M>) -> M::DifficultyAttributes { - M::difficulty(self.inner, map) - } - - /// Perform a difficulty calculation for a [`Converted`] beatmap without - /// processing the final skill values. - pub fn strains(self, map: &Converted<'_, M>) -> M::Strains { - M::strains(self.inner, map) - } - - /// Create a gradual difficulty calculator for a [`Converted`] beatmap. - pub fn gradual_difficulty(self, map: &Converted<'_, M>) -> M::GradualDifficulty { - M::gradual_difficulty(self.inner.to_owned(), map) - } - - /// Create a gradual performance calculator for a [`Converted`] beatmap. - pub fn gradual_performance(self, map: &Converted<'_, M>) -> M::GradualPerformance { - M::gradual_performance(self.inner.to_owned(), map) - } -} - -impl<'a, M: IGameMode> From<&'a Difficulty> for ConvertedDifficulty<'a, M> { - fn from(difficulty: &'a Difficulty) -> Self { - Self::new(difficulty) - } -} - -impl AsRef for ConvertedDifficulty<'_, M> { - fn as_ref(&self) -> &Difficulty { - self.inner - } -} - -impl Copy for ConvertedDifficulty<'_, M> {} - -impl Clone for ConvertedDifficulty<'_, M> { - fn clone(&self) -> Self { - *self - } -} - -impl Debug for ConvertedDifficulty<'_, M> { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.debug_struct("ConvertedDifficulty") - .field("inner", self.inner) - .field("mode", &GenericFormatter::::new()) - .finish() - } -} - -impl PartialEq for ConvertedDifficulty<'_, M> { - fn eq(&self, other: &Self) -> bool { - self.inner == other.inner - } -} diff --git a/src/any/difficulty/gradual.rs b/src/any/difficulty/gradual.rs index 2857588d..591e6db7 100644 --- a/src/any/difficulty/gradual.rs +++ b/src/any/difficulty/gradual.rs @@ -1,15 +1,13 @@ -use std::borrow::Cow; - use rosu_map::section::general::GameMode; use crate::{ any::DifficultyAttributes, - catch::{Catch, CatchBeatmap, CatchGradualDifficulty}, - mania::{Mania, ManiaBeatmap, ManiaGradualDifficulty}, - model::mode::IGameMode, - osu::{Osu, OsuBeatmap, OsuGradualDifficulty}, - taiko::{Taiko, TaikoBeatmap, TaikoGradualDifficulty}, - Beatmap, Converted, Difficulty, + catch::{Catch, CatchGradualDifficulty}, + mania::{Mania, ManiaGradualDifficulty}, + model::mode::{ConvertError, IGameMode}, + osu::{Osu, OsuGradualDifficulty}, + taiko::{Taiko, TaikoGradualDifficulty}, + Beatmap, Difficulty, }; /// Gradually calculate the difficulty attributes on maps of any mode. @@ -51,38 +49,26 @@ pub enum GradualDifficulty { Mania(ManiaGradualDifficulty), } -macro_rules! from_converted { - ( $fn:ident, $mode:ident, $converted:ident ) => { - #[doc = concat!("Create a [`GradualDifficulty`] for a [`", stringify!($converted), "`]")] - pub fn $fn(difficulty: Difficulty, converted: &$converted<'_>) -> Self { - Self::$mode($mode::gradual_difficulty(difficulty, converted)) - } - }; -} - impl GradualDifficulty { /// Create a [`GradualDifficulty`] for a map of any mode. + #[allow(clippy::missing_panics_doc)] pub fn new(difficulty: Difficulty, map: &Beatmap) -> Self { - let map = Cow::Borrowed(map); + Self::new_with_mode(difficulty, map, map.mode).expect("no conversion required") + } - match map.mode { - GameMode::Osu => Self::Osu(Osu::gradual_difficulty(difficulty, &Converted::new(map))), - GameMode::Taiko => { - Self::Taiko(Taiko::gradual_difficulty(difficulty, &Converted::new(map))) - } - GameMode::Catch => { - Self::Catch(Catch::gradual_difficulty(difficulty, &Converted::new(map))) - } - GameMode::Mania => { - Self::Mania(Mania::gradual_difficulty(difficulty, &Converted::new(map))) - } + /// Create a [`GradualDifficulty`] for a [`Beatmap`] on a specific [`GameMode`]. + pub fn new_with_mode( + difficulty: Difficulty, + map: &Beatmap, + mode: GameMode, + ) -> Result { + match mode { + GameMode::Osu => Osu::gradual_difficulty(difficulty, map).map(Self::Osu), + GameMode::Taiko => Taiko::gradual_difficulty(difficulty, map).map(Self::Taiko), + GameMode::Catch => Catch::gradual_difficulty(difficulty, map).map(Self::Catch), + GameMode::Mania => Mania::gradual_difficulty(difficulty, map).map(Self::Mania), } } - - from_converted!(from_osu_map, Osu, OsuBeatmap); - from_converted!(from_taiko_map, Taiko, TaikoBeatmap); - from_converted!(from_catch_map, Catch, CatchBeatmap); - from_converted!(from_mania_map, Mania, ManiaBeatmap); } impl Iterator for GradualDifficulty { diff --git a/src/any/difficulty/mod.rs b/src/any/difficulty/mod.rs index 8a923958..a5aa1f3a 100644 --- a/src/any/difficulty/mod.rs +++ b/src/any/difficulty/mod.rs @@ -1,5 +1,4 @@ use std::{ - borrow::Cow, fmt::{Debug, Formatter, Result as FmtResult}, num::NonZeroU64, }; @@ -9,20 +8,14 @@ use rosu_map::section::general::GameMode; use crate::{ catch::Catch, mania::Mania, - model::{ - beatmap::{Beatmap, Converted}, - mods::GameMods, - }, + model::{beatmap::Beatmap, mode::ConvertError, mods::GameMods}, osu::Osu, taiko::Taiko, GradualDifficulty, GradualPerformance, }; -use self::converted::ConvertedDifficulty; - use super::{attributes::DifficultyAttributes, InspectDifficulty, Strains}; -pub mod converted; pub mod gradual; pub mod inspect; pub mod object; @@ -100,14 +93,6 @@ impl Difficulty { } } - /// Use this [`Difficulty`] as a calculator for a specific [`IGameMode`]. - /// - /// Note that [`ConvertedDifficulty`] won't allow to further customize - /// fields so be sure they're all set before converting to it. - pub const fn with_mode(&self) -> ConvertedDifficulty<'_, M> { - ConvertedDifficulty::new(self) - } - /// Turn this [`Difficulty`] into a [`InspectDifficulty`] to inspect its /// configured values. pub fn inspect(self) -> InspectDifficulty { @@ -281,48 +266,86 @@ impl Difficulty { } /// Perform the difficulty calculation. + #[allow(clippy::missing_panics_doc)] pub fn calculate(&self, map: &Beatmap) -> DifficultyAttributes { - let map = Cow::Borrowed(map); - match map.mode { - GameMode::Osu => DifficultyAttributes::Osu(Osu::difficulty(self, &Converted::new(map))), - GameMode::Taiko => { - DifficultyAttributes::Taiko(Taiko::difficulty(self, &Converted::new(map))) - } - GameMode::Catch => { - DifficultyAttributes::Catch(Catch::difficulty(self, &Converted::new(map))) - } - GameMode::Mania => { - DifficultyAttributes::Mania(Mania::difficulty(self, &Converted::new(map))) - } + GameMode::Osu => DifficultyAttributes::Osu( + Osu::difficulty(self, map).expect("no conversion required"), + ), + GameMode::Taiko => DifficultyAttributes::Taiko( + Taiko::difficulty(self, map).expect("no conversion required"), + ), + GameMode::Catch => DifficultyAttributes::Catch( + Catch::difficulty(self, map).expect("no conversion required"), + ), + GameMode::Mania => DifficultyAttributes::Mania( + Mania::difficulty(self, map).expect("no conversion required"), + ), } } + /// Perform the difficulty calculation for a specific [`IGameMode`]. + pub fn calculate_for_mode( + &self, + map: &Beatmap, + ) -> Result { + M::difficulty(self, map) + } + /// Perform the difficulty calculation but instead of evaluating the skill /// strains, return them as is. /// /// Suitable to plot the difficulty of a map over time. + #[allow(clippy::missing_panics_doc)] pub fn strains(&self, map: &Beatmap) -> Strains { - let map = Cow::Borrowed(map); - match map.mode { - GameMode::Osu => Strains::Osu(Osu::strains(self, &Converted::new(map))), - GameMode::Taiko => Strains::Taiko(Taiko::strains(self, &Converted::new(map))), - GameMode::Catch => Strains::Catch(Catch::strains(self, &Converted::new(map))), - GameMode::Mania => Strains::Mania(Mania::strains(self, &Converted::new(map))), + GameMode::Osu => Strains::Osu(Osu::strains(self, map).expect("no conversion required")), + GameMode::Taiko => { + Strains::Taiko(Taiko::strains(self, map).expect("no conversion required")) + } + GameMode::Catch => { + Strains::Catch(Catch::strains(self, map).expect("no conversion required")) + } + GameMode::Mania => { + Strains::Mania(Mania::strains(self, map).expect("no conversion required")) + } } } + /// Perform the strain calculation for a specific [`IGameMode`]. + pub fn strains_for_mode( + &self, + map: &Beatmap, + ) -> Result { + M::strains(self, map) + } + /// Create a gradual difficulty calculator for a [`Beatmap`]. pub fn gradual_difficulty(self, map: &Beatmap) -> GradualDifficulty { GradualDifficulty::new(self, map) } + /// Create a gradual difficulty calculator for a [`Beatmap`] on a specific [`IGameMode`]. + pub fn gradual_difficulty_for_mode( + self, + map: &Beatmap, + ) -> Result { + M::gradual_difficulty(self, map) + } + /// Create a gradual performance calculator for a [`Beatmap`]. pub fn gradual_performance(self, map: &Beatmap) -> GradualPerformance { GradualPerformance::new(self, map) } + /// Create a gradual performance calculator for a [`Beatmap`] on a specific [`IGameMode`]. + pub fn gradual_performance_for_mode( + self, + map: &Beatmap, + ) -> Result { + M::gradual_performance(self, map) + } + pub(crate) const fn get_mods(&self) -> &GameMods { &self.mods } diff --git a/src/any/mod.rs b/src/any/mod.rs index 2088b5e0..88c77acf 100644 --- a/src/any/mod.rs +++ b/src/any/mod.rs @@ -1,8 +1,7 @@ pub use self::{ attributes::{DifficultyAttributes, PerformanceAttributes}, difficulty::{ - converted::ConvertedDifficulty, gradual::GradualDifficulty, inspect::InspectDifficulty, - Difficulty, ModsDependent, + gradual::GradualDifficulty, inspect::InspectDifficulty, Difficulty, ModsDependent, }, performance::{ gradual::GradualPerformance, diff --git a/src/any/performance/gradual.rs b/src/any/performance/gradual.rs index d0d401e4..95034178 100644 --- a/src/any/performance/gradual.rs +++ b/src/any/performance/gradual.rs @@ -1,15 +1,13 @@ -use std::borrow::Cow; - use rosu_map::section::general::GameMode; use crate::{ any::{PerformanceAttributes, ScoreState}, - catch::{Catch, CatchBeatmap, CatchGradualPerformance}, - mania::{Mania, ManiaBeatmap, ManiaGradualPerformance}, - model::mode::IGameMode, - osu::{Osu, OsuBeatmap, OsuGradualPerformance}, - taiko::{Taiko, TaikoBeatmap, TaikoGradualPerformance}, - Beatmap, Converted, Difficulty, + catch::{Catch, CatchGradualPerformance}, + mania::{Mania, ManiaGradualPerformance}, + model::mode::{ConvertError, IGameMode}, + osu::{Osu, OsuGradualPerformance}, + taiko::{Taiko, TaikoGradualPerformance}, + Beatmap, Difficulty, }; /// Gradually calculate the performance attributes on maps of any mode. @@ -100,39 +98,27 @@ pub enum GradualPerformance { Mania(ManiaGradualPerformance), } -macro_rules! from_converted { - ( $fn:ident, $mode:ident, $converted:ident ) => { - #[doc = concat!("Create a [`GradualPerformance`] for a [`", stringify!($converted), "`]")] - pub fn $fn(difficulty: Difficulty, converted: &$converted<'_>) -> Self { - Self::$mode($mode::gradual_performance(difficulty, converted)) - } - }; -} - impl GradualPerformance { /// Create a [`GradualPerformance`] for a map of any mode. + #[allow(clippy::missing_panics_doc)] pub fn new(difficulty: Difficulty, map: &Beatmap) -> Self { - let map = Cow::Borrowed(map); + Self::new_with_mode(difficulty, map, map.mode).expect("no conversion required") + } - match map.mode { - GameMode::Osu => Self::Osu(Osu::gradual_performance(difficulty, &Converted::new(map))), - GameMode::Taiko => { - Self::Taiko(Taiko::gradual_performance(difficulty, &Converted::new(map))) - } - GameMode::Catch => { - Self::Catch(Catch::gradual_performance(difficulty, &Converted::new(map))) - } - GameMode::Mania => { - Self::Mania(Mania::gradual_performance(difficulty, &Converted::new(map))) - } + /// Create a [`GradualPerformance`] for a [`Beatmap`] on a specific [`GameMode`]. + pub fn new_with_mode( + difficulty: Difficulty, + map: &Beatmap, + mode: GameMode, + ) -> Result { + match mode { + GameMode::Osu => Osu::gradual_performance(difficulty, map).map(Self::Osu), + GameMode::Taiko => Taiko::gradual_performance(difficulty, map).map(Self::Taiko), + GameMode::Catch => Catch::gradual_performance(difficulty, map).map(Self::Catch), + GameMode::Mania => Mania::gradual_performance(difficulty, map).map(Self::Mania), } } - from_converted!(from_osu_map, Osu, OsuBeatmap); - from_converted!(from_taiko_map, Taiko, TaikoBeatmap); - from_converted!(from_catch_map, Catch, CatchBeatmap); - from_converted!(from_mania_map, Mania, ManiaBeatmap); - /// Process the next hit object and calculate the performance attributes /// for the resulting score state. pub fn next(&mut self, state: ScoreState) -> Option { diff --git a/src/any/performance/into.rs b/src/any/performance/into.rs index 21f3de96..723be590 100644 --- a/src/any/performance/into.rs +++ b/src/any/performance/into.rs @@ -1,11 +1,9 @@ -use std::borrow::Cow; - use rosu_map::section::general::GameMode; use crate::{ any::{DifficultyAttributes, PerformanceAttributes}, model::mode::IGameMode, - Beatmap, Converted, Performance, + Beatmap, Performance, }; /// Turning a type into the generic [`IGameMode`]'s performance calculator. @@ -31,18 +29,6 @@ macro_rules! impl_from_mode { () => { crate::$module::$mode }; } - impl<'map> IntoModePerformance<'map, mode!()> for Converted<'map, mode!()> { - fn into_performance(self) -> ::Performance<'map> { - ::Performance::from_map_or_attrs(self.into()) - } - } - - impl<'map> IntoModePerformance<'map, mode!()> for &'map Converted<'_, mode!()> { - fn into_performance(self) -> ::Performance<'map> { - ::Performance::from_map_or_attrs(self.as_owned().into()) - } - } - impl<'map> IntoModePerformance<'map, mode!()> for crate::$module::$diff { fn into_performance(self) -> ::Performance<'map> { ::Performance::from_map_or_attrs(self.into()) @@ -55,22 +41,6 @@ macro_rules! impl_from_mode { } } - impl<'map> IntoPerformance<'map> for Converted<'map, mode!()> { - fn into_performance(self) -> Performance<'map> { - Performance::$mode( - >::into_performance(self) - ) - } - } - - impl<'map> IntoPerformance<'map> for &'map Converted<'_, mode!()> { - fn into_performance(self) -> Performance<'map> { - Performance::$mode( - >::into_performance(self) - ) - } - } - impl<'a> IntoPerformance<'a> for crate::$module::$diff { fn into_performance(self) -> Performance<'a> { Performance::$mode( @@ -86,6 +56,18 @@ macro_rules! impl_from_mode { ) } } + + impl<'map> IntoModePerformance<'map, mode!()> for &'map Beatmap { + fn into_performance(self) -> ::Performance<'map> { + ::Performance::from_map_or_attrs(self.into()) + } + } + + impl<'a> IntoModePerformance<'a, mode!()> for Beatmap { + fn into_performance(self) -> ::Performance<'a> { + ::Performance::from_map_or_attrs(self.into()) + } + } )* }; } @@ -115,22 +97,23 @@ impl_from_mode!( impl<'a> IntoPerformance<'a> for Beatmap { fn into_performance(self) -> Performance<'a> { - map_to_performance(self.mode, Cow::Owned(self)) + match self.mode { + GameMode::Osu => Performance::Osu(self.into()), + GameMode::Taiko => Performance::Taiko(self.into()), + GameMode::Catch => Performance::Catch(self.into()), + GameMode::Mania => Performance::Mania(self.into()), + } } } impl<'map> IntoPerformance<'map> for &'map Beatmap { fn into_performance(self) -> Performance<'map> { - map_to_performance(self.mode, Cow::Borrowed(self)) - } -} - -fn map_to_performance(mode: GameMode, map: Cow<'_, Beatmap>) -> Performance<'_> { - match mode { - GameMode::Osu => Performance::Osu(Converted::new(map).into()), - GameMode::Taiko => Performance::Taiko(Converted::new(map).into()), - GameMode::Catch => Performance::Catch(Converted::new(map).into()), - GameMode::Mania => Performance::Mania(Converted::new(map).into()), + match self.mode { + GameMode::Osu => Performance::Osu(self.into()), + GameMode::Taiko => Performance::Taiko(self.into()), + GameMode::Catch => Performance::Catch(self.into()), + GameMode::Mania => Performance::Mania(self.into()), + } } } diff --git a/src/any/performance/mod.rs b/src/any/performance/mod.rs index ca6d66e0..1352bcdf 100644 --- a/src/any/performance/mod.rs +++ b/src/any/performance/mod.rs @@ -2,7 +2,7 @@ use rosu_map::section::general::GameMode; use crate::{ catch::CatchPerformance, mania::ManiaPerformance, osu::OsuPerformance, taiko::TaikoPerformance, - Difficulty, + Difficulty, GameMods, }; use self::into::IntoPerformance; @@ -29,7 +29,7 @@ impl<'map> Performance<'map> { /// - previously calculated attributes ([`DifficultyAttributes`], /// [`PerformanceAttributes`], or mode-specific attributes like /// [`TaikoDifficultyAttributes`], [`ManiaPerformanceAttributes`], ...) - /// - a beatmap ([`Beatmap`] or [`Converted<'_, M>`]) + /// - a [`Beatmap`] (by reference or value) /// /// If a map is given, difficulty attributes will need to be calculated /// internally which is a costly operation. Hence, passing attributes @@ -40,7 +40,6 @@ impl<'map> Performance<'map> { /// Otherwise, the final attributes will be incorrect. /// /// [`Beatmap`]: crate::model::beatmap::Beatmap - /// [`Converted<'_, M>`]: crate::model::beatmap::Converted /// [`DifficultyAttributes`]: crate::any::DifficultyAttributes /// [`TaikoDifficultyAttributes`]: crate::taiko::TaikoDifficultyAttributes /// [`ManiaPerformanceAttributes`]: crate::mania::ManiaPerformanceAttributes @@ -50,12 +49,21 @@ impl<'map> Performance<'map> { /// Consume the performance calculator and calculate /// performance attributes for the given parameters. + #[allow(clippy::missing_panics_doc)] pub fn calculate(self) -> PerformanceAttributes { match self { - Self::Osu(o) => PerformanceAttributes::Osu(o.calculate()), - Self::Taiko(t) => PerformanceAttributes::Taiko(t.calculate()), - Self::Catch(f) => PerformanceAttributes::Catch(f.calculate()), - Self::Mania(m) => PerformanceAttributes::Mania(m.calculate()), + Self::Osu(o) => { + PerformanceAttributes::Osu(o.calculate().expect("no conversion required")) + } + Self::Taiko(t) => { + PerformanceAttributes::Taiko(t.calculate().expect("no conversion required")) + } + Self::Catch(f) => { + PerformanceAttributes::Catch(f.calculate().expect("no conversion required")) + } + Self::Mania(m) => { + PerformanceAttributes::Mania(m.calculate().expect("no conversion required")) + } } } @@ -108,7 +116,7 @@ impl<'map> Performance<'map> { /// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode) /// /// See - pub fn mods(self, mods: u32) -> Self { + pub fn mods(self, mods: impl Into) -> Self { match self { Self::Osu(o) => Self::Osu(o.mods(mods)), Self::Taiko(t) => Self::Taiko(t.mods(mods)), @@ -404,12 +412,13 @@ impl<'map> Performance<'map> { } /// Create the [`ScoreState`] that will be used for performance calculation. + #[allow(clippy::missing_panics_doc)] pub fn generate_state(&mut self) -> ScoreState { match self { - Self::Osu(o) => o.generate_state().into(), - Self::Taiko(t) => t.generate_state().into(), - Self::Catch(f) => f.generate_state().into(), - Self::Mania(m) => m.generate_state().into(), + Self::Osu(o) => o.generate_state().expect("no conversion required").into(), + Self::Taiko(t) => t.generate_state().expect("no conversion required").into(), + Self::Catch(f) => f.generate_state().expect("no conversion required").into(), + Self::Mania(m) => m.generate_state().expect("no conversion required").into(), } } } @@ -446,7 +455,7 @@ mod tests { catch::{CatchDifficultyAttributes, CatchPerformanceAttributes}, mania::{ManiaDifficultyAttributes, ManiaPerformanceAttributes}, osu::{OsuDifficultyAttributes, OsuPerformanceAttributes}, - taiko::{Taiko, TaikoDifficultyAttributes, TaikoPerformanceAttributes}, + taiko::{TaikoDifficultyAttributes, TaikoPerformanceAttributes}, Beatmap, }; @@ -455,10 +464,7 @@ mod tests { #[test] fn create() { let map = Beatmap::from_path("./resources/1028484.osu").unwrap(); - let converted = map.unchecked_as_converted::(); - let _ = Performance::new(&converted); - let _ = Performance::new(converted.as_owned()); let _ = Performance::new(&map); let _ = Performance::new(map.clone()); @@ -477,8 +483,6 @@ mod tests { TaikoPerformanceAttributes::default(), )); - let _ = Performance::from(&converted); - let _ = Performance::from(converted); let _ = Performance::from(&map); let _ = Performance::from(map); diff --git a/src/catch/convert.rs b/src/catch/convert.rs index 315b78b3..2d0067f2 100644 --- a/src/catch/convert.rs +++ b/src/catch/convert.rs @@ -2,9 +2,8 @@ use rosu_map::section::{general::GameMode, hit_objects::CurveBuffers}; use crate::{ model::{ - beatmap::{Beatmap, Converted}, + beatmap::Beatmap, hit_object::{HitObject, HitObjectKind, HoldNote, Spinner}, - mode::ConvertStatus, mods::Reflection, }, util::{float_ext::FloatExt, random::Random}, @@ -19,37 +18,18 @@ use super::{ juice_stream::{JuiceStream, JuiceStreamBufs, NestedJuiceStreamObjectKind}, palpable::PalpableObject, }, - Catch, PLAYFIELD_WIDTH, + PLAYFIELD_WIDTH, }; const RNG_SEED: i32 = 1337; -/// A [`Beatmap`] for [`Catch`] calculations. -pub type CatchBeatmap<'a> = Converted<'a, Catch>; - -pub const fn check_convert(map: &Beatmap) -> ConvertStatus { - match map.mode { - GameMode::Osu => ConvertStatus::Conversion, - GameMode::Catch => ConvertStatus::Noop, - GameMode::Taiko | GameMode::Mania => ConvertStatus::Incompatible, - } -} - -pub fn try_convert(map: &mut Beatmap) -> ConvertStatus { - match map.mode { - GameMode::Osu => { - map.mode = GameMode::Catch; - map.is_convert = true; - - ConvertStatus::Conversion - } - GameMode::Catch => ConvertStatus::Noop, - GameMode::Taiko | GameMode::Mania => ConvertStatus::Incompatible, - } +pub fn convert(map: &mut Beatmap) { + map.mode = GameMode::Catch; + map.is_convert = true; } pub fn convert_objects( - converted: &CatchBeatmap<'_>, + map: &Beatmap, count: &mut ObjectCountBuilder, reflection: Reflection, hr_offsets: bool, @@ -70,8 +50,8 @@ pub fn convert_objects( let mut last_pos = None; let mut last_start_time = 0.0; - for h in converted.hit_objects.iter() { - let mut new_objects = convert_object(h, converted, count, &mut bufs); + for h in map.hit_objects.iter() { + let mut new_objects = convert_object(h, map, count, &mut bufs); apply_pos_offset( &mut new_objects, @@ -99,7 +79,7 @@ pub fn convert_objects( fn convert_object<'a>( h: &'a HitObject, - converted: &CatchBeatmap<'_>, + map: &Beatmap, count: &mut ObjectCountBuilder, bufs: &'a mut JuiceStreamBufs, ) -> ObjectIter<'a> { @@ -107,8 +87,7 @@ fn convert_object<'a>( HitObjectKind::Circle => ObjectIterState::Fruit(Some(Fruit::new(count))), HitObjectKind::Slider(ref slider) => { let effective_x = h.pos.x.clamp(0.0, PLAYFIELD_WIDTH); - let stream = - JuiceStream::new(effective_x, h.start_time, slider, converted, count, bufs); + let stream = JuiceStream::new(effective_x, h.start_time, slider, map, count, bufs); ObjectIterState::JuiceStream(stream) } diff --git a/src/catch/difficulty/gradual.rs b/src/catch/difficulty/gradual.rs index c6d473cc..c5223020 100644 --- a/src/catch/difficulty/gradual.rs +++ b/src/catch/difficulty/gradual.rs @@ -1,13 +1,16 @@ use std::cmp; +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::skills::Skill, catch::{ attributes::{GradualObjectCount, ObjectCountBuilder}, convert::convert_objects, - CatchBeatmap, CatchDifficultyAttributes, + CatchDifficultyAttributes, }, - Difficulty, + model::mode::ConvertError, + Beatmap, Difficulty, }; use super::{ @@ -65,18 +68,20 @@ pub struct CatchGradualDifficulty { impl CatchGradualDifficulty { /// Create a new difficulty attributes iterator for osu!catch maps. - pub fn new(difficulty: Difficulty, converted: &CatchBeatmap<'_>) -> Self { + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Catch, difficulty.get_mods())?; + let clock_rate = difficulty.get_clock_rate(); let CatchDifficultySetup { map_attrs, attrs } = - CatchDifficultySetup::new(&difficulty, converted); + CatchDifficultySetup::new(&difficulty, &map); let hr_offsets = difficulty.get_hardrock_offsets(); let reflection = difficulty.get_mods().reflection(); let mut count = ObjectCountBuilder::new_gradual(); let palpable_objects = convert_objects( - converted, + &map, &mut count, reflection, hr_offsets, @@ -92,14 +97,14 @@ impl CatchGradualDifficulty { let count = count.into_gradual(); let movement = Movement::new(clock_rate); - Self { + Ok(Self { idx: 0, difficulty, attrs, count, diff_objects, movement, - } + }) } } @@ -168,30 +173,26 @@ impl ExactSizeIterator for CatchGradualDifficulty { #[cfg(test)] mod tests { - use crate::Beatmap; + use crate::{catch::Catch, Beatmap}; use super::*; #[test] fn empty() { - let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted(); - - let mut gradual = CatchGradualDifficulty::new(Difficulty::new(), &converted); - + let map = Beatmap::from_bytes(&[]).unwrap(); + let mut gradual = CatchGradualDifficulty::new(Difficulty::new(), &map).unwrap(); assert!(gradual.next().is_none()); } #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/2118524.osu") - .unwrap() - .unchecked_into_converted(); + let map = Beatmap::from_path("./resources/2118524.osu").unwrap(); let difficulty = Difficulty::new(); - let mut gradual = CatchGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_2nd = CatchGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_3rd = CatchGradualDifficulty::new(difficulty.clone(), &converted); + let mut gradual = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = CatchGradualDifficulty::new(difficulty.clone(), &map).unwrap(); for i in 1.. { let Some(next_gradual) = gradual.next() else { @@ -214,8 +215,8 @@ mod tests { let expected = difficulty .clone() .passed_objects(i as u32) - .with_mode() - .calculate(&converted); + .calculate_for_mode::(&map) + .unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/catch/difficulty/mod.rs b/src/catch/difficulty/mod.rs index c5b1e1a8..e94e1bdb 100644 --- a/src/catch/difficulty/mod.rs +++ b/src/catch/difficulty/mod.rs @@ -1,16 +1,18 @@ +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::{skills::Skill, Difficulty}, catch::{ catcher::Catcher, convert::convert_objects, difficulty::object::CatchDifficultyObject, }, - model::beatmap::BeatmapAttributes, + model::{beatmap::BeatmapAttributes, mode::ConvertError}, + Beatmap, }; use self::skills::movement::Movement; use super::{ attributes::{CatchDifficultyAttributes, ObjectCountBuilder}, - convert::CatchBeatmap, object::palpable::PalpableObject, }; @@ -22,16 +24,18 @@ const DIFFICULTY_MULTIPLIER: f64 = 4.59; pub fn difficulty( difficulty: &Difficulty, - converted: &CatchBeatmap<'_>, -) -> CatchDifficultyAttributes { + map: &Beatmap, +) -> Result { + let map = map.convert_ref(GameMode::Catch, difficulty.get_mods())?; + let DifficultyValues { movement, mut attrs, - } = DifficultyValues::calculate(difficulty, converted); + } = DifficultyValues::calculate(difficulty, &map); DifficultyValues::eval(&mut attrs, movement.difficulty_value()); - attrs + Ok(attrs) } pub struct CatchDifficultySetup { @@ -40,12 +44,12 @@ pub struct CatchDifficultySetup { } impl CatchDifficultySetup { - pub fn new(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self { - let map_attrs = converted.attributes().difficulty(difficulty).build(); + pub fn new(difficulty: &Difficulty, map: &Beatmap) -> Self { + let map_attrs = map.attributes().difficulty(difficulty).build(); let attrs = CatchDifficultyAttributes { ar: map_attrs.ar, - is_convert: converted.is_convert, + is_convert: map.is_convert, ..Default::default() }; @@ -59,26 +63,21 @@ pub struct DifficultyValues { } impl DifficultyValues { - pub fn calculate(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self { + pub fn calculate(difficulty: &Difficulty, map: &Beatmap) -> Self { let take = difficulty.get_passed_objects(); let clock_rate = difficulty.get_clock_rate(); let CatchDifficultySetup { map_attrs, mut attrs, - } = CatchDifficultySetup::new(difficulty, converted); + } = CatchDifficultySetup::new(difficulty, map); let hr_offsets = difficulty.get_hardrock_offsets(); let reflection = difficulty.get_mods().reflection(); let mut count = ObjectCountBuilder::new_regular(take); - let palpable_objects = convert_objects( - converted, - &mut count, - reflection, - hr_offsets, - map_attrs.cs as f32, - ); + let palpable_objects = + convert_objects(map, &mut count, reflection, hr_offsets, map_attrs.cs as f32); let diff_objects = Self::create_difficulty_objects( &map_attrs, diff --git a/src/catch/mod.rs b/src/catch/mod.rs index 9c28af96..2e5a3102 100644 --- a/src/catch/mod.rs +++ b/src/catch/mod.rs @@ -1,14 +1,15 @@ +use rosu_map::section::general::GameMode; + use crate::{ model::{ beatmap::Beatmap, - mode::{ConvertStatus, IGameMode}, + mode::{ConvertError, IGameMode}, }, Difficulty, }; pub use self::{ attributes::{CatchDifficultyAttributes, CatchPerformanceAttributes}, - convert::CatchBeatmap, difficulty::gradual::CatchGradualDifficulty, performance::{gradual::CatchGradualPerformance, CatchPerformance}, score_state::CatchScoreState, @@ -31,6 +32,13 @@ const PLAYFIELD_WIDTH: f32 = 512.0; /// [`GameMode::Catch`]: rosu_map::section::general::GameMode::Catch pub struct Catch; +impl Catch { + pub fn convert(map: &mut Beatmap) { + debug_assert!(!map.is_convert && map.mode == GameMode::Osu); + convert::convert(map); + } +} + impl IGameMode for Catch { type DifficultyAttributes = CatchDifficultyAttributes; type Strains = CatchStrains; @@ -38,40 +46,32 @@ impl IGameMode for Catch { type GradualDifficulty = CatchGradualDifficulty; type GradualPerformance = CatchGradualPerformance; - fn check_convert(map: &Beatmap) -> ConvertStatus { - convert::check_convert(map) - } - - fn try_convert(map: &mut Beatmap) -> ConvertStatus { - convert::try_convert(map) - } - fn difficulty( difficulty: &Difficulty, - converted: &CatchBeatmap<'_>, - ) -> Self::DifficultyAttributes { - difficulty::difficulty(difficulty, converted) + map: &Beatmap, + ) -> Result { + difficulty::difficulty(difficulty, map) } - fn strains(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self::Strains { - strains::strains(difficulty, converted) + fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + strains::strains(difficulty, map) } - fn performance(map: CatchBeatmap<'_>) -> Self::Performance<'_> { + fn performance(map: &Beatmap) -> Self::Performance<'_> { CatchPerformance::new(map) } fn gradual_difficulty( difficulty: Difficulty, - map: &CatchBeatmap<'_>, - ) -> Self::GradualDifficulty { + map: &Beatmap, + ) -> Result { CatchGradualDifficulty::new(difficulty, map) } fn gradual_performance( difficulty: Difficulty, - map: &CatchBeatmap<'_>, - ) -> Self::GradualPerformance { + map: &Beatmap, + ) -> Result { CatchGradualPerformance::new(difficulty, map) } } diff --git a/src/catch/object/juice_stream.rs b/src/catch/object/juice_stream.rs index 4ead239d..68481609 100644 --- a/src/catch/object/juice_stream.rs +++ b/src/catch/object/juice_stream.rs @@ -6,12 +6,13 @@ use rosu_map::section::{ }; use crate::{ - catch::{attributes::ObjectCountBuilder, convert::CatchBeatmap}, + catch::attributes::ObjectCountBuilder, model::{ control_point::{DifficultyPoint, TimingPoint}, hit_object::Slider, }, util::get_precision_adjusted_beat_len, + Beatmap, }; pub struct JuiceStream<'a> { @@ -26,18 +27,18 @@ impl<'a> JuiceStream<'a> { effective_x: f32, start_time: f64, slider: &'a Slider, - converted: &CatchBeatmap<'_>, + map: &Beatmap, count: &mut ObjectCountBuilder, bufs: &'a mut JuiceStreamBufs, ) -> Self { - let slider_multiplier = converted.slider_multiplier; - let slider_tick_rate = converted.slider_tick_rate; + let slider_multiplier = map.slider_multiplier; + let slider_tick_rate = map.slider_tick_rate; - let beat_len = converted + let beat_len = map .timing_point_at(start_time) .map_or(TimingPoint::DEFAULT_BEAT_LEN, |point| point.beat_len); - let slider_velocity = converted + let slider_velocity = map .difficulty_point_at(start_time) .map_or(DifficultyPoint::DEFAULT_SLIDER_VELOCITY, |point| { point.slider_velocity @@ -49,7 +50,7 @@ impl<'a> JuiceStream<'a> { / get_precision_adjusted_beat_len(slider_velocity, beat_len); let scoring_dist = velocity * beat_len; - let tick_dist_multiplier = if converted.version < 8 { + let tick_dist_multiplier = if map.version < 8 { slider_velocity.recip() } else { 1.0 diff --git a/src/catch/performance/gradual.rs b/src/catch/performance/gradual.rs index 3484f0db..801c9422 100644 --- a/src/catch/performance/gradual.rs +++ b/src/catch/performance/gradual.rs @@ -1,6 +1,7 @@ use crate::{ - catch::{CatchBeatmap, CatchGradualDifficulty, CatchPerformanceAttributes, CatchScoreState}, - Difficulty, + catch::{CatchGradualDifficulty, CatchPerformanceAttributes, CatchScoreState}, + model::mode::ConvertError, + Beatmap, Difficulty, }; /// Gradually calculate the performance attributes of an osu!catch map. @@ -90,10 +91,10 @@ pub struct CatchGradualPerformance { impl CatchGradualPerformance { /// Create a new gradual performance calculator for osu!catch maps. - pub fn new(difficulty: Difficulty, converted: &CatchBeatmap<'_>) -> Self { - let difficulty = CatchGradualDifficulty::new(difficulty, converted); + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { + let difficulty = CatchGradualDifficulty::new(difficulty, map)?; - Self { difficulty } + Ok(Self { difficulty }) } /// Process the next hit object and calculate the performance attributes @@ -116,6 +117,7 @@ impl CatchGradualPerformance { /// /// Note that the count is zero-indexed, so `n=0` will process 1 object, /// `n=1` will process 2, and so on. + #[allow(clippy::missing_panics_doc)] pub fn nth(&mut self, state: CatchScoreState, n: usize) -> Option { let performance = self .difficulty @@ -124,7 +126,8 @@ impl CatchGradualPerformance { .state(state) .difficulty(self.difficulty.difficulty.clone()) .passed_objects(self.difficulty.idx as u32) - .calculate(); + .calculate() + .expect("no conversion required"); Some(performance) } @@ -144,15 +147,13 @@ mod tests { #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/2118524.osu") - .unwrap() - .unchecked_into_converted(); + let map = Beatmap::from_path("./resources/2118524.osu").unwrap(); let difficulty = Difficulty::new().mods(88); // HDHRDT - let mut gradual = CatchGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_2nd = CatchGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_3rd = CatchGradualPerformance::new(difficulty.clone(), &converted); + let mut gradual = CatchGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = CatchGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = CatchGradualPerformance::new(difficulty.clone(), &map).unwrap(); let mut state = CatchScoreState::default(); @@ -176,12 +177,12 @@ mod tests { assert_eq!(next_gradual, next_gradual_3rd); } - let regular_calc = CatchPerformance::new(converted.as_owned()) + let regular_calc = CatchPerformance::new(&map) .difficulty(difficulty.clone()) .passed_objects(i as u32) .state(state.clone()); - let expected = regular_calc.calculate(); + let expected = regular_calc.calculate().unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/catch/performance/mod.rs b/src/catch/performance/mod.rs index ce12f016..f874a66a 100644 --- a/src/catch/performance/mod.rs +++ b/src/catch/performance/mod.rs @@ -1,8 +1,10 @@ use std::cmp::{self, Ordering}; +use rosu_map::section::general::GameMode; + use crate::{ any::{Difficulty, IntoModePerformance, IntoPerformance}, - model::mods::GameMods, + model::{mode::ConvertError, mods::GameMods}, osu::OsuPerformance, util::map_or_attrs::MapOrAttrs, Performance, @@ -37,7 +39,7 @@ impl<'map> CatchPerformance<'map> { /// The argument `map_or_attrs` must be either /// - previously calculated attributes ([`CatchDifficultyAttributes`] /// or [`CatchPerformanceAttributes`]) - /// - a beatmap ([`CatchBeatmap<'map>`]) + /// - a [`Beatmap`] (by reference or value) /// /// If a map is given, difficulty attributes will need to be calculated /// internally which is a costly operation. Hence, passing attributes @@ -47,20 +49,19 @@ impl<'map> CatchPerformance<'map> { /// have been calculated for the same map and [`Difficulty`] settings. /// Otherwise, the final attributes will be incorrect. /// - /// [`CatchBeatmap<'map>`]: crate::catch::CatchBeatmap + /// [`Beatmap`]: crate::model::beatmap::Beatmap pub fn new(map_or_attrs: impl IntoModePerformance<'map, Catch>) -> Self { map_or_attrs.into_performance() } /// Try to create a new performance calculator for osu!catch maps. /// - /// Returns `None` if `map_or_attrs` does not belong to osu!catch e.g. - /// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`] - /// of a different mode. + /// Returns `None` if `map_or_attrs` does not belong to osu!catch i.e. + /// a [`DifficultyAttributes`] or [`PerformanceAttributes`] of a different + /// mode. /// /// See [`CatchPerformance::new`] for more information. /// - /// [`Converted`]: crate::model::beatmap::Converted /// [`DifficultyAttributes`]: crate::any::DifficultyAttributes /// [`PerformanceAttributes`]: crate::any::PerformanceAttributes pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option { @@ -262,10 +263,10 @@ impl<'map> CatchPerformance<'map> { /// Create the [`CatchScoreState`] that will be used for performance calculation. #[allow(clippy::too_many_lines)] - pub fn generate_state(&mut self) -> CatchScoreState { + pub fn generate_state(&mut self) -> Result { let attrs = match self.map_or_attrs { MapOrAttrs::Map(ref map) => { - let attrs = self.difficulty.with_mode().calculate(map); + let attrs = self.difficulty.calculate_for_mode::(map)?; self.map_or_attrs.insert_attrs(attrs) } @@ -411,16 +412,16 @@ impl<'map> CatchPerformance<'map> { self.tiny_droplet_misses = Some(best_state.tiny_droplet_misses); self.misses = Some(best_state.misses); - best_state + Ok(best_state) } /// Calculate all performance related values, including pp and stars. - pub fn calculate(mut self) -> CatchPerformanceAttributes { - let state = self.generate_state(); + pub fn calculate(mut self) -> Result { + let state = self.generate_state()?; let attrs = match self.map_or_attrs { - MapOrAttrs::Map(ref map) => self.difficulty.with_mode().calculate(map), MapOrAttrs::Attrs(attrs) => attrs, + MapOrAttrs::Map(ref map) => self.difficulty.calculate_for_mode::(map)?, }; let inner = CatchPerformanceInner { @@ -429,7 +430,7 @@ impl<'map> CatchPerformance<'map> { state, }; - inner.calculate() + Ok(inner.calculate()) } pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Catch>) -> Self { @@ -456,14 +457,12 @@ impl<'map> TryFrom> for CatchPerformance<'map> { /// if it was constructed through attributes or /// [`OsuPerformance::generate_state`] was called. fn try_from(mut osu: OsuPerformance<'map>) -> Result { - let MapOrAttrs::Map(converted) = osu.map_or_attrs else { - return Err(osu); - }; + let mods = osu.difficulty.get_mods(); - let map = match converted.try_convert() { + let map = match OsuPerformance::try_convert_map(osu.map_or_attrs, GameMode::Catch, mods) { Ok(map) => map, - Err(map) => { - osu.map_or_attrs = MapOrAttrs::Map(map); + Err(map_or_attrs) => { + osu.map_or_attrs = map_or_attrs; return Err(osu); } @@ -607,7 +606,7 @@ mod test { use crate::{ any::{DifficultyAttributes, PerformanceAttributes}, - osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes}, + osu::{OsuDifficultyAttributes, OsuPerformanceAttributes}, Beatmap, }; @@ -626,8 +625,8 @@ mod test { fn attrs() -> CatchDifficultyAttributes { ATTRS .get_or_init(|| { - let converted = beatmap().unchecked_into_converted::(); - let attrs = Difficulty::new().with_mode().calculate(&converted); + let map = beatmap(); + let attrs = Difficulty::new().calculate_for_mode::(&map).unwrap(); assert_eq!(N_FRUITS, attrs.n_fruits); assert_eq!(N_DROPLETS, attrs.n_droplets); @@ -782,8 +781,8 @@ mod test { state = state.misses(misses); } - let first = state.generate_state(); - let state = state.generate_state(); + let first = state.generate_state().unwrap(); + let state = state.generate_state().unwrap(); assert_eq!(first, state); let expected = brute_force_best( @@ -807,7 +806,8 @@ mod test { .tiny_droplets(N_TINY_DROPLETS - 50) .tiny_droplet_misses(20) .misses(2) - .generate_state(); + .generate_state() + .unwrap(); let expected = CatchScoreState { max_combo: N_FRUITS + N_DROPLETS - 2, @@ -824,12 +824,11 @@ mod test { #[test] fn create() { let mut map = beatmap(); - let converted = map.unchecked_as_converted(); let _ = CatchPerformance::new(CatchDifficultyAttributes::default()); let _ = CatchPerformance::new(CatchPerformanceAttributes::default()); - let _ = CatchPerformance::new(&converted); - let _ = CatchPerformance::new(converted.as_owned()); + let _ = CatchPerformance::new(&map); + let _ = CatchPerformance::new(map.clone()); let _ = CatchPerformance::try_new(CatchDifficultyAttributes::default()).unwrap(); let _ = CatchPerformance::try_new(CatchPerformanceAttributes::default()).unwrap(); @@ -841,19 +840,20 @@ mod test { CatchPerformanceAttributes::default(), )) .unwrap(); - let _ = CatchPerformance::try_new(&converted).unwrap(); - let _ = CatchPerformance::try_new(converted.as_owned()).unwrap(); + let _ = CatchPerformance::try_new(&map).unwrap(); + let _ = CatchPerformance::try_new(map.clone()).unwrap(); let _ = CatchPerformance::from(CatchDifficultyAttributes::default()); let _ = CatchPerformance::from(CatchPerformanceAttributes::default()); - let _ = CatchPerformance::from(&converted); - let _ = CatchPerformance::from(converted); + let _ = CatchPerformance::from(&map); + let _ = CatchPerformance::from(map.clone()); let _ = CatchDifficultyAttributes::default().performance(); let _ = CatchPerformanceAttributes::default().performance(); - map.mode = GameMode::Osu; - let converted = map.unchecked_as_converted::(); + assert!(map + .convert_mut(GameMode::Osu, &GameMods::default()) + .is_err()); assert!(CatchPerformance::try_new(OsuDifficultyAttributes::default()).is_none()); assert!(CatchPerformance::try_new(OsuPerformanceAttributes::default()).is_none()); @@ -865,7 +865,5 @@ mod test { OsuPerformanceAttributes::default() )) .is_none()); - assert!(CatchPerformance::try_new(&converted).is_none()); - assert!(CatchPerformance::try_new(converted).is_none()); } } diff --git a/src/catch/strains.rs b/src/catch/strains.rs index ff133417..70280aaf 100644 --- a/src/catch/strains.rs +++ b/src/catch/strains.rs @@ -1,6 +1,8 @@ -use crate::{any::Difficulty, catch::difficulty::DifficultyValues}; +use rosu_map::section::general::GameMode; -use super::convert::CatchBeatmap; +use crate::{ + any::Difficulty, catch::difficulty::DifficultyValues, model::mode::ConvertError, Beatmap, +}; /// The result of calculating the strains on a osu!catch map. /// @@ -16,10 +18,11 @@ impl CatchStrains { pub const SECTION_LEN: f64 = 750.0; } -pub fn strains(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> CatchStrains { - let DifficultyValues { movement, .. } = DifficultyValues::calculate(difficulty, converted); +pub fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Catch, difficulty.get_mods())?; + let DifficultyValues { movement, .. } = DifficultyValues::calculate(difficulty, &map); - CatchStrains { + Ok(CatchStrains { movement: movement.get_curr_strain_peaks().into_vec(), - } + }) } diff --git a/src/lib.rs b/src/lib.rs index b2deed0a..b54337c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,10 +159,7 @@ #[doc(inline)] pub use self::{ any::{Difficulty, GradualDifficulty, GradualPerformance, Performance}, - model::{ - beatmap::{Beatmap, Converted}, - mods::GameMods, - }, + model::{beatmap::Beatmap, mods::GameMods}, }; /// Types for calculations of any mode. diff --git a/src/mania/convert/mod.rs b/src/mania/convert/mod.rs index ad2bf5bb..ca67073b 100644 --- a/src/mania/convert/mod.rs +++ b/src/mania/convert/mod.rs @@ -2,11 +2,11 @@ use rosu_map::{section::general::GameMode, util::Pos}; use crate::{ model::{ - beatmap::{Beatmap, Converted}, + beatmap::Beatmap, hit_object::{HitObjectKind, HoldNote, Spinner}, - mode::ConvertStatus, }, util::{limited_queue::LimitedQueue, random::Random, sort}, + GameMods, }; use self::{ @@ -18,38 +18,13 @@ use self::{ pattern_type::PatternType, }; -use super::Mania; - mod pattern; mod pattern_generator; mod pattern_type; -/// A [`Beatmap`] for [`Mania`] calculations. -pub type ManiaBeatmap<'a> = Converted<'a, Mania>; - const MAX_NOTES_FOR_DENSITY: usize = 7; -pub const fn check_convert(map: &Beatmap) -> ConvertStatus { - match map.mode { - GameMode::Osu => ConvertStatus::Conversion, - GameMode::Mania => ConvertStatus::Noop, - GameMode::Taiko | GameMode::Catch => ConvertStatus::Incompatible, - } -} - -pub fn try_convert(map: &mut Beatmap) -> ConvertStatus { - match map.mode { - GameMode::Osu => { - convert(map); - - ConvertStatus::Conversion - } - GameMode::Mania => ConvertStatus::Noop, - GameMode::Taiko | GameMode::Catch => ConvertStatus::Incompatible, - } -} - -fn convert(map: &mut Beatmap) { +pub fn convert(map: &mut Beatmap, mods: &GameMods) { let seed = (map.hp + map.cs).round_ties_even() as i32 * 20 + (map.od * 41.2) as i32 + map.ar.round_ties_even() as i32; @@ -226,11 +201,8 @@ mod tests { #[test] fn convert_mania() { - let converted = Beatmap::from_path("./resources/2785319.osu") - .unwrap() - .unchecked_into_converted::(); - - let map = &converted; + let map = Beatmap::from_path("./resources/2785319.osu").unwrap(); + let map = map.convert(GameMode::Mania, &GameMods::default()).unwrap(); assert!(map.is_convert); assert_eq!(map.mode, GameMode::Mania); diff --git a/src/mania/difficulty/gradual.rs b/src/mania/difficulty/gradual.rs index bc2508d8..23ee56dc 100644 --- a/src/mania/difficulty/gradual.rs +++ b/src/mania/difficulty/gradual.rs @@ -1,10 +1,12 @@ use std::cmp; +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::skills::Skill, - mania::{object::ObjectParams, ManiaBeatmap}, - model::{beatmap::HitWindows, hit_object::HitObject}, - Difficulty, + mania::object::ObjectParams, + model::{beatmap::HitWindows, hit_object::HitObject, mode::ConvertError}, + Beatmap, Difficulty, }; use super::{ @@ -65,18 +67,20 @@ struct NoteState { impl ManiaGradualDifficulty { /// Create a new difficulty attributes iterator for osu!mania maps. - pub fn new(difficulty: Difficulty, converted: &ManiaBeatmap<'_>) -> Self { + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Mania, difficulty.get_mods())?; + let take = difficulty.get_passed_objects(); - let total_columns = converted.cs.round_ties_even().max(1.0); + let total_columns = map.cs.round_ties_even().max(1.0); let clock_rate = difficulty.get_clock_rate(); - let mut params = ObjectParams::new(converted); + let mut params = ObjectParams::new(&map); let HitWindows { od_great: hit_window, .. - } = converted.attributes().difficulty(&difficulty).hit_windows(); + } = map.attributes().difficulty(&difficulty).hit_windows(); - let mania_objects = converted + let mania_objects = map .hit_objects .iter() .map(|h| ManiaObject::new(h, total_columns, &mut params)) @@ -88,13 +92,10 @@ impl ManiaGradualDifficulty { let mut note_state = NoteState::default(); - let objects_is_circle: Box<[_]> = converted - .hit_objects - .iter() - .map(HitObject::is_circle) - .collect(); + let objects_is_circle: Box<[_]> = + map.hit_objects.iter().map(HitObject::is_circle).collect(); - if let Some(h) = converted.hit_objects.first() { + if let Some(h) = map.hit_objects.first() { let hit_object = ManiaObject::new(h, total_columns, &mut params); increment_combo_raw( @@ -105,16 +106,16 @@ impl ManiaGradualDifficulty { ); } - Self { + Ok(Self { idx: 0, difficulty, objects_is_circle, - is_convert: converted.is_convert, + is_convert: map.is_convert, strain, diff_objects, hit_window, note_state, - } + }) } } @@ -224,28 +225,22 @@ mod tests { #[test] fn empty() { - let converted = Beatmap::from_bytes(&[]) - .unwrap() - .unchecked_into_converted::(); - - let mut gradual = ManiaGradualDifficulty::new(Difficulty::new(), &converted); - + let map = Beatmap::from_bytes(&[]).unwrap(); + let mut gradual = ManiaGradualDifficulty::new(Difficulty::new(), &map).unwrap(); assert!(gradual.next().is_none()); } #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/1638954.osu") - .unwrap() - .unchecked_into_converted::(); + let map = Beatmap::from_path("./resources/1638954.osu").unwrap(); let difficulty = Difficulty::new(); - let mut gradual = ManiaGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_2nd = ManiaGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_3rd = ManiaGradualDifficulty::new(difficulty.clone(), &converted); + let mut gradual = ManiaGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = ManiaGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = ManiaGradualDifficulty::new(difficulty.clone(), &map).unwrap(); - let hit_objects_len = converted.hit_objects.len(); + let hit_objects_len = map.hit_objects.len(); for i in 1.. { let Some(next_gradual) = gradual.next() else { @@ -268,8 +263,8 @@ mod tests { let expected = difficulty .clone() .passed_objects(i as u32) - .with_mode() - .calculate(&converted); + .calculate_for_mode::(&map) + .unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/mania/difficulty/mod.rs b/src/mania/difficulty/mod.rs index 2848962e..df500465 100644 --- a/src/mania/difficulty/mod.rs +++ b/src/mania/difficulty/mod.rs @@ -1,14 +1,18 @@ use std::cmp; +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::{skills::Skill, Difficulty}, mania::{ difficulty::{object::ManiaDifficultyObject, skills::strain::Strain}, object::{ManiaObject, ObjectParams}, }, + model::mode::ConvertError, + Beatmap, }; -use super::{attributes::ManiaDifficultyAttributes, convert::ManiaBeatmap}; +use super::attributes::ManiaDifficultyAttributes; pub mod gradual; mod object; @@ -18,26 +22,28 @@ const DIFFICULTY_MULTIPLIER: f64 = 0.018; pub fn difficulty( difficulty: &Difficulty, - converted: &ManiaBeatmap<'_>, -) -> ManiaDifficultyAttributes { - let n_objects = cmp::min(difficulty.get_passed_objects(), converted.hit_objects.len()) as u32; + map: &Beatmap, +) -> Result { + let map = map.convert_ref(GameMode::Mania, difficulty.get_mods())?; + + let n_objects = cmp::min(difficulty.get_passed_objects(), map.hit_objects.len()) as u32; - let values = DifficultyValues::calculate(difficulty, converted); + let values = DifficultyValues::calculate(difficulty, &map); - let hit_window = converted + let hit_window = map .attributes() .difficulty(difficulty) .hit_windows() .od_great; - ManiaDifficultyAttributes { + Ok(ManiaDifficultyAttributes { stars: values.strain.difficulty_value() * DIFFICULTY_MULTIPLIER, hit_window, max_combo: values.max_combo, n_objects, n_hold_notes: values.n_hold_notes, - is_convert: converted.is_convert, - } + is_convert: map.is_convert, + }) } pub struct DifficultyValues { @@ -47,13 +53,13 @@ pub struct DifficultyValues { } impl DifficultyValues { - pub fn calculate(difficulty: &Difficulty, converted: &ManiaBeatmap<'_>) -> Self { + pub fn calculate(difficulty: &Difficulty, map: &Beatmap) -> Self { let take = difficulty.get_passed_objects(); - let total_columns = converted.cs.round_ties_even().max(1.0); + let total_columns = map.cs.round_ties_even().max(1.0); let clock_rate = difficulty.get_clock_rate(); - let mut params = ObjectParams::new(converted); + let mut params = ObjectParams::new(map); - let mania_objects = converted + let mania_objects = map .hit_objects .iter() .map(|h| ManiaObject::new(h, total_columns, &mut params)) diff --git a/src/mania/mod.rs b/src/mania/mod.rs index f6691c6a..53736e93 100644 --- a/src/mania/mod.rs +++ b/src/mania/mod.rs @@ -1,14 +1,15 @@ +use rosu_map::section::general::GameMode; + use crate::{ model::{ beatmap::Beatmap, - mode::{ConvertStatus, IGameMode}, + mode::{ConvertError, IGameMode}, }, - Difficulty, + Difficulty, GameMods, }; pub use self::{ attributes::{ManiaDifficultyAttributes, ManiaPerformanceAttributes}, - convert::ManiaBeatmap, difficulty::gradual::ManiaGradualDifficulty, performance::{gradual::ManiaGradualPerformance, ManiaPerformance}, score_state::ManiaScoreState, @@ -28,6 +29,13 @@ mod strains; /// [`GameMode::Mania`]: rosu_map::section::general::GameMode::Mania pub struct Mania; +impl Mania { + pub(crate) fn convert(map: &mut Beatmap, mods: &GameMods) { + debug_assert!(!map.is_convert && map.mode == GameMode::Osu); + convert::convert(map, mods); + } +} + impl IGameMode for Mania { type DifficultyAttributes = ManiaDifficultyAttributes; type Strains = ManiaStrains; @@ -35,40 +43,32 @@ impl IGameMode for Mania { type GradualDifficulty = ManiaGradualDifficulty; type GradualPerformance = ManiaGradualPerformance; - fn check_convert(map: &Beatmap) -> ConvertStatus { - convert::check_convert(map) - } - - fn try_convert(map: &mut Beatmap) -> ConvertStatus { - convert::try_convert(map) - } - fn difficulty( difficulty: &Difficulty, - converted: &ManiaBeatmap<'_>, - ) -> Self::DifficultyAttributes { - difficulty::difficulty(difficulty, converted) + map: &Beatmap, + ) -> Result { + difficulty::difficulty(difficulty, map) } - fn strains(difficulty: &Difficulty, converted: &ManiaBeatmap<'_>) -> Self::Strains { - strains::strains(difficulty, converted) + fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + strains::strains(difficulty, map) } - fn performance(map: ManiaBeatmap<'_>) -> Self::Performance<'_> { + fn performance(map: &Beatmap) -> Self::Performance<'_> { ManiaPerformance::new(map) } fn gradual_difficulty( difficulty: Difficulty, - map: &ManiaBeatmap<'_>, - ) -> Self::GradualDifficulty { + map: &Beatmap, + ) -> Result { ManiaGradualDifficulty::new(difficulty, map) } fn gradual_performance( difficulty: Difficulty, - map: &ManiaBeatmap<'_>, - ) -> Self::GradualPerformance { + map: &Beatmap, + ) -> Result { ManiaGradualPerformance::new(difficulty, map) } } diff --git a/src/mania/performance/gradual.rs b/src/mania/performance/gradual.rs index ce23f38a..b797da49 100644 --- a/src/mania/performance/gradual.rs +++ b/src/mania/performance/gradual.rs @@ -1,7 +1,4 @@ -use crate::{ - mania::{ManiaBeatmap, ManiaGradualDifficulty}, - Difficulty, -}; +use crate::{mania::ManiaGradualDifficulty, model::mode::ConvertError, Beatmap, Difficulty}; use super::{ManiaPerformanceAttributes, ManiaScoreState}; @@ -75,10 +72,10 @@ pub struct ManiaGradualPerformance { impl ManiaGradualPerformance { /// Create a new gradual performance calculator for osu!mania maps. - pub fn new(difficulty: Difficulty, converted: &ManiaBeatmap<'_>) -> Self { - let difficulty = ManiaGradualDifficulty::new(difficulty, converted); + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { + let difficulty = ManiaGradualDifficulty::new(difficulty, map)?; - Self { difficulty } + Ok(Self { difficulty }) } /// Process the next hit object and calculate the performance attributes @@ -98,6 +95,7 @@ impl ManiaGradualPerformance { /// /// Note that the count is zero-indexed, so `n=0` will process 1 object, /// `n=1` will process 2, and so on. + #[allow(clippy::missing_panics_doc)] pub fn nth(&mut self, state: ManiaScoreState, n: usize) -> Option { let performance = self .difficulty @@ -106,7 +104,8 @@ impl ManiaGradualPerformance { .state(state) .difficulty(self.difficulty.difficulty.clone()) .passed_objects(self.difficulty.idx as u32) - .calculate(); + .calculate() + .expect("no conversion required"); Some(performance) } @@ -120,34 +119,29 @@ impl ManiaGradualPerformance { #[cfg(test)] mod tests { - use crate::{ - mania::{Mania, ManiaPerformance}, - Beatmap, - }; + use crate::{mania::ManiaPerformance, Beatmap}; use super::*; #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/1638954.osu") - .unwrap() - .unchecked_into_converted::(); + let map = Beatmap::from_path("./resources/1638954.osu").unwrap(); let difficulty = Difficulty::new().mods(88); // HDHRDT - let mut gradual = ManiaGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_2nd = ManiaGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_3rd = ManiaGradualPerformance::new(difficulty.clone(), &converted); + let mut gradual = ManiaGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = ManiaGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = ManiaGradualPerformance::new(difficulty.clone(), &map).unwrap(); let mut state = ManiaScoreState::default(); - let hit_objects_len = converted.hit_objects.len(); + let hit_objects_len = map.hit_objects.len(); for i in 1.. { state.misses += 1; // Hold notes award two hitresults in lazer - if let Some(h) = converted.hit_objects.get(i - 1) { + if let Some(h) = map.hit_objects.get(i - 1) { if !h.is_circle() { state.n320 += 1; } @@ -170,15 +164,15 @@ mod tests { assert_eq!(next_gradual, next_gradual_3rd); } - let mut regular_calc = ManiaPerformance::new(converted.as_owned()) + let mut regular_calc = ManiaPerformance::new(&map) .difficulty(difficulty.clone()) .passed_objects(i as u32) .state(state.clone()); - let regular_state = regular_calc.generate_state(); + let regular_state = regular_calc.generate_state().unwrap(); assert_eq!(state, regular_state); - let expected = regular_calc.calculate(); + let expected = regular_calc.calculate().unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/mania/performance/mod.rs b/src/mania/performance/mod.rs index bd11897f..dba7f48e 100644 --- a/src/mania/performance/mod.rs +++ b/src/mania/performance/mod.rs @@ -1,8 +1,10 @@ use std::cmp; +use rosu_map::section::general::GameMode; + use crate::{ any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance}, - model::mods::GameMods, + model::{mode::ConvertError, mods::GameMods}, osu::OsuPerformance, util::map_or_attrs::MapOrAttrs, Performance, @@ -38,7 +40,7 @@ impl<'map> ManiaPerformance<'map> { /// The argument `map_or_attrs` must be either /// - previously calculated attributes ([`ManiaDifficultyAttributes`] /// or [`ManiaPerformanceAttributes`]) - /// - a beatmap ([`ManiaBeatmap<'map>`]) + /// - a [`Beatmap`] (by reference or value) /// /// If a map is given, difficulty attributes will need to be calculated /// internally which is a costly operation. Hence, passing attributes @@ -48,20 +50,19 @@ impl<'map> ManiaPerformance<'map> { /// have been calculated for the same map and [`Difficulty`] settings. /// Otherwise, the final attributes will be incorrect. /// - /// [`ManiaBeatmap<'map>`]: crate::mania::ManiaBeatmap + /// [`Beatmap`]: crate::model::beatmap::Beatmap pub fn new(map_or_attrs: impl IntoModePerformance<'map, Mania>) -> Self { map_or_attrs.into_performance() } /// Try to create a new performance calculator for osu!mania maps. /// - /// Returns `None` if `map_or_attrs` does not belong to osu!mania e.g. - /// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`] - /// of a different mode. + /// Returns `None` if `map_or_attrs` does not belong to osu!mania i.e. + /// a [`DifficultyAttributes`] or [`PerformanceAttributes`] of a different + /// mode. /// /// See [`ManiaPerformance::new`] for more information. /// - /// [`Converted`]: crate::model::beatmap::Converted /// [`DifficultyAttributes`]: crate::any::DifficultyAttributes /// [`PerformanceAttributes`]: crate::any::PerformanceAttributes pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option { @@ -248,10 +249,10 @@ impl<'map> ManiaPerformance<'map> { /// Create the [`ManiaScoreState`] that will be used for performance calculation. #[allow(clippy::too_many_lines, clippy::similar_names)] - pub fn generate_state(&mut self) -> ManiaScoreState { + pub fn generate_state(&mut self) -> Result { let attrs = match self.map_or_attrs { MapOrAttrs::Map(ref map) => { - let attrs = self.difficulty.with_mode().calculate(map); + let attrs = self.difficulty.calculate_for_mode::(map)?; self.map_or_attrs.insert_attrs(attrs) } @@ -774,23 +775,23 @@ impl<'map> ManiaPerformance<'map> { self.n50 = Some(n50); self.misses = Some(misses); - ManiaScoreState { + Ok(ManiaScoreState { n320, n300, n200, n100, n50, misses, - } + }) } /// Calculate all performance related values, including pp and stars. - pub fn calculate(mut self) -> ManiaPerformanceAttributes { - let state = self.generate_state(); + pub fn calculate(mut self) -> Result { + let state = self.generate_state()?; let attrs = match self.map_or_attrs { - MapOrAttrs::Map(ref map) => self.difficulty.with_mode().calculate(map), MapOrAttrs::Attrs(attrs) => attrs, + MapOrAttrs::Map(ref map) => self.difficulty.calculate_for_mode::(map)?, }; let inner = ManiaPerformanceInner { @@ -799,7 +800,7 @@ impl<'map> ManiaPerformance<'map> { state, }; - inner.calculate() + Ok(inner.calculate()) } pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Mania>) -> Self { @@ -827,14 +828,12 @@ impl<'map> TryFrom> for ManiaPerformance<'map> { /// if it was constructed through attributes or /// [`OsuPerformance::generate_state`] was called. fn try_from(mut osu: OsuPerformance<'map>) -> Result { - let MapOrAttrs::Map(converted) = osu.map_or_attrs else { - return Err(osu); - }; + let mods = osu.difficulty.get_mods(); - let map = match converted.try_convert() { + let map = match OsuPerformance::try_convert_map(osu.map_or_attrs, GameMode::Mania, mods) { Ok(map) => map, - Err(map) => { - osu.map_or_attrs = MapOrAttrs::Map(map); + Err(map_or_attrs) => { + osu.map_or_attrs = map_or_attrs; return Err(osu); } @@ -959,7 +958,7 @@ mod tests { use crate::{ any::{DifficultyAttributes, PerformanceAttributes}, - osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes}, + osu::{OsuDifficultyAttributes, OsuPerformanceAttributes}, Beatmap, }; @@ -977,17 +976,13 @@ mod tests { fn attrs() -> ManiaDifficultyAttributes { ATTRS .get_or_init(|| { - let converted = beatmap().unchecked_into_converted::(); - let attrs = Difficulty::new().with_mode().calculate(&converted); + let map = beatmap(); + let attrs = Difficulty::new().calculate_for_mode::(&map).unwrap(); - assert_eq!(N_OBJECTS, converted.hit_objects.len() as u32); + assert_eq!(N_OBJECTS, map.hit_objects.len() as u32); assert_eq!( N_HOLD_NOTES, - converted - .hit_objects - .iter() - .filter(|h| !h.is_circle()) - .count() as u32 + map.hit_objects.iter().filter(|h| !h.is_circle()).count() as u32 ); attrs @@ -1277,8 +1272,8 @@ mod tests { state = state.misses(misses); } - let first = state.generate_state(); - let state = state.generate_state(); + let first = state.generate_state().unwrap(); + let state = state.generate_state().unwrap(); assert_eq!(first, state); let expected = brute_force_best( @@ -1304,7 +1299,8 @@ mod tests { .n320(500) .misses(2) .hitresult_priority(HitResultPriority::BestCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = ManiaScoreState { n320: 500, @@ -1326,7 +1322,8 @@ mod tests { .n50(50) .misses(2) .hitresult_priority(HitResultPriority::WorstCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = ManiaScoreState { n320: 0, @@ -1343,12 +1340,11 @@ mod tests { #[test] fn create() { let mut map = beatmap(); - let converted = map.unchecked_as_converted(); let _ = ManiaPerformance::new(ManiaDifficultyAttributes::default()); let _ = ManiaPerformance::new(ManiaPerformanceAttributes::default()); - let _ = ManiaPerformance::new(&converted); - let _ = ManiaPerformance::new(converted.as_owned()); + let _ = ManiaPerformance::new(&map); + let _ = ManiaPerformance::new(map.clone()); let _ = ManiaPerformance::try_new(ManiaDifficultyAttributes::default()).unwrap(); let _ = ManiaPerformance::try_new(ManiaPerformanceAttributes::default()).unwrap(); @@ -1360,19 +1356,20 @@ mod tests { ManiaPerformanceAttributes::default(), )) .unwrap(); - let _ = ManiaPerformance::try_new(&converted).unwrap(); - let _ = ManiaPerformance::try_new(converted.as_owned()).unwrap(); + let _ = ManiaPerformance::try_new(&map).unwrap(); + let _ = ManiaPerformance::try_new(map.clone()).unwrap(); let _ = ManiaPerformance::from(ManiaDifficultyAttributes::default()); let _ = ManiaPerformance::from(ManiaPerformanceAttributes::default()); - let _ = ManiaPerformance::from(&converted); - let _ = ManiaPerformance::from(converted); + let _ = ManiaPerformance::from(&map); + let _ = ManiaPerformance::from(map.clone()); let _ = ManiaDifficultyAttributes::default().performance(); let _ = ManiaPerformanceAttributes::default().performance(); - map.mode = GameMode::Osu; - let converted = map.unchecked_as_converted::(); + assert!(map + .convert_mut(GameMode::Osu, &GameMods::default()) + .is_err()); assert!(ManiaPerformance::try_new(OsuDifficultyAttributes::default()).is_none()); assert!(ManiaPerformance::try_new(OsuPerformanceAttributes::default()).is_none()); @@ -1384,7 +1381,5 @@ mod tests { OsuPerformanceAttributes::default() )) .is_none()); - assert!(ManiaPerformance::try_new(&converted).is_none()); - assert!(ManiaPerformance::try_new(converted).is_none()); } } diff --git a/src/mania/strains.rs b/src/mania/strains.rs index ad9b5135..0727434d 100644 --- a/src/mania/strains.rs +++ b/src/mania/strains.rs @@ -1,6 +1,8 @@ -use crate::{any::Difficulty, mania::difficulty::DifficultyValues}; +use rosu_map::section::general::GameMode; -use super::convert::ManiaBeatmap; +use crate::{ + any::Difficulty, mania::difficulty::DifficultyValues, model::mode::ConvertError, Beatmap, +}; /// The result of calculating the strains on a osu!mania map. /// @@ -16,10 +18,11 @@ impl ManiaStrains { pub const SECTION_LEN: f64 = 400.0; } -pub fn strains(difficulty: &Difficulty, converted: &ManiaBeatmap<'_>) -> ManiaStrains { - let values = DifficultyValues::calculate(difficulty, converted); +pub fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Mania, difficulty.get_mods())?; + let values = DifficultyValues::calculate(difficulty, &map); - ManiaStrains { + Ok(ManiaStrains { strains: values.strain.get_curr_strain_peaks().into_vec(), - } + }) } diff --git a/src/model/beatmap/attributes.rs b/src/model/beatmap/attributes.rs index 8b75a170..1b7797d9 100644 --- a/src/model/beatmap/attributes.rs +++ b/src/model/beatmap/attributes.rs @@ -2,7 +2,7 @@ use rosu_map::section::general::GameMode; use crate::{any::difficulty::ModsDependent, model::mods::GameMods, Difficulty}; -use super::{converted::Converted, Beatmap}; +use super::Beatmap; /// Summary struct for a [`Beatmap`]'s attributes. #[derive(Clone, Debug, PartialEq)] @@ -371,12 +371,6 @@ impl From<&Beatmap> for BeatmapAttributesBuilder { } } -impl From<&Converted<'_, M>> for BeatmapAttributesBuilder { - fn from(converted: &Converted<'_, M>) -> Self { - Self::new().map(converted) - } -} - // False positive? Value looks consumed to me... #[allow(clippy::needless_pass_by_value)] fn difficulty_range(difficulty: f64, windows: GameModeHitWindows) -> f64 { diff --git a/src/model/beatmap/converted.rs b/src/model/beatmap/converted.rs deleted file mode 100644 index 9186bd68..00000000 --- a/src/model/beatmap/converted.rs +++ /dev/null @@ -1,211 +0,0 @@ -use std::{ - borrow::Cow, - fmt::{Debug, Formatter, Result as FmtResult}, - marker::PhantomData, - ops::Deref, -}; - -use crate::{ - model::mode::{ConvertStatus, IGameMode}, - util::generic_fmt::GenericFormatter, - Difficulty, -}; - -use super::Beatmap; - -const INCOMPATIBLE_MODES: &str = "the gamemodes were incompatible"; - -/// A [`Beatmap`] that is attached to a mode. -/// -/// # Incompatibility -/// -/// The following conversions are compatible: -/// - `Osu` → `Osu` -/// - `Taiko` → `Taiko` -/// - `Catch` → `Catch` -/// - `Mania` → `Mania` -/// - `Osu` → `Taiko` -/// - `Osu` → `Catch` -/// - `Osu` → `Mania` -/// -/// All other conversions are incompatible. -pub struct Converted<'a, M> { - map: Cow<'a, Beatmap>, - mode: PhantomData, -} - -impl<'a, M> Converted<'a, M> { - /// Initialize a [`Converted`] beatmap by promising the given map's mode - /// matches the generic type `M`. - pub(crate) const fn new(map: Cow<'a, Beatmap>) -> Self { - Self { - map, - mode: PhantomData, - } - } - - /// Returns the internal [`Beatmap`]. - pub fn into_inner(self) -> Cow<'a, Beatmap> { - self.map - } - - /// Borrow the contained [`Beatmap`] to cheaply create a new owned - /// [`Converted`]. - /// - /// This is the same as `.clone()` except cheap - but its lifetime might be - /// shorter. - #[must_use] - pub fn as_owned(&'a self) -> Self { - Self::new(Cow::Borrowed(self.map.as_ref())) - } -} - -impl Converted<'_, M> { - /// Attempt to convert a [`Beatmap`] to the specified mode. - /// - /// If the conversion is incompatible the [`Beatmap`] will be returned - /// unchanged as `Err`. - #[allow(clippy::result_large_err)] - pub fn try_from_owned(mut map: Beatmap) -> Result { - match M::try_convert(&mut map) { - ConvertStatus::Noop => Ok(Self::new(Cow::Owned(map))), - ConvertStatus::Conversion => Ok(Self::new(Cow::Owned(map))), - ConvertStatus::Incompatible => Err(map), - } - } - - /// Convert a [`Beatmap`] to the specified mode. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. - pub fn unchecked_from_owned(map: Beatmap) -> Self { - Self::try_from_owned(map).unwrap_or_else(|_| panic!("{}", INCOMPATIBLE_MODES)) - } - - /// Create a gradual difficulty calculator for the map. - pub fn gradual_difficulty(&self, difficulty: Difficulty) -> M::GradualDifficulty { - M::gradual_difficulty(difficulty, self) - } - - /// Create a gradual performance calculator for the map. - pub fn gradual_performance(&self, difficulty: Difficulty) -> M::GradualPerformance { - M::gradual_performance(difficulty, self) - } -} - -impl<'a, M: IGameMode> Converted<'a, M> { - /// Create a performance calculator for the map. - pub fn performance(self) -> M::Performance<'a> { - M::performance(self) - } - - /// Attempt to convert a [`&Beatmap`] to the specified mode. - /// - /// If the conversion is incompatible, `None` is returned. - /// - /// [`&Beatmap`]: Beatmap - pub fn try_from_ref(map: &'a Beatmap) -> Option { - let mut map = match M::check_convert(map) { - ConvertStatus::Noop => return Some(Self::new(Cow::Borrowed(map))), - ConvertStatus::Conversion => map.to_owned(), - ConvertStatus::Incompatible => return None, - }; - - match M::try_convert(&mut map) { - ConvertStatus::Conversion => Some(Self::new(Cow::Owned(map))), - ConvertStatus::Noop => Some(Self::new(Cow::Owned(map))), - ConvertStatus::Incompatible => None, - } - } - - /// Convert a [`&Beatmap`] to the specified mode. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. - /// - /// [`&Beatmap`]: Beatmap - pub fn unchecked_from_ref(map: &'a Beatmap) -> Self { - Self::try_from_ref(map).expect(INCOMPATIBLE_MODES) - } - - /// Attempt to convert a [`&mut Beatmap`] to the specified mode. - /// - /// If the conversion is incompatible, `None` is returned. - /// - /// [`&mut Beatmap`]: Beatmap - pub fn try_from_mut(map: &'a mut Beatmap) -> Option { - match M::try_convert(map) { - ConvertStatus::Conversion => Some(Self::new(Cow::Borrowed(map))), - ConvertStatus::Noop => Some(Self::new(Cow::Borrowed(map))), - ConvertStatus::Incompatible => None, - } - } - - /// Convert a [`&mut Beatmap`] to the specified mode. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. - /// - /// [`&mut Beatmap`]: Beatmap - pub fn unchecked_from_mut(map: &'a mut Beatmap) -> Self { - Self::try_from_mut(map).expect(INCOMPATIBLE_MODES) - } - - /// Attempt to convert a [`Converted`] from mode `M` to mode `N`. - /// - /// If the conversion is incompatible the [`Converted`] will be returned - /// unchanged as `Err`. - #[allow(clippy::result_large_err)] - pub fn try_convert(self) -> Result, Self> { - match self.map { - Cow::Borrowed(map) => Converted::::try_from_ref(map).ok_or(self), - Cow::Owned(map) => { - Converted::::try_from_owned(map).map_err(|map| Self::new(Cow::Owned(map))) - } - } - } - - /// Convert a [`Converted`] from mode `M` to mode `N`. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. - pub fn unchecked_convert(self) -> Converted<'a, N> { - match self.map { - Cow::Borrowed(map) => Converted::::unchecked_from_ref(map), - Cow::Owned(map) => Converted::::unchecked_from_owned(map), - } - } -} - -impl Clone for Converted<'_, M> { - fn clone(&self) -> Self { - Self::new(self.map.clone()) - } -} - -impl Debug for Converted<'_, M> { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.debug_struct("Converted") - .field("map", &self.map) - .field("mode", &GenericFormatter::::new()) - .finish() - } -} - -impl PartialEq for Converted<'_, M> { - fn eq(&self, other: &Self) -> bool { - self.map == other.map - } -} - -impl Deref for Converted<'_, M> { - type Target = Beatmap; - - fn deref(&self) -> &Self::Target { - self.map.as_ref() - } -} diff --git a/src/model/beatmap/mod.rs b/src/model/beatmap/mod.rs index 26ac7865..e18f725f 100644 --- a/src/model/beatmap/mod.rs +++ b/src/model/beatmap/mod.rs @@ -1,4 +1,4 @@ -use std::{io, path::Path, str::FromStr}; +use std::{borrow::Cow, io, path::Path, str::FromStr}; use rosu_map::{ section::{general::GameMode, hit_objects::hit_samples::HitSoundType}, @@ -8,13 +8,12 @@ use rosu_map::{ pub use rosu_map::section::events::BreakPeriod; use crate::{ - catch::Catch, mania::Mania, osu::Osu, taiko::Taiko, Difficulty, GradualDifficulty, + catch::Catch, mania::Mania, taiko::Taiko, Difficulty, GameMods, GradualDifficulty, GradualPerformance, Performance, }; pub use self::{ attributes::{BeatmapAttributes, BeatmapAttributesBuilder, HitWindows}, - converted::Converted, decode::{BeatmapState, ParseBeatmapError}, }; @@ -24,12 +23,11 @@ use super::{ TimingPoint, }, hit_object::HitObject, - mode::{ConvertStatus, IGameMode}, + mode::ConvertError, }; mod attributes; mod bpm; -mod converted; mod decode; /// All beatmap data that is relevant for difficulty and performance @@ -122,75 +120,67 @@ impl Beatmap { self.breaks.iter().map(BreakPeriod::duration).sum() } - /// Convert a [`&mut Beatmap`] to the specified mode with an argument - /// instead of a generic parameter. - /// - /// [`&mut Beatmap`]: Beatmap - pub fn convert_in_place(&mut self, mode: GameMode) -> ConvertStatus { - match mode { - GameMode::Osu => Osu::try_convert(self), - GameMode::Taiko => Taiko::try_convert(self), - GameMode::Catch => Catch::try_convert(self), - GameMode::Mania => Mania::try_convert(self), - } + /// Attempt to convert a [`Beatmap`] to the specified mode. + pub fn convert(mut self, mode: GameMode, mods: &GameMods) -> Result { + self.convert_mut(mode, mods)?; + + Ok(self) } /// Attempt to convert a [`&Beatmap`] to the specified mode. /// - /// If the conversion is incompatible, `None` is returned. - /// /// [`&Beatmap`]: Beatmap - pub fn try_as_converted(&self) -> Option> { - Converted::try_from_ref(self) - } + pub fn convert_ref( + &self, + mode: GameMode, + mods: &GameMods, + ) -> Result, ConvertError> { + if self.mode == mode { + return Ok(Cow::Borrowed(self)); + } else if self.is_convert { + return Err(ConvertError::AlreadyConverted); + } else if self.mode != GameMode::Osu { + return Err(ConvertError::Convert { + from: self.mode, + to: mode, + }); + } - /// Convert a [`&Beatmap`] to the specified mode. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. - /// - /// [`&Beatmap`]: Beatmap - pub fn unchecked_as_converted(&self) -> Converted<'_, M> { - Converted::unchecked_from_ref(self) - } + let mut map = self.to_owned(); - /// Attempt to convert a [`&mut Beatmap`] to the specified mode. - /// - /// If the conversion is incompatible, `None` is returned. - /// - /// [`&mut Beatmap`]: Beatmap - pub fn try_as_converted_mut(&mut self) -> Option> { - Converted::try_from_mut(self) + match mode { + GameMode::Taiko => Taiko::convert(&mut map), + GameMode::Catch => Catch::convert(&mut map), + GameMode::Mania => Mania::convert(&mut map, mods), + GameMode::Osu => unreachable!(), + }; + + Ok(Cow::Owned(map)) } - /// Convert a [`&mut Beatmap`] to the specified mode. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. + /// Attempt to convert a [`&mut Beatmap`] to the specified mode. /// /// [`&mut Beatmap`]: Beatmap - pub fn unchecked_as_converted_mut(&mut self) -> Converted<'_, M> { - Converted::unchecked_from_mut(self) - } + pub fn convert_mut(&mut self, mode: GameMode, mods: &GameMods) -> Result<(), ConvertError> { + if self.mode == mode { + return Ok(()); + } else if self.is_convert { + return Err(ConvertError::AlreadyConverted); + } else if self.mode != GameMode::Osu { + return Err(ConvertError::Convert { + from: self.mode, + to: mode, + }); + } - /// Attempt to convert a [`Beatmap`] to the specified mode. - /// - /// If the conversion is incompatible the [`Beatmap`] will be returned - /// unchanged as `Err`. - #[allow(clippy::result_large_err)] - pub fn try_into_converted<'a, M: IGameMode>(self) -> Result, Self> { - Converted::try_from_owned(self) - } + match mode { + GameMode::Taiko => Taiko::convert(self), + GameMode::Catch => Catch::convert(self), + GameMode::Mania => Mania::convert(self, mods), + GameMode::Osu => unreachable!(), + } - /// Convert a [`Beatmap`] to the specified mode. - /// - /// # Panics - /// - /// Panics if the conversion is incompatible. - pub fn unchecked_into_converted<'a, M: IGameMode>(self) -> Converted<'a, M> { - Converted::unchecked_from_owned(self) + Ok(()) } } diff --git a/src/model/mode.rs b/src/model/mode.rs index 1ee15301..a2c92f8c 100644 --- a/src/model/mode.rs +++ b/src/model/mode.rs @@ -1,8 +1,13 @@ +use std::{ + error::Error, + fmt::{Display, Formatter, Result as FmtResult}, +}; + pub use rosu_map::section::general::GameMode; use crate::Difficulty; -use super::beatmap::{Beatmap, Converted}; +use super::beatmap::Beatmap; /// A way to specify a gamemode at compile-time. /// @@ -29,54 +34,58 @@ pub trait IGameMode: Sized { /// The type of a gradual performance calculator. type GradualPerformance; - /// Check whether the map's mode can be converted to the current type. - fn check_convert(map: &Beatmap) -> ConvertStatus; - - /// Attempt to convert a beatmap. - /// - /// In case [`ConvertStatus::Incompatible`] is returned, the map is not - /// modified. - fn try_convert(map: &mut Beatmap) -> ConvertStatus; - - /// Perform a difficulty calculation for a [`Converted`] beatmap and - /// process the final skill values. - fn difficulty(difficulty: &Difficulty, map: &Converted<'_, Self>) - -> Self::DifficultyAttributes; + /// Perform a difficulty calculation for a [`Beatmap`] and process the + /// final skill values. + fn difficulty( + difficulty: &Difficulty, + map: &Beatmap, + ) -> Result; - /// Perform a difficulty calculation for a [`Converted`] beatmap without - /// processing the final skill values. - fn strains(difficulty: &Difficulty, map: &Converted<'_, Self>) -> Self::Strains; + /// Perform a difficulty calculation for a [`Beatmap`] without processing + /// the final skill values. + fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result; - /// Create a performance calculator for a [`Converted`] beatmap. - fn performance(map: Converted<'_, Self>) -> Self::Performance<'_>; + /// Create a performance calculator for a [`Beatmap`]. + fn performance(map: &Beatmap) -> Self::Performance<'_>; - /// Create a gradual difficulty calculator for a [`Converted`] beatmap. + /// Create a gradual difficulty calculator for a [`Beatmap`]. fn gradual_difficulty( difficulty: Difficulty, - map: &Converted<'_, Self>, - ) -> Self::GradualDifficulty; + map: &Beatmap, + ) -> Result; - /// Create a gradual performance calculator for a [`Converted`] beatmap. + /// Create a gradual performance calculator for a [`Beatmap`]. fn gradual_performance( difficulty: Difficulty, - map: &Converted<'_, Self>, - ) -> Self::GradualPerformance; + map: &Beatmap, + ) -> Result; } -/// The status of a conversion through [`IGameMode::try_convert`]. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ConvertStatus { - /// Conversion not necessary. - Noop, - /// Conversion possible. - Conversion, - /// Conversion not possible. - Incompatible, +/// Error type when failing to convert a [`Beatmap`] from one [`GameMode`] to +/// another. +#[derive(Copy, Clone, Debug)] +pub enum ConvertError { + /// Cannot convert an already converted map + AlreadyConverted, + /// Cannot convert from [`GameMode`] `from` to `to` + Convert { from: GameMode, to: GameMode }, +} + +impl Error for ConvertError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } } -impl ConvertStatus { - /// Whether this [`ConvertStatus`] represents a success. - pub const fn success(self) -> bool { - matches!(self, Self::Noop | Self::Conversion) +impl Display for ConvertError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + ConvertError::AlreadyConverted => { + f.write_str("Cannot convert an already converted map") + } + ConvertError::Convert { from, to } => { + write!(f, "Cannot convert from {from:?} to {to:?}") + } + } } } diff --git a/src/osu/convert.rs b/src/osu/convert.rs index 30e33f5b..ae2d36f1 100644 --- a/src/osu/convert.rs +++ b/src/osu/convert.rs @@ -1,35 +1,15 @@ -use rosu_map::section::{general::GameMode, hit_objects::CurveBuffers}; +use rosu_map::section::hit_objects::CurveBuffers; -use crate::model::{ - beatmap::{Beatmap, Converted}, - mode::ConvertStatus, - mods::Reflection, -}; +use crate::model::{beatmap::Beatmap, mods::Reflection}; use super::{ attributes::OsuDifficultyAttributes, difficulty::scaling_factor::ScalingFactor, object::{NestedSliderObjectKind, OsuObject, OsuObjectKind}, - Osu, }; -/// A [`Beatmap`] for [`Osu`] calculations. -pub type OsuBeatmap<'a> = Converted<'a, Osu>; - -pub fn check_convert(map: &Beatmap) -> ConvertStatus { - if map.mode == GameMode::Osu { - ConvertStatus::Noop - } else { - ConvertStatus::Incompatible - } -} - -pub fn try_convert(map: &mut Beatmap) -> ConvertStatus { - check_convert(map) -} - pub fn convert_objects( - converted: &OsuBeatmap<'_>, + map: &Beatmap, scaling_factor: &ScalingFactor, reflection: Reflection, time_preempt: f64, @@ -40,10 +20,10 @@ pub fn convert_objects( // mean=5.16 | median=4 let mut ticks_buf = Vec::new(); - let mut osu_objects: Box<[_]> = converted + let mut osu_objects: Box<[_]> = map .hit_objects .iter() - .map(|h| OsuObject::new(h, converted, &mut curve_bufs, &mut ticks_buf)) + .map(|h| OsuObject::new(h, map, &mut curve_bufs, &mut ticks_buf)) .inspect(|h| { if take == 0 { return; @@ -77,9 +57,9 @@ pub fn convert_objects( .for_each(OsuObject::reflect_both_axes), } - let stack_threshold = time_preempt * f64::from(converted.stack_leniency); + let stack_threshold = time_preempt * f64::from(map.stack_leniency); - if converted.version >= 6 { + if map.version >= 6 { stacking(&mut osu_objects, stack_threshold); } else { old_stacking(&mut osu_objects, stack_threshold); diff --git a/src/osu/difficulty/gradual.rs b/src/osu/difficulty/gradual.rs index bf314ec8..f431cb49 100644 --- a/src/osu/difficulty/gradual.rs +++ b/src/osu/difficulty/gradual.rs @@ -1,13 +1,15 @@ use std::{cmp, mem}; +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::skills::Skill, + model::mode::ConvertError, osu::{ convert::convert_objects, object::{OsuObject, OsuObjectKind}, - OsuBeatmap, }, - Difficulty, + Beatmap, Difficulty, }; use self::osu_objects::OsuObjects; @@ -71,22 +73,23 @@ struct NotClonable; impl OsuGradualDifficulty { /// Create a new difficulty attributes iterator for osu!standard maps. - pub fn new(difficulty: Difficulty, converted: &OsuBeatmap<'_>) -> Self { + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { let mods = difficulty.get_mods(); + let map = map.convert_ref(GameMode::Osu, mods)?; let OsuDifficultySetup { scaling_factor, map_attrs, mut attrs, time_preempt, - } = OsuDifficultySetup::new(&difficulty, converted); + } = OsuDifficultySetup::new(&difficulty, &map); let osu_objects = convert_objects( - converted, + &map, &scaling_factor, mods.reflection(), time_preempt, - converted.hit_objects.len(), + map.hit_objects.len(), &mut attrs, ); @@ -111,7 +114,7 @@ impl OsuGradualDifficulty { let skills = OsuSkills::new(mods, &scaling_factor, &map_attrs, time_preempt); let diff_objects = extend_lifetime(diff_objects.into_boxed_slice()); - Self { + Ok(Self { idx: 0, difficulty, attrs, @@ -119,7 +122,7 @@ impl OsuGradualDifficulty { diff_objects, osu_objects, _not_clonable: NotClonable, - } + }) } fn increment_combo(h: &OsuObject, attrs: &mut OsuDifficultyAttributes) { @@ -265,28 +268,22 @@ mod tests { #[test] fn empty() { - let converted = Beatmap::from_bytes(&[]) - .unwrap() - .unchecked_into_converted::(); - - let mut gradual = OsuGradualDifficulty::new(Difficulty::new(), &converted); - + let map = Beatmap::from_bytes(&[]).unwrap(); + let mut gradual = OsuGradualDifficulty::new(Difficulty::new(), &map).unwrap(); assert!(gradual.next().is_none()); } #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/2785319.osu") - .unwrap() - .unchecked_into_converted::(); + let map = Beatmap::from_path("./resources/2785319.osu").unwrap(); let difficulty = Difficulty::new(); - let mut gradual = OsuGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_2nd = OsuGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_3rd = OsuGradualDifficulty::new(difficulty.clone(), &converted); + let mut gradual = OsuGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = OsuGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = OsuGradualDifficulty::new(difficulty.clone(), &map).unwrap(); - let hit_objects_len = converted.hit_objects.len(); + let hit_objects_len = map.hit_objects.len(); for i in 1.. { let Some(next_gradual) = gradual.next() else { @@ -309,8 +306,8 @@ mod tests { let expected = difficulty .clone() .passed_objects(i as u32) - .with_mode() - .calculate(&converted); + .calculate_for_mode::(&map) + .unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/osu/difficulty/mod.rs b/src/osu/difficulty/mod.rs index f8ec0ab2..5258b97c 100644 --- a/src/osu/difficulty/mod.rs +++ b/src/osu/difficulty/mod.rs @@ -1,5 +1,6 @@ use std::{cmp, pin::Pin}; +use rosu_map::section::general::GameMode; use skills::{ flashlight::Flashlight, strain::{DifficultyValue, OsuStrainSkill, UsedOsuStrainSkills}, @@ -7,18 +8,19 @@ use skills::{ use crate::{ any::difficulty::{skills::Skill, Difficulty}, - model::{beatmap::BeatmapAttributes, mods::GameMods}, + model::{beatmap::BeatmapAttributes, mode::ConvertError, mods::GameMods}, osu::{ convert::convert_objects, difficulty::{object::OsuDifficultyObject, scaling_factor::ScalingFactor}, object::OsuObject, performance::PERFORMANCE_BASE_MULTIPLIER, }, + Beatmap, }; use self::skills::OsuSkills; -use super::{attributes::OsuDifficultyAttributes, convert::OsuBeatmap}; +use super::attributes::OsuDifficultyAttributes; pub mod gradual; mod object; @@ -30,7 +32,12 @@ const DIFFICULTY_MULTIPLIER: f64 = 0.0675; const HD_FADE_IN_DURATION_MULTIPLIER: f64 = 0.4; const HD_FADE_OUT_DURATION_MULTIPLIER: f64 = 0.3; -pub fn difficulty(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuDifficultyAttributes { +pub fn difficulty( + difficulty: &Difficulty, + map: &Beatmap, +) -> Result { + let map = map.convert_ref(GameMode::Osu, difficulty.get_mods())?; + let DifficultyValues { skills: OsuSkills { @@ -40,7 +47,7 @@ pub fn difficulty(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuDif flashlight, }, mut attrs, - } = DifficultyValues::calculate(difficulty, converted); + } = DifficultyValues::calculate(difficulty, &map); let aim_difficulty_value = aim.difficulty_value(); let aim_no_sliders_difficulty_value = aim_no_sliders.difficulty_value(); @@ -60,7 +67,7 @@ pub fn difficulty(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuDif flashlight_difficulty_value, ); - attrs + Ok(attrs) } pub struct OsuDifficultySetup { @@ -71,9 +78,9 @@ pub struct OsuDifficultySetup { } impl OsuDifficultySetup { - pub fn new(difficulty: &Difficulty, converted: &OsuBeatmap) -> Self { + pub fn new(difficulty: &Difficulty, map: &Beatmap) -> Self { let clock_rate = difficulty.get_clock_rate(); - let map_attrs = converted.attributes().difficulty(difficulty).build(); + let map_attrs = map.attributes().difficulty(difficulty).build(); let scaling_factor = ScalingFactor::new(map_attrs.cs); let attrs = OsuDifficultyAttributes { @@ -100,7 +107,7 @@ pub struct DifficultyValues { } impl DifficultyValues { - pub fn calculate(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> Self { + pub fn calculate(difficulty: &Difficulty, map: &Beatmap) -> Self { let mods = difficulty.get_mods(); let take = difficulty.get_passed_objects(); @@ -109,10 +116,10 @@ impl DifficultyValues { map_attrs, mut attrs, time_preempt, - } = OsuDifficultySetup::new(difficulty, converted); + } = OsuDifficultySetup::new(difficulty, map); let mut osu_objects = convert_objects( - converted, + map, &scaling_factor, mods.reflection(), time_preempt, @@ -134,7 +141,7 @@ impl DifficultyValues { let mut flashlight = Skill::new(&mut skills.flashlight, &diff_objects); // The first hit object has no difficulty object - let take_diff_objects = cmp::min(converted.hit_objects.len(), take).saturating_sub(1); + let take_diff_objects = cmp::min(map.hit_objects.len(), take).saturating_sub(1); for hit_object in diff_objects.iter().take(take_diff_objects) { aim.process(hit_object); diff --git a/src/osu/mod.rs b/src/osu/mod.rs index 60fb37bd..3433aa5a 100644 --- a/src/osu/mod.rs +++ b/src/osu/mod.rs @@ -3,14 +3,13 @@ use rosu_map::util::Pos; use crate::{ model::{ beatmap::Beatmap, - mode::{ConvertStatus, IGameMode}, + mode::{ConvertError, IGameMode}, }, Difficulty, }; pub use self::{ attributes::{OsuDifficultyAttributes, OsuPerformanceAttributes}, - convert::OsuBeatmap, difficulty::gradual::OsuGradualDifficulty, performance::{gradual::OsuGradualPerformance, OsuPerformance}, score_state::{OsuScoreOrigin, OsuScoreState}, @@ -39,37 +38,32 @@ impl IGameMode for Osu { type GradualDifficulty = OsuGradualDifficulty; type GradualPerformance = OsuGradualPerformance; - fn check_convert(map: &Beatmap) -> ConvertStatus { - convert::check_convert(map) - } - - fn try_convert(map: &mut Beatmap) -> ConvertStatus { - convert::try_convert(map) - } - fn difficulty( difficulty: &Difficulty, - converted: &OsuBeatmap<'_>, - ) -> Self::DifficultyAttributes { - difficulty::difficulty(difficulty, converted) + map: &Beatmap, + ) -> Result { + difficulty::difficulty(difficulty, map) } - fn strains(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> Self::Strains { - strains::strains(difficulty, converted) + fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + strains::strains(difficulty, map) } - fn performance(map: OsuBeatmap<'_>) -> Self::Performance<'_> { + fn performance(map: &Beatmap) -> Self::Performance<'_> { OsuPerformance::new(map) } - fn gradual_difficulty(difficulty: Difficulty, map: &OsuBeatmap<'_>) -> Self::GradualDifficulty { + fn gradual_difficulty( + difficulty: Difficulty, + map: &Beatmap, + ) -> Result { OsuGradualDifficulty::new(difficulty, map) } fn gradual_performance( difficulty: Difficulty, - map: &OsuBeatmap<'_>, - ) -> Self::GradualPerformance { + map: &Beatmap, + ) -> Result { OsuGradualPerformance::new(difficulty, map) } } diff --git a/src/osu/object.rs b/src/osu/object.rs index 085e9cf0..95ec4cf2 100644 --- a/src/osu/object.rs +++ b/src/osu/object.rs @@ -14,9 +14,10 @@ use crate::{ hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner}, }, util::{get_precision_adjusted_beat_len, sort}, + Beatmap, }; -use super::{convert::OsuBeatmap, PLAYFIELD_BASE_SIZE}; +use super::PLAYFIELD_BASE_SIZE; pub struct OsuObject { pub pos: Pos, @@ -34,14 +35,14 @@ impl OsuObject { pub fn new( h: &HitObject, - converted: &OsuBeatmap<'_>, + map: &Beatmap, curve_bufs: &mut CurveBuffers, ticks_buf: &mut Vec, ) -> Self { let kind = match h.kind { HitObjectKind::Circle => OsuObjectKind::Circle, HitObjectKind::Slider(ref slider) => { - OsuObjectKind::Slider(OsuSlider::new(h, slider, converted, curve_bufs, ticks_buf)) + OsuObjectKind::Slider(OsuSlider::new(h, slider, map, curve_bufs, ticks_buf)) } HitObjectKind::Spinner(spinner) => OsuObjectKind::Spinner(spinner), HitObjectKind::Hold(HoldNote { duration }) => { @@ -188,19 +189,19 @@ impl OsuSlider { fn new( h: &HitObject, slider: &Slider, - converted: &OsuBeatmap<'_>, + map: &Beatmap, curve_bufs: &mut CurveBuffers, ticks_buf: &mut Vec, ) -> Self { let start_time = h.start_time; - let slider_multiplier = converted.slider_multiplier; - let slider_tick_rate = converted.slider_tick_rate; + let slider_multiplier = map.slider_multiplier; + let slider_tick_rate = map.slider_tick_rate; - let beat_len = converted + let beat_len = map .timing_point_at(start_time) .map_or(TimingPoint::DEFAULT_BEAT_LEN, |point| point.beat_len); - let (slider_velocity, generate_ticks) = converted.difficulty_point_at(start_time).map_or( + let (slider_velocity, generate_ticks) = map.difficulty_point_at(start_time).map_or( ( DifficultyPoint::DEFAULT_SLIDER_VELOCITY, DifficultyPoint::DEFAULT_GENERATE_TICKS, @@ -221,7 +222,7 @@ impl OsuSlider { let duration = end_time - start_time; let span_duration = duration / span_count; - let tick_dist_multiplier = if converted.version < 8 { + let tick_dist_multiplier = if map.version < 8 { slider_velocity.recip() } else { 1.0 diff --git a/src/osu/performance/gradual.rs b/src/osu/performance/gradual.rs index f3d43c1b..fb839abd 100644 --- a/src/osu/performance/gradual.rs +++ b/src/osu/performance/gradual.rs @@ -1,7 +1,4 @@ -use crate::{ - osu::{OsuBeatmap, OsuGradualDifficulty}, - Difficulty, -}; +use crate::{model::mode::ConvertError, osu::OsuGradualDifficulty, Beatmap, Difficulty}; use super::{OsuPerformanceAttributes, OsuScoreState}; @@ -86,11 +83,11 @@ pub struct OsuGradualPerformance { impl OsuGradualPerformance { /// Create a new gradual performance calculator for osu!standard maps. - pub fn new(difficulty: Difficulty, converted: &OsuBeatmap<'_>) -> Self { + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { let lazer = difficulty.get_lazer(); - let difficulty = OsuGradualDifficulty::new(difficulty, converted); + let difficulty = OsuGradualDifficulty::new(difficulty, map)?; - Self { lazer, difficulty } + Ok(Self { lazer, difficulty }) } /// Process the next hit object and calculate the performance attributes @@ -110,6 +107,7 @@ impl OsuGradualPerformance { /// /// Note that the count is zero-indexed, so `n=0` will process 1 object, /// `n=1` will process 2, and so on. + #[allow(clippy::missing_panics_doc)] pub fn nth(&mut self, state: OsuScoreState, n: usize) -> Option { let performance = self .difficulty @@ -119,7 +117,8 @@ impl OsuGradualPerformance { .state(state) .difficulty(self.difficulty.difficulty.clone()) .passed_objects(self.difficulty.idx as u32) - .calculate(); + .calculate() + .expect("no conversion required"); Some(performance) } @@ -133,28 +132,23 @@ impl OsuGradualPerformance { #[cfg(test)] mod tests { - use crate::{ - osu::{Osu, OsuPerformance}, - Beatmap, - }; + use crate::{osu::OsuPerformance, Beatmap}; use super::*; #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/2785319.osu") - .unwrap() - .unchecked_into_converted::(); + let map = Beatmap::from_path("./resources/2785319.osu").unwrap(); let difficulty = Difficulty::new().mods(88); // HDHRDT - let mut gradual = OsuGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_2nd = OsuGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_3rd = OsuGradualPerformance::new(difficulty.clone(), &converted); + let mut gradual = OsuGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = OsuGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = OsuGradualPerformance::new(difficulty.clone(), &map).unwrap(); let mut state = OsuScoreState::default(); - let hit_objects_len = converted.hit_objects.len(); + let hit_objects_len = map.hit_objects.len(); for i in 1.. { state.misses += 1; @@ -176,15 +170,15 @@ mod tests { assert_eq!(next_gradual, next_gradual_3rd); } - let mut regular_calc = OsuPerformance::new(converted.as_owned()) + let mut regular_calc = OsuPerformance::new(&map) .difficulty(difficulty.clone()) .passed_objects(i as u32) .state(state); - let regular_state = regular_calc.generate_state(); + let regular_state = regular_calc.generate_state().unwrap(); assert_eq!(state, regular_state); - let expected = regular_calc.calculate(); + let expected = regular_calc.calculate().unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/osu/performance/mod.rs b/src/osu/performance/mod.rs index a5fe120c..71cd297a 100644 --- a/src/osu/performance/mod.rs +++ b/src/osu/performance/mod.rs @@ -1,4 +1,4 @@ -use std::cmp; +use std::{borrow::Cow, cmp}; use rosu_map::section::general::GameMode; @@ -6,9 +6,10 @@ use crate::{ any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance, Performance}, catch::CatchPerformance, mania::ManiaPerformance, - model::mods::GameMods, + model::{mode::ConvertError, mods::GameMods}, taiko::TaikoPerformance, util::{float_ext::FloatExt, map_or_attrs::MapOrAttrs}, + Beatmap, }; use super::{ @@ -43,7 +44,7 @@ impl<'map> OsuPerformance<'map> { /// The argument `map_or_attrs` must be either /// - previously calculated attributes ([`OsuDifficultyAttributes`] /// or [`OsuPerformanceAttributes`]) - /// - a beatmap ([`OsuBeatmap<'map>`]) + /// - a [`Beatmap`] (by reference or value) /// /// If a map is given, difficulty attributes will need to be calculated /// internally which is a costly operation. Hence, passing attributes @@ -52,21 +53,18 @@ impl<'map> OsuPerformance<'map> { /// However, when passing previously calculated attributes, make sure they /// have been calculated for the same map and [`Difficulty`] settings. /// Otherwise, the final attributes will be incorrect. - /// - /// [`OsuBeatmap<'map>`]: crate::osu::OsuBeatmap pub fn new(map_or_attrs: impl IntoModePerformance<'map, Osu>) -> Self { map_or_attrs.into_performance() } /// Try to create a new performance calculator for osu! maps. /// - /// Returns `None` if `map_or_attrs` does not belong to osu! e.g. - /// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`] - /// of a different mode. + /// Returns `None` if `map_or_attrs` does not belong to osu! i.e. + /// a [`DifficultyAttributes`] or [`PerformanceAttributes`] of a different + /// mode. /// /// See [`OsuPerformance::new`] for more information. /// - /// [`Converted`]: crate::model::beatmap::Converted /// [`DifficultyAttributes`]: crate::any::DifficultyAttributes /// [`PerformanceAttributes`]: crate::any::PerformanceAttributes pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option { @@ -349,10 +347,10 @@ impl<'map> OsuPerformance<'map> { /// Create the [`OsuScoreState`] that will be used for performance calculation. #[allow(clippy::too_many_lines)] - pub fn generate_state(&mut self) -> OsuScoreState { + pub fn generate_state(&mut self) -> Result { let attrs = match self.map_or_attrs { MapOrAttrs::Map(ref map) => { - let attrs = self.difficulty.with_mode().calculate(map); + let attrs = self.difficulty.calculate_for_mode::(map)?; self.map_or_attrs.insert_attrs(attrs) } @@ -636,7 +634,7 @@ impl<'map> OsuPerformance<'map> { self.n50 = Some(n50); self.misses = Some(misses); - OsuScoreState { + Ok(OsuScoreState { max_combo, large_tick_hits, slider_end_hits, @@ -644,16 +642,16 @@ impl<'map> OsuPerformance<'map> { n100, n50, misses, - } + }) } /// Calculate all performance related values, including pp and stars. - pub fn calculate(mut self) -> OsuPerformanceAttributes { - let state = self.generate_state(); + pub fn calculate(mut self) -> Result { + let state = self.generate_state()?; let attrs = match self.map_or_attrs { - MapOrAttrs::Map(ref map) => self.difficulty.with_mode().calculate(map), MapOrAttrs::Attrs(attrs) => attrs, + MapOrAttrs::Map(ref map) => self.difficulty.calculate_for_mode::(map)?, }; let mods = self.difficulty.get_mods(); @@ -718,7 +716,7 @@ impl<'map> OsuPerformance<'map> { using_classic_slider_acc, }; - inner.calculate() + Ok(inner.calculate()) } pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Osu>) -> Self { @@ -736,6 +734,33 @@ impl<'map> OsuPerformance<'map> { hitresult_priority: HitResultPriority::DEFAULT, } } + + #[allow(clippy::result_large_err)] + pub(crate) fn try_convert_map( + map_or_attrs: MapOrAttrs<'map, Osu>, + mode: GameMode, + mods: &GameMods, + ) -> Result, MapOrAttrs<'map, Osu>> { + let MapOrAttrs::Map(map) = map_or_attrs else { + return Err(map_or_attrs); + }; + + match map { + Cow::Borrowed(map) => match map.convert_ref(mode, mods) { + Ok(map) => Ok(map), + Err(_) => { + return Err(MapOrAttrs::Map(Cow::Borrowed(map))); + } + }, + Cow::Owned(mut map) => { + if map.convert_mut(mode, mods).is_err() { + return Err(MapOrAttrs::Map(Cow::Owned(map))); + } + + Ok(Cow::Owned(map)) + } + } + } } impl<'map, T: IntoModePerformance<'map, Osu>> From for OsuPerformance<'map> { @@ -1147,7 +1172,7 @@ mod test { use crate::{ any::{DifficultyAttributes, PerformanceAttributes}, - taiko::{Taiko, TaikoDifficultyAttributes, TaikoPerformanceAttributes}, + taiko::{TaikoDifficultyAttributes, TaikoPerformanceAttributes}, Beatmap, }; @@ -1166,8 +1191,8 @@ mod test { fn attrs() -> OsuDifficultyAttributes { ATTRS .get_or_init(|| { - let converted = beatmap().unchecked_into_converted::(); - let attrs = Difficulty::new().with_mode().calculate(&converted); + let map = beatmap(); + let attrs = Difficulty::new().calculate_for_mode::(&map).unwrap(); assert_eq!( (attrs.n_circles, attrs.n_sliders, attrs.n_spinners), @@ -1382,8 +1407,8 @@ mod test { state = state.misses(misses); } - let first = state.generate_state(); - let state = state.generate_state(); + let first = state.generate_state().unwrap(); + let state = state.generate_state().unwrap(); assert_eq!(first, state); let mut expected = brute_force_best( @@ -1413,7 +1438,8 @@ mod test { .n100(20) .misses(2) .hitresult_priority(HitResultPriority::BestCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = OsuScoreState { max_combo: 500, @@ -1437,7 +1463,8 @@ mod test { .n50(10) .misses(2) .hitresult_priority(HitResultPriority::BestCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = OsuScoreState { max_combo: 500, @@ -1460,7 +1487,8 @@ mod test { .n50(10) .misses(2) .hitresult_priority(HitResultPriority::WorstCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = OsuScoreState { max_combo: 500, @@ -1485,7 +1513,8 @@ mod test { .n50(10) .misses(2) .hitresult_priority(HitResultPriority::WorstCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = OsuScoreState { max_combo: 500, @@ -1503,12 +1532,11 @@ mod test { #[test] fn create() { let mut map = beatmap(); - let converted = map.unchecked_as_converted(); let _ = OsuPerformance::new(OsuDifficultyAttributes::default()); let _ = OsuPerformance::new(OsuPerformanceAttributes::default()); - let _ = OsuPerformance::new(&converted); - let _ = OsuPerformance::new(converted.as_owned()); + let _ = OsuPerformance::new(&map); + let _ = OsuPerformance::new(map.clone()); let _ = OsuPerformance::try_new(OsuDifficultyAttributes::default()).unwrap(); let _ = OsuPerformance::try_new(OsuPerformanceAttributes::default()).unwrap(); @@ -1519,19 +1547,19 @@ mod test { OsuPerformanceAttributes::default(), )) .unwrap(); - let _ = OsuPerformance::try_new(&converted).unwrap(); - let _ = OsuPerformance::try_new(converted.as_owned()).unwrap(); + let _ = OsuPerformance::try_new(&map).unwrap(); + let _ = OsuPerformance::try_new(map.clone()).unwrap(); let _ = OsuPerformance::from(OsuDifficultyAttributes::default()); let _ = OsuPerformance::from(OsuPerformanceAttributes::default()); - let _ = OsuPerformance::from(&converted); - let _ = OsuPerformance::from(converted); + let _ = OsuPerformance::from(&map); + let _ = OsuPerformance::from(map.clone()); let _ = OsuDifficultyAttributes::default().performance(); let _ = OsuPerformanceAttributes::default().performance(); - map.mode = GameMode::Taiko; - let converted = map.unchecked_as_converted::(); + map.convert_mut(GameMode::Taiko, &GameMods::default()) + .unwrap(); assert!(OsuPerformance::try_new(TaikoDifficultyAttributes::default()).is_none()); assert!(OsuPerformance::try_new(TaikoPerformanceAttributes::default()).is_none()); @@ -1543,7 +1571,7 @@ mod test { TaikoPerformanceAttributes::default() )) .is_none()); - assert!(OsuPerformance::try_new(&converted).is_none()); - assert!(OsuPerformance::try_new(converted).is_none()); + assert!(OsuPerformance::try_new(&map).is_none()); + assert!(OsuPerformance::try_new(map).is_none()); } } diff --git a/src/osu/score_state.rs b/src/osu/score_state.rs index be1ceac2..63708a84 100644 --- a/src/osu/score_state.rs +++ b/src/osu/score_state.rs @@ -89,6 +89,7 @@ impl Default for OsuScoreState { } } +/// Type to pass [`OsuScoreState::accuracy`] and specify the origin of a score. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum OsuScoreOrigin { /// For scores set on osu!stable diff --git a/src/osu/strains.rs b/src/osu/strains.rs index a9b0ef38..9adfc6be 100644 --- a/src/osu/strains.rs +++ b/src/osu/strains.rs @@ -1,9 +1,8 @@ -use crate::Difficulty; +use rosu_map::section::general::GameMode; -use super::{ - convert::OsuBeatmap, - difficulty::{skills::OsuSkills, DifficultyValues}, -}; +use crate::{model::mode::ConvertError, Beatmap, Difficulty}; + +use super::difficulty::{skills::OsuSkills, DifficultyValues}; /// The result of calculating the strains on a osu! map. /// @@ -25,7 +24,9 @@ impl OsuStrains { pub const SECTION_LEN: f64 = 400.0; } -pub fn strains(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuStrains { +pub fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Osu, difficulty.get_mods())?; + let DifficultyValues { skills: OsuSkills { @@ -35,12 +36,12 @@ pub fn strains(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> OsuStrain flashlight, }, attrs: _, - } = DifficultyValues::calculate(difficulty, converted); + } = DifficultyValues::calculate(difficulty, &map); - OsuStrains { + Ok(OsuStrains { aim: aim.get_curr_strain_peaks().into_vec(), aim_no_sliders: aim_no_sliders.get_curr_strain_peaks().into_vec(), speed: speed.get_curr_strain_peaks().into_vec(), flashlight: flashlight.get_curr_strain_peaks().into_vec(), - } + }) } diff --git a/src/taiko/convert.rs b/src/taiko/convert.rs index 83a19ef9..1c284a7b 100644 --- a/src/taiko/convert.rs +++ b/src/taiko/convert.rs @@ -4,43 +4,17 @@ use rosu_map::{section::general::GameMode, util::Pos}; use crate::{ model::{ - beatmap::{Beatmap, Converted}, + beatmap::Beatmap, control_point::{DifficultyPoint, TimingPoint}, hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner}, - mode::ConvertStatus, }, util::{float_ext::FloatExt, get_precision_adjusted_beat_len, sort::TandemSorter}, }; -use super::Taiko; - -/// A [`Beatmap`] for [`Taiko`] calculations. -pub type TaikoBeatmap<'a> = Converted<'a, Taiko>; - const VELOCITY_MULTIPLIER: f32 = 1.4; const OSU_BASE_SCORING_DIST: f32 = 100.0; -pub const fn check_convert(map: &Beatmap) -> ConvertStatus { - match map.mode { - GameMode::Osu => ConvertStatus::Conversion, - GameMode::Taiko => ConvertStatus::Noop, - GameMode::Catch | GameMode::Mania => ConvertStatus::Incompatible, - } -} - -pub fn try_convert(map: &mut Beatmap) -> ConvertStatus { - match map.mode { - GameMode::Osu => { - convert(map); - - ConvertStatus::Conversion - } - GameMode::Taiko => ConvertStatus::Noop, - GameMode::Catch | GameMode::Mania => ConvertStatus::Incompatible, - } -} - -fn convert(map: &mut Beatmap) { +pub fn convert(map: &mut Beatmap) { let mut new_objects = Vec::new(); let mut new_sounds = Vec::new(); diff --git a/src/taiko/difficulty/gradual.rs b/src/taiko/difficulty/gradual.rs index 2dd54789..04dca492 100644 --- a/src/taiko/difficulty/gradual.rs +++ b/src/taiko/difficulty/gradual.rs @@ -1,11 +1,12 @@ use std::{cmp, mem, slice::Iter}; +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::skills::Skill, - model::{beatmap::HitWindows, hit_object::HitObject}, - taiko::TaikoBeatmap, + model::{beatmap::HitWindows, hit_object::HitObject, mode::ConvertError}, util::sync::RefCount, - Difficulty, + Beatmap, Difficulty, }; use super::{ @@ -69,13 +70,15 @@ enum FirstTwoCombos { impl TaikoGradualDifficulty { /// Create a new difficulty attributes iterator for osu!taiko maps. - pub fn new(difficulty: Difficulty, converted: &TaikoBeatmap<'_>) -> Self { + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Taiko, difficulty.get_mods())?; + let take = difficulty.get_passed_objects(); let clock_rate = difficulty.get_clock_rate(); let first_combos = match ( - converted.hit_objects.first().map(HitObject::is_circle), - converted.hit_objects.get(1).map(HitObject::is_circle), + map.hit_objects.first().map(HitObject::is_circle), + map.hit_objects.get(1).map(HitObject::is_circle), ) { (None, _) | (Some(false), Some(false) | None) => FirstTwoCombos::None, (Some(true), Some(false) | None) => FirstTwoCombos::OnlyFirst, @@ -87,13 +90,13 @@ impl TaikoGradualDifficulty { od_great, od_ok, ar: _, - } = converted.attributes().difficulty(&difficulty).hit_windows(); + } = map.attributes().difficulty(&difficulty).hit_windows(); let mut n_diff_objects = 0; let mut max_combo = 0; let diff_objects = DifficultyValues::create_difficulty_objects( - converted, + &map, take as u32, clock_rate, &mut max_combo, @@ -105,19 +108,15 @@ impl TaikoGradualDifficulty { let attrs = TaikoDifficultyAttributes { great_hit_window: od_great, ok_hit_window: od_ok.unwrap_or(0.0), - is_convert: converted.is_convert, + is_convert: map.is_convert, ..Default::default() }; - let total_hits = converted - .hit_objects - .iter() - .filter(|h| h.is_circle()) - .count(); + let total_hits = map.hit_objects.iter().filter(|h| h.is_circle()).count(); let diff_objects_iter = extend_lifetime(diff_objects.iter()); - Self { + Ok(Self { idx: 0, difficulty, diff_objects, @@ -126,7 +125,7 @@ impl TaikoGradualDifficulty { attrs, total_hits, first_combos, - } + }) } } @@ -266,38 +265,30 @@ impl ExactSizeIterator for TaikoGradualDifficulty { #[cfg(test)] mod tests { - use crate::Beatmap; + use crate::{taiko::Taiko, Beatmap}; use super::*; #[test] fn empty() { - let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted(); - - let mut gradual = TaikoGradualDifficulty::new(Difficulty::new(), &converted); - + let map = Beatmap::from_bytes(&[]).unwrap(); + let mut gradual = TaikoGradualDifficulty::new(Difficulty::new(), &map).unwrap(); assert!(gradual.next().is_none()); } #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/1028484.osu") - .unwrap() - .unchecked_into_converted(); + let map = Beatmap::from_path("./resources/1028484.osu").unwrap(); let difficulty = Difficulty::new(); - let mut gradual = TaikoGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_2nd = TaikoGradualDifficulty::new(difficulty.clone(), &converted); - let mut gradual_3rd = TaikoGradualDifficulty::new(difficulty.clone(), &converted); + let mut gradual = TaikoGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = TaikoGradualDifficulty::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = TaikoGradualDifficulty::new(difficulty.clone(), &map).unwrap(); - let hit_objects_len = converted.hit_objects.len(); + let hit_objects_len = map.hit_objects.len(); - let n_hits = converted - .hit_objects - .iter() - .filter(|h| h.is_circle()) - .count(); + let n_hits = map.hit_objects.iter().filter(|h| h.is_circle()).count(); for i in 1.. { let Some(next_gradual) = gradual.next() else { @@ -320,8 +311,8 @@ mod tests { let expected = difficulty .clone() .passed_objects(i as u32) - .with_mode() - .calculate(&converted); + .calculate_for_mode::(&map) + .unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/taiko/difficulty/mod.rs b/src/taiko/difficulty/mod.rs index 26299a5e..7f41db18 100644 --- a/src/taiko/difficulty/mod.rs +++ b/src/taiko/difficulty/mod.rs @@ -1,8 +1,10 @@ use std::cmp; +use rosu_map::section::general::GameMode; + use crate::{ any::difficulty::skills::Skill, - model::beatmap::HitWindows, + model::{beatmap::HitWindows, mode::ConvertError}, taiko::{ difficulty::{ color::preprocessor::ColorDifficultyPreprocessor, @@ -10,12 +12,12 @@ use crate::{ }, object::TaikoObject, }, - Difficulty, + Beatmap, Difficulty, }; use self::skills::{color::Color, rhythm::Rhythm, stamina::Stamina, TaikoSkills}; -use super::{attributes::TaikoDifficultyAttributes, convert::TaikoBeatmap}; +use super::attributes::TaikoDifficultyAttributes; mod color; pub mod gradual; @@ -30,27 +32,29 @@ const STAMINA_SKILL_MULTIPLIER: f64 = 0.375 * DIFFICULTY_MULTIPLIER; pub fn difficulty( difficulty: &Difficulty, - converted: &TaikoBeatmap<'_>, -) -> TaikoDifficultyAttributes { + map: &Beatmap, +) -> Result { + let map = map.convert_ref(GameMode::Taiko, difficulty.get_mods())?; + let HitWindows { od_great, od_ok, ar: _, - } = converted.attributes().difficulty(difficulty).hit_windows(); + } = map.attributes().difficulty(difficulty).hit_windows(); - let DifficultyValues { skills, max_combo } = DifficultyValues::calculate(difficulty, converted); + let DifficultyValues { skills, max_combo } = DifficultyValues::calculate(difficulty, &map); let mut attrs = TaikoDifficultyAttributes { great_hit_window: od_great, ok_hit_window: od_ok.unwrap_or(0.0), max_combo, - is_convert: converted.is_convert, + is_convert: map.is_convert, ..Default::default() }; DifficultyValues::eval(&mut attrs, skills); - attrs + Ok(attrs) } fn combined_difficulty_value(color: Color, rhythm: Rhythm, stamina: Stamina) -> f64 { @@ -118,7 +122,7 @@ pub struct DifficultyValues { } impl DifficultyValues { - pub fn calculate(difficulty: &Difficulty, converted: &TaikoBeatmap<'_>) -> Self { + pub fn calculate(difficulty: &Difficulty, converted: &Beatmap) -> Self { let take = difficulty.get_passed_objects(); let clock_rate = difficulty.get_clock_rate(); @@ -190,7 +194,7 @@ impl DifficultyValues { } pub fn create_difficulty_objects( - converted: &TaikoBeatmap<'_>, + converted: &Beatmap, take: u32, clock_rate: f64, max_combo: &mut u32, diff --git a/src/taiko/mod.rs b/src/taiko/mod.rs index 12fbf5d3..ed814374 100644 --- a/src/taiko/mod.rs +++ b/src/taiko/mod.rs @@ -1,14 +1,15 @@ +use rosu_map::section::general::GameMode; + use crate::{ model::{ beatmap::Beatmap, - mode::{ConvertStatus, IGameMode}, + mode::{ConvertError, IGameMode}, }, Difficulty, }; pub use self::{ attributes::{TaikoDifficultyAttributes, TaikoPerformanceAttributes}, - convert::TaikoBeatmap, difficulty::gradual::TaikoGradualDifficulty, performance::{gradual::TaikoGradualPerformance, TaikoPerformance}, score_state::TaikoScoreState, @@ -28,6 +29,13 @@ mod strains; /// [`GameMode::Taiko`]: rosu_map::section::general::GameMode::Taiko pub struct Taiko; +impl Taiko { + pub fn convert(map: &mut Beatmap) { + debug_assert!(!map.is_convert && map.mode == GameMode::Osu); + convert::convert(map); + } +} + impl IGameMode for Taiko { type DifficultyAttributes = TaikoDifficultyAttributes; type Strains = TaikoStrains; @@ -35,40 +43,32 @@ impl IGameMode for Taiko { type GradualDifficulty = TaikoGradualDifficulty; type GradualPerformance = TaikoGradualPerformance; - fn check_convert(map: &Beatmap) -> ConvertStatus { - convert::check_convert(map) - } - - fn try_convert(map: &mut Beatmap) -> ConvertStatus { - convert::try_convert(map) - } - fn difficulty( difficulty: &Difficulty, - converted: &TaikoBeatmap<'_>, - ) -> Self::DifficultyAttributes { - difficulty::difficulty(difficulty, converted) + map: &Beatmap, + ) -> Result { + difficulty::difficulty(difficulty, map) } - fn strains(difficulty: &Difficulty, converted: &TaikoBeatmap<'_>) -> Self::Strains { - strains::strains(difficulty, converted) + fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + strains::strains(difficulty, map) } - fn performance(map: TaikoBeatmap<'_>) -> Self::Performance<'_> { + fn performance(map: &Beatmap) -> Self::Performance<'_> { TaikoPerformance::new(map) } fn gradual_difficulty( difficulty: Difficulty, - map: &TaikoBeatmap<'_>, - ) -> Self::GradualDifficulty { + map: &Beatmap, + ) -> Result { TaikoGradualDifficulty::new(difficulty, map) } fn gradual_performance( difficulty: Difficulty, - map: &TaikoBeatmap<'_>, - ) -> Self::GradualPerformance { + map: &Beatmap, + ) -> Result { TaikoGradualPerformance::new(difficulty, map) } } diff --git a/src/taiko/performance/gradual.rs b/src/taiko/performance/gradual.rs index 263d389c..62396846 100644 --- a/src/taiko/performance/gradual.rs +++ b/src/taiko/performance/gradual.rs @@ -1,6 +1,7 @@ use crate::{ - taiko::{difficulty::gradual::TaikoGradualDifficulty, TaikoBeatmap, TaikoScoreState}, - Difficulty, + model::mode::ConvertError, + taiko::{difficulty::gradual::TaikoGradualDifficulty, TaikoScoreState}, + Beatmap, Difficulty, }; use super::TaikoPerformanceAttributes; @@ -84,10 +85,10 @@ pub struct TaikoGradualPerformance { impl TaikoGradualPerformance { /// Create a new gradual performance calculator for osu!taiko maps. - pub fn new(difficulty: Difficulty, converted: &TaikoBeatmap<'_>) -> Self { - let difficulty = TaikoGradualDifficulty::new(difficulty, converted); + pub fn new(difficulty: Difficulty, map: &Beatmap) -> Result { + let difficulty = TaikoGradualDifficulty::new(difficulty, map)?; - Self { difficulty } + Ok(Self { difficulty }) } /// Process the next hit object and calculate the performance attributes @@ -107,6 +108,7 @@ impl TaikoGradualPerformance { /// /// Note that the count is zero-indexed, so `n=0` will process 1 object, /// `n=1` will process 2, and so on. + #[allow(clippy::missing_panics_doc)] pub fn nth(&mut self, state: TaikoScoreState, n: usize) -> Option { let performance = self .difficulty @@ -115,7 +117,8 @@ impl TaikoGradualPerformance { .state(state) .difficulty(self.difficulty.difficulty.clone()) .passed_objects(self.difficulty.idx as u32) - .calculate(); + .calculate() + .expect("no conversion required"); Some(performance) } @@ -135,25 +138,19 @@ mod tests { #[test] fn next_and_nth() { - let converted = Beatmap::from_path("./resources/1028484.osu") - .unwrap() - .unchecked_into_converted(); + let map = Beatmap::from_path("./resources/1028484.osu").unwrap(); let difficulty = Difficulty::new().mods(88); // HDHRDT - let mut gradual = TaikoGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_2nd = TaikoGradualPerformance::new(difficulty.clone(), &converted); - let mut gradual_3rd = TaikoGradualPerformance::new(difficulty.clone(), &converted); + let mut gradual = TaikoGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_2nd = TaikoGradualPerformance::new(difficulty.clone(), &map).unwrap(); + let mut gradual_3rd = TaikoGradualPerformance::new(difficulty.clone(), &map).unwrap(); let mut state = TaikoScoreState::default(); - let hit_objects_len = converted.hit_objects.len(); + let hit_objects_len = map.hit_objects.len(); - let n_hits = converted - .hit_objects - .iter() - .filter(|h| h.is_circle()) - .count(); + let n_hits = map.hit_objects.iter().filter(|h| h.is_circle()).count(); for i in 1.. { state.misses += 1; @@ -175,15 +172,15 @@ mod tests { assert_eq!(next_gradual, next_gradual_3rd); } - let mut regular_calc = TaikoPerformance::new(converted.as_owned()) + let mut regular_calc = TaikoPerformance::new(&map) .difficulty(difficulty.clone()) .passed_objects(i as u32) .state(state); - let regular_state = regular_calc.generate_state(); + let regular_state = regular_calc.generate_state().unwrap(); assert_eq!(state, regular_state); - let expected = regular_calc.calculate(); + let expected = regular_calc.calculate().unwrap(); assert_eq!(next_gradual, expected); } diff --git a/src/taiko/performance/mod.rs b/src/taiko/performance/mod.rs index 39a76bcd..9dc43eca 100644 --- a/src/taiko/performance/mod.rs +++ b/src/taiko/performance/mod.rs @@ -1,8 +1,10 @@ use std::cmp; +use rosu_map::section::general::GameMode; + use crate::{ any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance}, - model::mods::GameMods, + model::{mode::ConvertError, mods::GameMods}, osu::OsuPerformance, util::{map_or_attrs::MapOrAttrs, special_functions}, Performance, @@ -36,7 +38,7 @@ impl<'map> TaikoPerformance<'map> { /// The argument `map_or_attrs` must be either /// - previously calculated attributes ([`TaikoDifficultyAttributes`] /// or [`TaikoPerformanceAttributes`]) - /// - a beatmap ([`TaikoBeatmap<'map>`]) + /// - a [`Beatmap`] (by reference or value) /// /// If a map is given, difficulty attributes will need to be calculated /// internally which is a costly operation. Hence, passing attributes @@ -46,20 +48,19 @@ impl<'map> TaikoPerformance<'map> { /// have been calculated for the same map and [`Difficulty`] settings. /// Otherwise, the final attributes will be incorrect. /// - /// [`TaikoBeatmap<'map>`]: crate::taiko::TaikoBeatmap + /// [`Beatmap`]: crate::model::beatmap::Beatmap pub fn new(map_or_attrs: impl IntoModePerformance<'map, Taiko>) -> Self { map_or_attrs.into_performance() } /// Try to create a new performance calculator for osu!taiko maps. /// - /// Returns `None` if `map_or_attrs` does not belong to osu!taiko e.g. - /// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`] - /// of a different mode. + /// Returns `None` if `map_or_attrs` does not belong to osu!taiko i.e. + /// a [`DifficultyAttributes`] or [`PerformanceAttributes`] of a different + /// mode. /// /// See [`TaikoPerformance::new`] for more information. /// - /// [`Converted`]: crate::model::beatmap::Converted /// [`DifficultyAttributes`]: crate::any::DifficultyAttributes /// [`PerformanceAttributes`]: crate::any::PerformanceAttributes pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option { @@ -214,10 +215,10 @@ impl<'map> TaikoPerformance<'map> { } /// Create the [`TaikoScoreState`] that will be used for performance calculation. - pub fn generate_state(&mut self) -> TaikoScoreState { + pub fn generate_state(&mut self) -> Result { let attrs = match self.map_or_attrs { MapOrAttrs::Map(ref map) => { - let attrs = self.difficulty.with_mode().calculate(map); + let attrs = self.difficulty.calculate_for_mode::(map)?; self.map_or_attrs.insert_attrs(attrs) } @@ -297,21 +298,21 @@ impl<'map> TaikoPerformance<'map> { self.n100 = Some(n100); self.misses = Some(misses); - TaikoScoreState { + Ok(TaikoScoreState { max_combo, n300, n100, misses, - } + }) } /// Calculate all performance related values, including pp and stars. - pub fn calculate(mut self) -> TaikoPerformanceAttributes { - let state = self.generate_state(); + pub fn calculate(mut self) -> Result { + let state = self.generate_state()?; let attrs = match self.map_or_attrs { - MapOrAttrs::Map(ref map) => self.difficulty.with_mode().calculate(map), MapOrAttrs::Attrs(attrs) => attrs, + MapOrAttrs::Map(ref map) => self.difficulty.calculate_for_mode::(map)?, }; let inner = TaikoPerformanceInner { @@ -320,7 +321,7 @@ impl<'map> TaikoPerformance<'map> { attrs, }; - inner.calculate() + Ok(inner.calculate()) } pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Taiko>) -> Self { @@ -346,14 +347,12 @@ impl<'map> TryFrom> for TaikoPerformance<'map> { /// if it was constructed through attributes or /// [`OsuPerformance::generate_state`] was called. fn try_from(mut osu: OsuPerformance<'map>) -> Result { - let MapOrAttrs::Map(converted) = osu.map_or_attrs else { - return Err(osu); - }; + let mods = osu.difficulty.get_mods(); - let map = match converted.try_convert() { + let map = match OsuPerformance::try_convert_map(osu.map_or_attrs, GameMode::Taiko, mods) { Ok(map) => map, - Err(map) => { - osu.map_or_attrs = MapOrAttrs::Map(map); + Err(map_or_attrs) => { + osu.map_or_attrs = map_or_attrs; return Err(osu); } @@ -594,7 +593,7 @@ mod test { use crate::{ any::{DifficultyAttributes, PerformanceAttributes}, - osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes}, + osu::{OsuDifficultyAttributes, OsuPerformanceAttributes}, Beatmap, }; @@ -611,8 +610,8 @@ mod test { fn attrs() -> TaikoDifficultyAttributes { ATTRS .get_or_init(|| { - let converted = beatmap().unchecked_into_converted::(); - let attrs = Difficulty::new().with_mode().calculate(&converted); + let map = beatmap(); + let attrs = Difficulty::new().calculate_for_mode::(&map).unwrap(); assert_eq!(MAX_COMBO, attrs.max_combo); @@ -715,8 +714,8 @@ mod test { state = state.misses(misses); } - let first = state.generate_state(); - let state = state.generate_state(); + let first = state.generate_state().unwrap(); + let state = state.generate_state().unwrap(); assert_eq!(first, state); let mut expected = brute_force_best( @@ -739,7 +738,8 @@ mod test { .n300(150) .misses(2) .hitresult_priority(HitResultPriority::BestCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = TaikoScoreState { max_combo: 100, @@ -757,7 +757,8 @@ mod test { .combo(100) .misses(2) .hitresult_priority(HitResultPriority::BestCase) - .generate_state(); + .generate_state() + .unwrap(); let expected = TaikoScoreState { max_combo: 100, @@ -772,12 +773,11 @@ mod test { #[test] fn create() { let mut map = beatmap(); - let converted = map.unchecked_as_converted(); let _ = TaikoPerformance::new(TaikoDifficultyAttributes::default()); let _ = TaikoPerformance::new(TaikoPerformanceAttributes::default()); - let _ = TaikoPerformance::new(&converted); - let _ = TaikoPerformance::new(converted.as_owned()); + let _ = TaikoPerformance::new(&map); + let _ = TaikoPerformance::new(map.clone()); let _ = TaikoPerformance::try_new(TaikoDifficultyAttributes::default()).unwrap(); let _ = TaikoPerformance::try_new(TaikoPerformanceAttributes::default()).unwrap(); @@ -789,19 +789,18 @@ mod test { TaikoPerformanceAttributes::default(), )) .unwrap(); - let _ = TaikoPerformance::try_new(&converted).unwrap(); - let _ = TaikoPerformance::try_new(converted.as_owned()).unwrap(); + let _ = TaikoPerformance::try_new(&map).unwrap(); + let _ = TaikoPerformance::try_new(map.clone()).unwrap(); let _ = TaikoPerformance::from(TaikoDifficultyAttributes::default()); let _ = TaikoPerformance::from(TaikoPerformanceAttributes::default()); - let _ = TaikoPerformance::from(&converted); - let _ = TaikoPerformance::from(converted); + let _ = TaikoPerformance::from(&map); + let _ = TaikoPerformance::from(map.clone()); let _ = TaikoDifficultyAttributes::default().performance(); let _ = TaikoPerformanceAttributes::default().performance(); - map.mode = GameMode::Osu; - let converted = map.unchecked_as_converted::(); + assert!(map.convert_mut(GameMode::Osu, &Default::default()).is_err()); assert!(TaikoPerformance::try_new(OsuDifficultyAttributes::default()).is_none()); assert!(TaikoPerformance::try_new(OsuPerformanceAttributes::default()).is_none()); @@ -813,7 +812,5 @@ mod test { OsuPerformanceAttributes::default() )) .is_none()); - assert!(TaikoPerformance::try_new(&converted).is_none()); - assert!(TaikoPerformance::try_new(converted).is_none()); } } diff --git a/src/taiko/strains.rs b/src/taiko/strains.rs index 9dd2e287..261b3aa1 100644 --- a/src/taiko/strains.rs +++ b/src/taiko/strains.rs @@ -1,6 +1,6 @@ -use crate::{taiko::difficulty::DifficultyValues, Difficulty}; +use rosu_map::section::general::GameMode; -use super::convert::TaikoBeatmap; +use crate::{model::mode::ConvertError, taiko::difficulty::DifficultyValues, Beatmap, Difficulty}; /// The result of calculating the strains on a osu!taiko map. /// @@ -20,12 +20,13 @@ impl TaikoStrains { pub const SECTION_LEN: f64 = 400.0; } -pub fn strains(difficulty: &Difficulty, converted: &TaikoBeatmap<'_>) -> TaikoStrains { - let values = DifficultyValues::calculate(difficulty, converted); +pub fn strains(difficulty: &Difficulty, map: &Beatmap) -> Result { + let map = map.convert_ref(GameMode::Taiko, difficulty.get_mods())?; + let values = DifficultyValues::calculate(difficulty, &map); - TaikoStrains { + Ok(TaikoStrains { color: values.skills.color.get_curr_strain_peaks().into_vec(), rhythm: values.skills.rhythm.get_curr_strain_peaks().into_vec(), stamina: values.skills.stamina.get_curr_strain_peaks().into_vec(), - } + }) } diff --git a/src/util/generic_fmt.rs b/src/util/generic_fmt.rs deleted file mode 100644 index 06ee73a3..00000000 --- a/src/util/generic_fmt.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::{ - any, - fmt::{Debug, Formatter, Result as FmtResult}, - marker::PhantomData, -}; - -pub struct GenericFormatter(PhantomData); - -impl GenericFormatter { - pub const fn new() -> Self { - Self(PhantomData) - } -} - -impl Debug for GenericFormatter { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - fn fmt_stripped(full_type_name: &str, f: &mut Formatter<'_>) -> FmtResult { - // Strip fully qualified syntax - if let Some(position) = full_type_name.rfind("::") { - if let Some(type_name) = full_type_name.get(position + 2..) { - f.write_str(type_name)?; - } - } - - Ok(()) - } - - fmt_stripped(any::type_name::(), f) - } -} diff --git a/src/util/map_or_attrs.rs b/src/util/map_or_attrs.rs index d74a3339..a5f9e34b 100644 --- a/src/util/map_or_attrs.rs +++ b/src/util/map_or_attrs.rs @@ -1,9 +1,12 @@ -use std::fmt::{Debug, Formatter, Result as FmtResult}; +use std::{ + borrow::Cow, + fmt::{Debug, Formatter, Result as FmtResult}, +}; -use crate::model::{beatmap::Converted, mode::IGameMode}; +use crate::{model::mode::IGameMode, Beatmap}; pub enum MapOrAttrs<'map, M: IGameMode> { - Map(Converted<'map, M>), + Map(Cow<'map, Beatmap>), Attrs(M::DifficultyAttributes), } @@ -69,9 +72,15 @@ where } } -impl<'map, M: IGameMode> From> for MapOrAttrs<'map, M> { - fn from(converted: Converted<'map, M>) -> Self { - Self::Map(converted) +impl<'map, M: IGameMode> From<&'map Beatmap> for MapOrAttrs<'map, M> { + fn from(map: &'map Beatmap) -> Self { + Self::Map(Cow::Borrowed(map)) + } +} + +impl From for MapOrAttrs<'_, M> { + fn from(map: Beatmap) -> Self { + Self::Map(Cow::Owned(map)) } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 1ff0fd8a..33dc0edc 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,4 @@ pub mod float_ext; -pub mod generic_fmt; pub mod limited_queue; pub mod map_or_attrs; pub mod random; diff --git a/tests/decode.rs b/tests/decode.rs index c37459c9..b4762f97 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -1,4 +1,4 @@ -use rosu_pp::{catch::Catch, mania::Mania, model::mode::GameMode, osu::Osu, taiko::Taiko, Beatmap}; +use rosu_pp::{model::mode::GameMode, Beatmap, GameMods}; use crate::common::assert_eq_float; @@ -92,23 +92,23 @@ fn mania() { #[test] fn empty_osu() { let map = Beatmap::from_bytes(&[]).unwrap(); - let _ = map.unchecked_as_converted::(); + let _ = map.convert(GameMode::Osu, &GameMods::default()); } #[test] fn empty_taiko() { let map = Beatmap::from_bytes(&[]).unwrap(); - let _ = map.unchecked_as_converted::(); + let _ = map.convert(GameMode::Taiko, &GameMods::default()); } #[test] fn empty_catch() { let map = Beatmap::from_bytes(&[]).unwrap(); - let _ = map.unchecked_as_converted::(); + let _ = map.convert(GameMode::Catch, &GameMods::default()); } #[test] fn empty_mania() { let map = Beatmap::from_bytes(&[]).unwrap(); - let _ = map.unchecked_as_converted::(); + let _ = map.convert(GameMode::Mania, &GameMods::default()); } diff --git a/tests/difficulty.rs b/tests/difficulty.rs index 00308040..6a48f324 100644 --- a/tests/difficulty.rs +++ b/tests/difficulty.rs @@ -18,14 +18,17 @@ macro_rules! test_cases { $( $key:ident: $value:literal $( , )? )* } $( ; )? )* } ) => { - let map = Beatmap::from_path(common::$path) - .unwrap() - .unchecked_into_converted::<$mode>(); + let map = Beatmap::from_path(common::$path).unwrap(); $( let mods = 0 $( + $mods )*; let expected = test_cases!(@$mode { $( $key: $value, )* }); - let actual = Difficulty::new().mods(mods).with_mode().calculate(&map); + + let actual = Difficulty::new() + .mods(mods) + .calculate_for_mode::<$mode>(&map) + .unwrap(); + run(&actual, &expected, mods); )* }; diff --git a/tests/performance.rs b/tests/performance.rs index 50404bc0..520c726c 100644 --- a/tests/performance.rs +++ b/tests/performance.rs @@ -18,14 +18,12 @@ macro_rules! test_cases { $( $key:ident: $value:expr $( , )? )* } ;)* } ) => { - let map = Beatmap::from_path(common::$path) - .unwrap() - .unchecked_into_converted(); + let map = Beatmap::from_path(common::$path).unwrap(); $( let mods = 0 $( + $mods )*; let (calc, expected) = test_cases!(@$mode { map, $( $key: $value, )* }); - let actual = calc.mods(mods).calculate(); + let actual = calc.mods(mods).calculate().unwrap(); run(&actual, &expected, mods); )* }; @@ -39,7 +37,7 @@ macro_rules! test_cases { effective_miss_count: $effective_miss_count:expr, }) => { ( - OsuPerformance::from($map.as_owned()).lazer(true), + OsuPerformance::from(&$map).lazer(true), OsuPerformanceAttributes { pp: $pp, pp_acc: $pp_acc, @@ -60,7 +58,7 @@ macro_rules! test_cases { estimated_unstable_rate: $estimated_unstable_rate:expr, }) => { ( - TaikoPerformance::from($map.as_owned()), + TaikoPerformance::from(&$map), TaikoPerformanceAttributes { pp: $pp, pp_acc: $pp_acc, @@ -76,7 +74,7 @@ macro_rules! test_cases { pp: $pp:expr, }) => { ( - CatchPerformance::from($map.as_owned()), + CatchPerformance::from(&$map), CatchPerformanceAttributes { pp: $pp, ..Default::default() @@ -89,7 +87,7 @@ macro_rules! test_cases { pp_difficulty: $pp_difficulty:expr, }) => { ( - ManiaPerformance::from($map.as_owned()), + ManiaPerformance::from(&$map), ManiaPerformanceAttributes { pp: $pp, pp_difficulty: $pp_difficulty, From 420e4c0697926168ca2af01723625b51de8ad035 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 18 Nov 2024 15:51:25 +0100 Subject: [PATCH 2/3] fix: consider mods for mania converts --- src/mania/convert/mod.rs | 8 +++-- src/model/mods.rs | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/mania/convert/mod.rs b/src/mania/convert/mod.rs index ca67073b..7c4daa46 100644 --- a/src/mania/convert/mod.rs +++ b/src/mania/convert/mod.rs @@ -31,7 +31,7 @@ pub fn convert(map: &mut Beatmap, mods: &GameMods) { let mut random = Random::new(seed); - map.cs = target_columns(map); + map.cs = target_columns(map, mods); let mut prev_note_times = LimitedQueue::::new(); let mut density = f64::from(i32::MAX); @@ -160,7 +160,11 @@ impl Default for PrevValues { } } -fn target_columns(map: &Beatmap) -> f32 { +fn target_columns(map: &Beatmap, mods: &GameMods) -> f32 { + if let Some(keys) = mods.mania_keys() { + return keys; + } + let rounded_cs = map.cs.round_ties_even(); let rounded_od = map.od.round_ties_even(); diff --git a/src/model/mods.rs b/src/model/mods.rs index 596e0d42..e5c0d30e 100644 --- a/src/model/mods.rs +++ b/src/model/mods.rs @@ -149,6 +149,84 @@ impl GameMods { } } } + + pub(crate) fn mania_keys(&self) -> Option { + match self.inner { + GameModsInner::Lazer(ref mods) => { + if mods.contains_intermode(GameModIntermode::OneKey) { + Some(1.0) + } else if mods.contains_intermode(GameModIntermode::TwoKeys) { + Some(2.0) + } else if mods.contains_intermode(GameModIntermode::ThreeKeys) { + Some(3.0) + } else if mods.contains_intermode(GameModIntermode::FourKeys) { + Some(4.0) + } else if mods.contains_intermode(GameModIntermode::FiveKeys) { + Some(5.0) + } else if mods.contains_intermode(GameModIntermode::SixKeys) { + Some(6.0) + } else if mods.contains_intermode(GameModIntermode::SevenKeys) { + Some(7.0) + } else if mods.contains_intermode(GameModIntermode::EightKeys) { + Some(8.0) + } else if mods.contains_intermode(GameModIntermode::NineKeys) { + Some(9.0) + } else if mods.contains_intermode(GameModIntermode::TenKeys) { + Some(10.0) + } else { + None + } + } + GameModsInner::Intermode(ref mods) => { + if mods.contains(GameModIntermode::OneKey) { + Some(1.0) + } else if mods.contains(GameModIntermode::TwoKeys) { + Some(2.0) + } else if mods.contains(GameModIntermode::ThreeKeys) { + Some(3.0) + } else if mods.contains(GameModIntermode::FourKeys) { + Some(4.0) + } else if mods.contains(GameModIntermode::FiveKeys) { + Some(5.0) + } else if mods.contains(GameModIntermode::SixKeys) { + Some(6.0) + } else if mods.contains(GameModIntermode::SevenKeys) { + Some(7.0) + } else if mods.contains(GameModIntermode::EightKeys) { + Some(8.0) + } else if mods.contains(GameModIntermode::NineKeys) { + Some(9.0) + } else if mods.contains(GameModIntermode::TenKeys) { + Some(10.0) + } else { + None + } + } + GameModsInner::Legacy(ref mods) => { + if mods.contains(GameModsLegacy::Key1) { + Some(1.0) + } else if mods.contains(GameModsLegacy::Key2) { + Some(2.0) + } else if mods.contains(GameModsLegacy::Key3) { + Some(3.0) + } else if mods.contains(GameModsLegacy::Key4) { + Some(4.0) + } else if mods.contains(GameModsLegacy::Key5) { + Some(5.0) + } else if mods.contains(GameModsLegacy::Key6) { + Some(6.0) + } else if mods.contains(GameModsLegacy::Key7) { + Some(7.0) + } else if mods.contains(GameModsLegacy::Key8) { + Some(8.0) + } else if mods.contains(GameModsLegacy::Key9) { + Some(9.0) + } else { + None + } + } + } + } } macro_rules! impl_map_attr { From dae2eb49b0da6516a38bee1f8eb8637eab38687b Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 18 Nov 2024 16:20:41 +0100 Subject: [PATCH 3/3] fix: doc tests --- src/catch/difficulty/gradual.rs | 6 ++---- src/catch/performance/gradual.rs | 6 ++---- src/mania/difficulty/gradual.rs | 6 ++---- src/mania/performance/gradual.rs | 6 ++---- src/osu/difficulty/gradual.rs | 6 ++---- src/osu/performance/gradual.rs | 6 ++---- src/taiko/difficulty/gradual.rs | 6 ++---- src/taiko/performance/gradual.rs | 6 ++---- src/util/sync.rs | 8 ++++---- 9 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/catch/difficulty/gradual.rs b/src/catch/difficulty/gradual.rs index c5223020..4d17e52f 100644 --- a/src/catch/difficulty/gradual.rs +++ b/src/catch/difficulty/gradual.rs @@ -37,12 +37,10 @@ use super::{ /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::catch::{Catch, CatchGradualDifficulty}; /// -/// let converted = Beatmap::from_path("./resources/2118524.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/2118524.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut iter = CatchGradualDifficulty::new(difficulty, &converted); +/// let mut iter = CatchGradualDifficulty::new(difficulty, &map).unwrap(); /// /// // the difficulty of the map after the first hit object /// let attrs1 = iter.next(); diff --git a/src/catch/performance/gradual.rs b/src/catch/performance/gradual.rs index 801c9422..e74c8611 100644 --- a/src/catch/performance/gradual.rs +++ b/src/catch/performance/gradual.rs @@ -25,12 +25,10 @@ use crate::{ /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::catch::{Catch, CatchGradualPerformance, CatchScoreState}; /// -/// let converted = Beatmap::from_path("./resources/2118524.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/2118524.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut gradual = CatchGradualPerformance::new(difficulty, &converted); +/// let mut gradual = CatchGradualPerformance::new(difficulty, &map).unwrap(); /// let mut state = CatchScoreState::new(); // empty state, everything is on 0. /// /// // The first 10 hitresults are only fruits diff --git a/src/mania/difficulty/gradual.rs b/src/mania/difficulty/gradual.rs index 23ee56dc..ebe40378 100644 --- a/src/mania/difficulty/gradual.rs +++ b/src/mania/difficulty/gradual.rs @@ -29,12 +29,10 @@ use super::{ /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::mania::ManiaGradualDifficulty; /// -/// let converted = Beatmap::from_path("./resources/1638954.osu") -/// .unwrap() -/// .unchecked_into_converted(); +/// let map = Beatmap::from_path("./resources/1638954.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut iter = ManiaGradualDifficulty::new(difficulty, &converted); +/// let mut iter = ManiaGradualDifficulty::new(difficulty, &map).unwrap(); /// /// // the difficulty of the map after the first hit object /// let attrs1 = iter.next(); diff --git a/src/mania/performance/gradual.rs b/src/mania/performance/gradual.rs index b797da49..7fc946aa 100644 --- a/src/mania/performance/gradual.rs +++ b/src/mania/performance/gradual.rs @@ -20,12 +20,10 @@ use super::{ManiaPerformanceAttributes, ManiaScoreState}; /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::mania::{Mania, ManiaGradualPerformance, ManiaScoreState}; /// -/// let converted = Beatmap::from_path("./resources/1638954.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/1638954.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut gradual = ManiaGradualPerformance::new(difficulty, &converted); +/// let mut gradual = ManiaGradualPerformance::new(difficulty, &map).unwrap(); /// let mut state = ManiaScoreState::new(); // empty state, everything is on 0. /// /// // The first 10 hitresults are 320s diff --git a/src/osu/difficulty/gradual.rs b/src/osu/difficulty/gradual.rs index f431cb49..74935ae9 100644 --- a/src/osu/difficulty/gradual.rs +++ b/src/osu/difficulty/gradual.rs @@ -35,12 +35,10 @@ use super::{ /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::osu::{Osu, OsuGradualDifficulty}; /// -/// let converted = Beatmap::from_path("./resources/2785319.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut iter = OsuGradualDifficulty::new(difficulty, &converted); +/// let mut iter = OsuGradualDifficulty::new(difficulty, &map).unwrap(); /// /// // the difficulty of the map after the first hit object /// let attrs1 = iter.next(); diff --git a/src/osu/performance/gradual.rs b/src/osu/performance/gradual.rs index fb839abd..b888b244 100644 --- a/src/osu/performance/gradual.rs +++ b/src/osu/performance/gradual.rs @@ -20,12 +20,10 @@ use super::{OsuPerformanceAttributes, OsuScoreState}; /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::osu::{Osu, OsuGradualPerformance, OsuScoreState}; /// -/// let converted = Beatmap::from_path("./resources/2785319.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut gradual = OsuGradualPerformance::new(difficulty, &converted); +/// let mut gradual = OsuGradualPerformance::new(difficulty, &map).unwrap(); /// let mut state = OsuScoreState::new(); // empty state, everything is on 0. /// /// // The first 10 hits are 300s and there are no sliders for additional combo diff --git a/src/taiko/difficulty/gradual.rs b/src/taiko/difficulty/gradual.rs index 04dca492..3c76b8f2 100644 --- a/src/taiko/difficulty/gradual.rs +++ b/src/taiko/difficulty/gradual.rs @@ -30,12 +30,10 @@ use super::{ /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::taiko::{Taiko, TaikoGradualDifficulty}; /// -/// let converted = Beatmap::from_path("./resources/1028484.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/1028484.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut iter = TaikoGradualDifficulty::new(difficulty, &converted); +/// let mut iter = TaikoGradualDifficulty::new(difficulty, &map).unwrap(); /// /// // the difficulty of the map after the first hit object /// let attrs1 = iter.next(); diff --git a/src/taiko/performance/gradual.rs b/src/taiko/performance/gradual.rs index 62396846..fb213a45 100644 --- a/src/taiko/performance/gradual.rs +++ b/src/taiko/performance/gradual.rs @@ -24,12 +24,10 @@ use super::TaikoPerformanceAttributes; /// use rosu_pp::{Beatmap, Difficulty}; /// use rosu_pp::taiko::{Taiko, TaikoGradualPerformance, TaikoScoreState}; /// -/// let converted = Beatmap::from_path("./resources/1028484.osu") -/// .unwrap() -/// .unchecked_into_converted::(); +/// let map = Beatmap::from_path("./resources/1028484.osu").unwrap(); /// /// let difficulty = Difficulty::new().mods(64); // DT -/// let mut gradual = TaikoGradualPerformance::new(difficulty, &converted); +/// let mut gradual = TaikoGradualPerformance::new(difficulty, &map).unwrap(); /// let mut state = TaikoScoreState::new(); // empty state, everything is on 0. /// /// // The first 10 hitresults are 300s diff --git a/src/util/sync.rs b/src/util/sync.rs index 3aad15bd..afed7037 100644 --- a/src/util/sync.rs +++ b/src/util/sync.rs @@ -139,9 +139,9 @@ impl fmt::Debug for Weak { /// ```compile_fail /// use rosu_pp::{taiko::TaikoGradualDifficulty, Beatmap, Difficulty}; /// -/// let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted(); +/// let map = Beatmap::from_bytes(&[]).unwrap(); /// let difficulty = Difficulty::new(); -/// let mut gradual = TaikoGradualDifficulty::new(&difficulty, &converted); +/// let mut gradual = TaikoGradualDifficulty::new(&difficulty, &map).unwrap(); /// /// // Rc> cannot be shared across threads so compilation should fail /// std::thread::spawn(move || { let _ = gradual.next(); }); @@ -155,8 +155,8 @@ mod tests { fn share_gradual_taiko() { use crate::{taiko::TaikoGradualDifficulty, Beatmap, Difficulty}; - let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted(); - let mut gradual = TaikoGradualDifficulty::new(Difficulty::new(), &converted); + let map = Beatmap::from_bytes(&[]).unwrap(); + let mut gradual = TaikoGradualDifficulty::new(Difficulty::new(), &map).unwrap(); // Arc> *can* be shared across threads so this should compile std::thread::spawn(move || {