refactor: create performance calculators through traits (#33)
* create perf calc through IntoPerformance traits * added tests * adjusted documentation * fix doctest * simplify From impls
This commit is contained in:
@@ -34,7 +34,7 @@ let diff_attrs = rosu_pp::Difficulty::new()
|
||||
let stars = diff_attrs.stars();
|
||||
|
||||
// Calculate performance attributes
|
||||
let perf_attrs = rosu_pp::Performance::from_attributes(diff_attrs)
|
||||
let perf_attrs = rosu_pp::Performance::new(diff_attrs)
|
||||
// To speed up the calculation, we used the previous attributes.
|
||||
// **Note** that this should only be done if the map and all difficulty
|
||||
// settings stay the same, otherwise the final attributes will be incorrect!
|
||||
|
||||
+3
-47
@@ -5,7 +5,7 @@ use crate::{
|
||||
taiko::{TaikoDifficultyAttributes, TaikoPerformanceAttributes},
|
||||
};
|
||||
|
||||
use super::performance::Performance;
|
||||
use super::performance::{into::IntoPerformance, Performance};
|
||||
|
||||
/// The result of a difficulty calculation based on the mode.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -43,7 +43,7 @@ impl DifficultyAttributes {
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub fn performance<'a>(self) -> Performance<'a> {
|
||||
self.into()
|
||||
self.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ impl PerformanceAttributes {
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub fn performance<'a>(self) -> Performance<'a> {
|
||||
self.into()
|
||||
self.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,47 +112,3 @@ impl From<PerformanceAttributes> for DifficultyAttributes {
|
||||
attrs.difficulty_attributes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation.
|
||||
pub trait AttributeProvider {
|
||||
/// Provide the actual difficulty attributes.
|
||||
fn attributes(self) -> DifficultyAttributes;
|
||||
}
|
||||
|
||||
impl AttributeProvider for DifficultyAttributes {
|
||||
fn attributes(self) -> DifficultyAttributes {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributeProvider for PerformanceAttributes {
|
||||
fn attributes(self) -> DifficultyAttributes {
|
||||
match self {
|
||||
Self::Osu(attrs) => DifficultyAttributes::Osu(attrs.difficulty),
|
||||
Self::Taiko(attrs) => DifficultyAttributes::Taiko(attrs.difficulty),
|
||||
Self::Catch(attrs) => DifficultyAttributes::Catch(attrs.difficulty),
|
||||
Self::Mania(attrs) => DifficultyAttributes::Mania(attrs.difficulty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_attr_provider {
|
||||
( $mode:ident: $difficulty:ident, $performance:ident ) => {
|
||||
impl AttributeProvider for $difficulty {
|
||||
fn attributes(self) -> DifficultyAttributes {
|
||||
DifficultyAttributes::$mode(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributeProvider for $performance {
|
||||
fn attributes(self) -> DifficultyAttributes {
|
||||
DifficultyAttributes::$mode(self.difficulty)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_attr_provider!(Catch: CatchDifficultyAttributes, CatchPerformanceAttributes);
|
||||
impl_attr_provider!(Mania: ManiaDifficultyAttributes, ManiaPerformanceAttributes);
|
||||
impl_attr_provider!(Osu: OsuDifficultyAttributes, OsuPerformanceAttributes);
|
||||
impl_attr_provider!(Taiko: TaikoDifficultyAttributes, TaikoPerformanceAttributes);
|
||||
|
||||
+6
-2
@@ -1,10 +1,14 @@
|
||||
pub use self::{
|
||||
attributes::{AttributeProvider, DifficultyAttributes, PerformanceAttributes},
|
||||
attributes::{DifficultyAttributes, PerformanceAttributes},
|
||||
difficulty::{
|
||||
converted::ConvertedDifficulty, gradual::GradualDifficulty, inspect::InspectDifficulty,
|
||||
Difficulty, ModsDependent,
|
||||
},
|
||||
performance::{gradual::GradualPerformance, HitResultPriority, Performance},
|
||||
performance::{
|
||||
gradual::GradualPerformance,
|
||||
into::{IntoModePerformance, IntoPerformance},
|
||||
HitResultPriority, Performance,
|
||||
},
|
||||
score_state::ScoreState,
|
||||
strains::Strains,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
model::mode::IGameMode,
|
||||
Beatmap, Converted, Performance,
|
||||
};
|
||||
|
||||
/// Turning a type into the generic [`IGameMode`]'s performance calculator.
|
||||
pub trait IntoModePerformance<'map, M: IGameMode> {
|
||||
fn into_performance(self) -> M::Performance<'map>;
|
||||
}
|
||||
|
||||
/// Turning a type into a performance calculator of any mode.
|
||||
pub trait IntoPerformance<'a> {
|
||||
fn into_performance(self) -> Performance<'a>;
|
||||
}
|
||||
|
||||
macro_rules! impl_from_mode {
|
||||
(
|
||||
$(
|
||||
$module:ident {
|
||||
$mode:ident, $diff:ident, $perf:ident
|
||||
}
|
||||
,)*
|
||||
) => {
|
||||
$(
|
||||
macro_rules! mode {
|
||||
() => { crate::$module::$mode };
|
||||
}
|
||||
|
||||
impl<'map> IntoModePerformance<'map, mode!()> for Converted<'map, mode!()> {
|
||||
fn into_performance(self) -> <mode!() as IGameMode>::Performance<'map> {
|
||||
<mode!() as IGameMode>::Performance::from_map_or_attrs(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> IntoModePerformance<'map, mode!()> for &'map Converted<'_, mode!()> {
|
||||
fn into_performance(self) -> <mode!() as IGameMode>::Performance<'map> {
|
||||
<mode!() as IGameMode>::Performance::from_map_or_attrs(self.as_owned().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> IntoModePerformance<'map, mode!()> for crate::$module::$diff {
|
||||
fn into_performance(self) -> <mode!() as IGameMode>::Performance<'map> {
|
||||
<mode!() as IGameMode>::Performance::from_map_or_attrs(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> IntoModePerformance<'map, mode!()> for crate::$module::$perf {
|
||||
fn into_performance(self) -> <mode!() as IGameMode>::Performance<'map> {
|
||||
<mode!() as IGameMode>::Performance::from_map_or_attrs(self.difficulty.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> IntoPerformance<'map> for Converted<'map, mode!()> {
|
||||
fn into_performance(self) -> Performance<'map> {
|
||||
Performance::$mode(
|
||||
<Self as IntoModePerformance<'map, mode!()>>::into_performance(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> IntoPerformance<'map> for &'map Converted<'_, mode!()> {
|
||||
fn into_performance(self) -> Performance<'map> {
|
||||
Performance::$mode(
|
||||
<Self as IntoModePerformance<'map, mode!()>>::into_performance(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPerformance<'a> for crate::$module::$diff {
|
||||
fn into_performance(self) -> Performance<'a> {
|
||||
Performance::$mode(
|
||||
<Self as IntoModePerformance<'a, mode!()>>::into_performance(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPerformance<'a> for crate::$module::$perf {
|
||||
fn into_performance(self) -> Performance<'a> {
|
||||
Performance::$mode(
|
||||
<Self as IntoModePerformance<'a, mode!()>>::into_performance(self)
|
||||
)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_mode!(
|
||||
osu {
|
||||
Osu,
|
||||
OsuDifficultyAttributes,
|
||||
OsuPerformanceAttributes
|
||||
},
|
||||
taiko {
|
||||
Taiko,
|
||||
TaikoDifficultyAttributes,
|
||||
TaikoPerformanceAttributes
|
||||
},
|
||||
catch {
|
||||
Catch,
|
||||
CatchDifficultyAttributes,
|
||||
CatchPerformanceAttributes
|
||||
},
|
||||
mania {
|
||||
Mania,
|
||||
ManiaDifficultyAttributes,
|
||||
ManiaPerformanceAttributes
|
||||
},
|
||||
);
|
||||
|
||||
impl<'a> IntoPerformance<'a> for Beatmap {
|
||||
fn into_performance(self) -> Performance<'a> {
|
||||
map_to_performance(self.mode, Cow::Owned(self))
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPerformance<'a> for DifficultyAttributes {
|
||||
fn into_performance(self) -> Performance<'a> {
|
||||
match self {
|
||||
Self::Osu(attrs) => Performance::Osu(attrs.into()),
|
||||
Self::Taiko(attrs) => Performance::Taiko(attrs.into()),
|
||||
Self::Catch(attrs) => Performance::Catch(attrs.into()),
|
||||
Self::Mania(attrs) => Performance::Mania(attrs.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPerformance<'a> for PerformanceAttributes {
|
||||
fn into_performance(self) -> Performance<'a> {
|
||||
match self {
|
||||
Self::Osu(attrs) => Performance::Osu(attrs.difficulty.into()),
|
||||
Self::Taiko(attrs) => Performance::Taiko(attrs.difficulty.into()),
|
||||
Self::Catch(attrs) => Performance::Catch(attrs.difficulty.into()),
|
||||
Self::Mania(attrs) => Performance::Mania(attrs.difficulty.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
-79
@@ -1,23 +1,16 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::attributes::DifficultyAttributes,
|
||||
catch::{Catch, CatchPerformance},
|
||||
mania::{Mania, ManiaPerformance},
|
||||
model::beatmap::{Beatmap, Converted},
|
||||
osu::{Osu, OsuPerformance},
|
||||
taiko::{Taiko, TaikoPerformance},
|
||||
catch::CatchPerformance, mania::ManiaPerformance, osu::OsuPerformance, taiko::TaikoPerformance,
|
||||
Difficulty,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attributes::{AttributeProvider, PerformanceAttributes},
|
||||
score_state::ScoreState,
|
||||
};
|
||||
use self::into::IntoPerformance;
|
||||
|
||||
use super::{attributes::PerformanceAttributes, score_state::ScoreState};
|
||||
|
||||
pub mod gradual;
|
||||
pub mod into;
|
||||
|
||||
/// Performance calculator on maps of any mode.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -30,43 +23,29 @@ pub enum Performance<'map> {
|
||||
}
|
||||
|
||||
impl<'map> Performance<'map> {
|
||||
/// Create a new performance calculator for maps of any mode.
|
||||
/// Create a new performance calculator for any mode.
|
||||
///
|
||||
/// Note that creating [`Performance`] this way will require to perform the
|
||||
/// costly computation of difficulty attributes internally. If attributes
|
||||
/// for the current [`Difficulty`] settings are already available, consider
|
||||
/// using [`from_attributes`] instead.
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`DifficultyAttributes`],
|
||||
/// [`PerformanceAttributes`], or mode-specific attributes like
|
||||
/// [`TaikoDifficultyAttributes`], [`ManiaPerformanceAttributes`], ...)
|
||||
/// - a beatmap ([`Beatmap`] or [`Converted<'_, M>`])
|
||||
///
|
||||
/// [`from_attributes`]: Self::from_attributes
|
||||
pub const fn from_map(map: &'map Beatmap) -> Self {
|
||||
let mode = map.mode;
|
||||
let map = Cow::Borrowed(map);
|
||||
|
||||
match mode {
|
||||
GameMode::Osu => Self::Osu(OsuPerformance::from_map(Converted::new(map))),
|
||||
GameMode::Taiko => Self::Taiko(TaikoPerformance::from_map(Converted::new(map))),
|
||||
GameMode::Catch => Self::Catch(CatchPerformance::from_map(Converted::new(map))),
|
||||
GameMode::Mania => Self::Mania(ManiaPerformance::from_map(Converted::new(map))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new performance calculator through previously calculated
|
||||
/// attributes.
|
||||
/// If a map is given, difficulty attributes will need to be calculated
|
||||
/// internally which is a costly operation. Hence, passing attributes
|
||||
/// should be prefered.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
pub fn from_attributes(attrs: impl AttributeProvider) -> Self {
|
||||
const fn inner(attrs: DifficultyAttributes) -> Performance<'static> {
|
||||
match attrs {
|
||||
DifficultyAttributes::Osu(attrs) => Performance::Osu(attrs.performance()),
|
||||
DifficultyAttributes::Taiko(attrs) => Performance::Taiko(attrs.performance()),
|
||||
DifficultyAttributes::Catch(attrs) => Performance::Catch(attrs.performance()),
|
||||
DifficultyAttributes::Mania(attrs) => Performance::Mania(attrs.performance()),
|
||||
}
|
||||
}
|
||||
|
||||
inner(attrs.attributes())
|
||||
/// 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.
|
||||
///
|
||||
/// [`Beatmap`]: crate::model::beatmap::Beatmap
|
||||
/// [`Converted<'_, M>`]: crate::model::beatmap::Converted
|
||||
/// [`DifficultyAttributes`]: crate::any::DifficultyAttributes
|
||||
/// [`TaikoDifficultyAttributes`]: crate::taiko::TaikoDifficultyAttributes
|
||||
/// [`ManiaPerformanceAttributes`]: crate::mania::ManiaPerformanceAttributes
|
||||
pub fn new(map_or_attrs: impl IntoPerformance<'map>) -> Self {
|
||||
map_or_attrs.into_performance()
|
||||
}
|
||||
|
||||
/// Consume the performance calculator and calculate
|
||||
@@ -82,13 +61,12 @@ impl<'map> Performance<'map> {
|
||||
|
||||
/// Attempt to convert the map to the specified mode.
|
||||
///
|
||||
/// Returns `Err(self)` if the conversion is incompatible or the internal
|
||||
/// beatmap was already replaced with difficulty attributes, i.e. if
|
||||
/// [`Performance::from_attributes`] or [`Performance::generate_state`] was
|
||||
/// called.
|
||||
/// Returns `Err(self)` if the conversion is incompatible or no beatmap is
|
||||
/// contained, i.e. if this [`Performance`] was created through attributes
|
||||
/// or [`Performance::generate_state`] was called.
|
||||
///
|
||||
/// If the given mode should be ignored in case it is incompatible or if
|
||||
/// the internal beatmap was replaced, use [`mode_or_ignore`] instead.
|
||||
/// If the given mode should be ignored in case of an error, use
|
||||
/// [`mode_or_ignore`] instead.
|
||||
///
|
||||
/// [`mode_or_ignore`]: Self::mode_or_ignore
|
||||
// Both variants have the same size
|
||||
@@ -380,33 +358,6 @@ impl<'map> Performance<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AttributeProvider> From<A> for Performance<'_> {
|
||||
fn from(attrs: A) -> Self {
|
||||
Self::from_attributes(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_converted {
|
||||
( $mode:ident ) => {
|
||||
impl<'a> From<Converted<'a, $mode>> for Performance<'a> {
|
||||
fn from(converted: Converted<'a, $mode>) -> Self {
|
||||
Self::$mode(converted.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> From<&'b Converted<'a, $mode>> for Performance<'a> {
|
||||
fn from(converted: &'b Converted<'a, $mode>) -> Self {
|
||||
Self::$mode(converted.as_owned().into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_converted!(Osu);
|
||||
impl_from_converted!(Taiko);
|
||||
impl_from_converted!(Catch);
|
||||
impl_from_converted!(Mania);
|
||||
|
||||
/// While generating remaining hitresults, decide how they should be distributed.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum HitResultPriority {
|
||||
@@ -425,3 +376,72 @@ impl Default for HitResultPriority {
|
||||
Self::DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IntoPerformance<'a>> From<T> for Performance<'a> {
|
||||
fn from(into: T) -> Self {
|
||||
into.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
any::DifficultyAttributes,
|
||||
catch::{CatchDifficultyAttributes, CatchPerformanceAttributes},
|
||||
mania::{ManiaDifficultyAttributes, ManiaPerformanceAttributes},
|
||||
osu::{OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
taiko::{Taiko, TaikoDifficultyAttributes, TaikoPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create() {
|
||||
let map = Beatmap::from_path("./resources/1028484.osu").unwrap();
|
||||
let converted = map.unchecked_as_converted::<Taiko>();
|
||||
|
||||
let _ = Performance::new(&converted);
|
||||
let _ = Performance::new(converted.as_owned());
|
||||
let _ = Performance::new(&map);
|
||||
let _ = Performance::new(map.clone());
|
||||
|
||||
let _ = Performance::new(OsuDifficultyAttributes::default());
|
||||
let _ = Performance::new(TaikoDifficultyAttributes::default());
|
||||
let _ = Performance::new(CatchDifficultyAttributes::default());
|
||||
let _ = Performance::new(ManiaDifficultyAttributes::default());
|
||||
|
||||
let _ = Performance::new(OsuPerformanceAttributes::default());
|
||||
let _ = Performance::new(TaikoPerformanceAttributes::default());
|
||||
let _ = Performance::new(CatchPerformanceAttributes::default());
|
||||
let _ = Performance::new(ManiaPerformanceAttributes::default());
|
||||
|
||||
let _ = Performance::new(DifficultyAttributes::Osu(OsuDifficultyAttributes::default()));
|
||||
let _ = Performance::new(PerformanceAttributes::Taiko(
|
||||
TaikoPerformanceAttributes::default(),
|
||||
));
|
||||
|
||||
let _ = Performance::from(&converted);
|
||||
let _ = Performance::from(converted);
|
||||
let _ = Performance::from(&map);
|
||||
let _ = Performance::from(map);
|
||||
|
||||
let _ = Performance::from(OsuDifficultyAttributes::default());
|
||||
let _ = Performance::from(TaikoDifficultyAttributes::default());
|
||||
let _ = Performance::from(CatchDifficultyAttributes::default());
|
||||
let _ = Performance::from(ManiaDifficultyAttributes::default());
|
||||
|
||||
let _ = Performance::from(OsuPerformanceAttributes::default());
|
||||
let _ = Performance::from(TaikoPerformanceAttributes::default());
|
||||
let _ = Performance::from(CatchPerformanceAttributes::default());
|
||||
let _ = Performance::from(ManiaPerformanceAttributes::default());
|
||||
|
||||
let _ = Performance::from(DifficultyAttributes::Osu(OsuDifficultyAttributes::default()));
|
||||
let _ = Performance::from(PerformanceAttributes::Taiko(
|
||||
TaikoPerformanceAttributes::default(),
|
||||
));
|
||||
|
||||
let _ = DifficultyAttributes::Osu(OsuDifficultyAttributes::default()).performance();
|
||||
let _ = PerformanceAttributes::Taiko(TaikoPerformanceAttributes::default()).performance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ impl CatchDifficultyAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> CatchPerformance<'a> {
|
||||
CatchPerformance::from_attributes(self)
|
||||
pub fn performance<'a>(self) -> CatchPerformance<'a> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub(crate) fn set_object_count(&mut self, count: &ObjectCount) {
|
||||
@@ -89,8 +89,8 @@ impl CatchPerformanceAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> CatchPerformance<'a> {
|
||||
CatchPerformance::from_attributes(self.difficulty)
|
||||
pub fn performance<'a>(self) -> CatchPerformance<'a> {
|
||||
self.difficulty.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ impl IGameMode for Catch {
|
||||
}
|
||||
|
||||
fn performance(map: CatchBeatmap<'_>) -> Self::Performance<'_> {
|
||||
CatchPerformance::from_map(map)
|
||||
CatchPerformance::new(map)
|
||||
}
|
||||
|
||||
fn gradual_difficulty(
|
||||
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
assert_eq!(next_gradual, next_gradual_3rd);
|
||||
}
|
||||
|
||||
let regular_calc = CatchPerformance::from_map(converted.as_owned())
|
||||
let regular_calc = CatchPerformance::new(converted.as_owned())
|
||||
.difficulty(difficulty.clone())
|
||||
.passed_objects(i as u32)
|
||||
.state(state.clone());
|
||||
|
||||
+107
-73
@@ -1,14 +1,14 @@
|
||||
use std::cmp::{self, Ordering};
|
||||
|
||||
use crate::{
|
||||
any::{AttributeProvider, Difficulty, DifficultyAttributes},
|
||||
any::{Difficulty, IntoModePerformance, IntoPerformance},
|
||||
osu::OsuPerformance,
|
||||
util::{map_or_attrs::MapOrAttrs, mods::Mods},
|
||||
Performance,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attributes::{CatchDifficultyAttributes, CatchPerformanceAttributes},
|
||||
convert::CatchBeatmap,
|
||||
score_state::CatchScoreState,
|
||||
Catch,
|
||||
};
|
||||
@@ -33,57 +33,38 @@ pub struct CatchPerformance<'map> {
|
||||
impl<'map> CatchPerformance<'map> {
|
||||
/// Create a new performance calculator for osu!catch maps.
|
||||
///
|
||||
/// Note that creating [`CatchPerformance`] this way will require to
|
||||
/// perform the costly computation of [`CatchDifficultyAttributes`]
|
||||
/// internally. If difficulty attributes for the current [`Difficulty`]
|
||||
/// settings are already available, consider using [`from_attributes`] or
|
||||
/// [`try_from_attributes`] instead.
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`CatchDifficultyAttributes`]
|
||||
/// or [`CatchPerformanceAttributes`])
|
||||
/// - a beatmap ([`CatchBeatmap<'map>`])
|
||||
///
|
||||
/// [`from_attributes`]: Self::from_attributes
|
||||
/// [`try_from_attributes`]: Self::try_from_attributes
|
||||
pub const fn from_map(map: CatchBeatmap<'map>) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Map(map),
|
||||
difficulty: Difficulty::new(),
|
||||
acc: None,
|
||||
combo: None,
|
||||
fruits: None,
|
||||
droplets: None,
|
||||
tiny_droplets: None,
|
||||
tiny_droplet_misses: None,
|
||||
misses: None,
|
||||
}
|
||||
/// If a map is given, difficulty attributes will need to be calculated
|
||||
/// internally which is a costly operation. Hence, passing attributes
|
||||
/// should be prefered.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// [`CatchBeatmap<'map>`]: crate::catch::CatchBeatmap
|
||||
pub fn new(map_or_attrs: impl IntoModePerformance<'map, Catch>) -> Self {
|
||||
map_or_attrs.into_performance()
|
||||
}
|
||||
|
||||
/// Create a new performance calculator from difficulty attributes.
|
||||
/// Try to create a new performance calculator for osu!catch maps.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
pub const fn from_attributes(attrs: CatchDifficultyAttributes) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Attrs(attrs),
|
||||
difficulty: Difficulty::new(),
|
||||
acc: None,
|
||||
combo: None,
|
||||
fruits: None,
|
||||
droplets: None,
|
||||
tiny_droplets: None,
|
||||
tiny_droplet_misses: None,
|
||||
misses: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to create a new performance calculator from difficulty attributes.
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu!catch e.g.
|
||||
/// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`]
|
||||
/// of a different mode.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
/// See [`CatchPerformance::new`] for more information.
|
||||
///
|
||||
/// Returns `None` if `attrs` contained attributes of a different mode.
|
||||
pub fn try_from_attributes(attrs: impl AttributeProvider) -> Option<Self> {
|
||||
if let DifficultyAttributes::Catch(attrs) = attrs.attributes() {
|
||||
Some(Self::from_attributes(attrs))
|
||||
/// [`Converted`]: crate::model::beatmap::Converted
|
||||
/// [`DifficultyAttributes`]: crate::any::DifficultyAttributes
|
||||
/// [`PerformanceAttributes`]: crate::any::PerformanceAttributes
|
||||
pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option<Self> {
|
||||
if let Performance::Catch(calc) = map_or_attrs.into_performance() {
|
||||
Some(calc)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -435,6 +416,20 @@ impl<'map> CatchPerformance<'map> {
|
||||
|
||||
inner.calculate()
|
||||
}
|
||||
|
||||
pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Catch>) -> Self {
|
||||
Self {
|
||||
map_or_attrs,
|
||||
difficulty: Difficulty::new(),
|
||||
acc: None,
|
||||
combo: None,
|
||||
fruits: None,
|
||||
droplets: None,
|
||||
tiny_droplets: None,
|
||||
tiny_droplet_misses: None,
|
||||
misses: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
|
||||
@@ -442,12 +437,9 @@ impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
|
||||
|
||||
/// Try to create [`CatchPerformance`] through [`OsuPerformance`].
|
||||
///
|
||||
/// Returns `None` if [`OsuPerformance`] already replaced its internal
|
||||
/// beatmap with [`OsuDifficultyAttributes`], i.e. if
|
||||
/// [`OsuPerformance::from_attributes`] or [`OsuPerformance::generate_state`]
|
||||
/// was called.
|
||||
///
|
||||
/// [`OsuDifficultyAttributes`]: crate::osu::OsuDifficultyAttributes
|
||||
/// Returns `None` if [`OsuPerformance`] does not contain a beatmap, i.e.
|
||||
/// if it was constructed through attributes or
|
||||
/// [`OsuPerformance::generate_state`] was called.
|
||||
fn try_from(mut osu: OsuPerformance<'map>) -> Result<Self, Self::Error> {
|
||||
let MapOrAttrs::Map(converted) = osu.map_or_attrs else {
|
||||
return Err(osu);
|
||||
@@ -488,21 +480,9 @@ impl<'map> TryFrom<OsuPerformance<'map>> for CatchPerformance<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> From<CatchBeatmap<'map>> for CatchPerformance<'map> {
|
||||
fn from(map: CatchBeatmap<'map>) -> Self {
|
||||
Self::from_map(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CatchDifficultyAttributes> for CatchPerformance<'_> {
|
||||
fn from(attrs: CatchDifficultyAttributes) -> Self {
|
||||
Self::from_attributes(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CatchPerformanceAttributes> for CatchPerformance<'_> {
|
||||
fn from(attrs: CatchPerformanceAttributes) -> Self {
|
||||
Self::from_attributes(attrs.difficulty)
|
||||
impl<'map, T: IntoModePerformance<'map, Catch>> From<T> for CatchPerformance<'map> {
|
||||
fn from(into: T) -> Self {
|
||||
into.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,8 +586,13 @@ mod test {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use proptest::prelude::*;
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::Beatmap;
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -617,13 +602,14 @@ mod test {
|
||||
const N_DROPLETS: u32 = 2;
|
||||
const N_TINY_DROPLETS: u32 = 291;
|
||||
|
||||
fn beatmap() -> Beatmap {
|
||||
Beatmap::from_path("./resources/2118524.osu").unwrap()
|
||||
}
|
||||
|
||||
fn attrs() -> CatchDifficultyAttributes {
|
||||
ATTRS
|
||||
.get_or_init(|| {
|
||||
let converted = Beatmap::from_path("./resources/2118524.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Catch>();
|
||||
|
||||
let converted = beatmap().unchecked_into_converted::<Catch>();
|
||||
let attrs = Difficulty::new().with_mode().calculate(&converted);
|
||||
|
||||
assert_eq!(N_FRUITS, attrs.n_fruits);
|
||||
@@ -815,4 +801,52 @@ mod test {
|
||||
|
||||
assert_eq!(state, expected);
|
||||
}
|
||||
|
||||
#[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::try_new(CatchDifficultyAttributes::default()).unwrap();
|
||||
let _ = CatchPerformance::try_new(CatchPerformanceAttributes::default()).unwrap();
|
||||
let _ = CatchPerformance::try_new(DifficultyAttributes::Catch(
|
||||
CatchDifficultyAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = CatchPerformance::try_new(PerformanceAttributes::Catch(
|
||||
CatchPerformanceAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = CatchPerformance::try_new(&converted).unwrap();
|
||||
let _ = CatchPerformance::try_new(converted.as_owned()).unwrap();
|
||||
|
||||
let _ = CatchPerformance::from(CatchDifficultyAttributes::default());
|
||||
let _ = CatchPerformance::from(CatchPerformanceAttributes::default());
|
||||
let _ = CatchPerformance::from(&converted);
|
||||
let _ = CatchPerformance::from(converted);
|
||||
|
||||
let _ = CatchDifficultyAttributes::default().performance();
|
||||
let _ = CatchPerformanceAttributes::default().performance();
|
||||
|
||||
map.mode = GameMode::Osu;
|
||||
let converted = map.unchecked_as_converted::<Osu>();
|
||||
|
||||
assert!(CatchPerformance::try_new(OsuDifficultyAttributes::default()).is_none());
|
||||
assert!(CatchPerformance::try_new(OsuPerformanceAttributes::default()).is_none());
|
||||
assert!(CatchPerformance::try_new(DifficultyAttributes::Osu(
|
||||
OsuDifficultyAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(CatchPerformance::try_new(PerformanceAttributes::Osu(
|
||||
OsuPerformanceAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(CatchPerformance::try_new(&converted).is_none());
|
||||
assert!(CatchPerformance::try_new(converted).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
//! let stars = diff_attrs.stars();
|
||||
//!
|
||||
//! // Calculate performance attributes
|
||||
//! let perf_attrs = rosu_pp::Performance::from_attributes(diff_attrs)
|
||||
//! let perf_attrs = rosu_pp::Performance::new(diff_attrs)
|
||||
//! // To speed up the calculation, we used the previous attributes.
|
||||
//! // **Note** that this should only be done if the map and all difficulty
|
||||
//! // settings stay the same, otherwise the final attributes will be incorrect!
|
||||
|
||||
@@ -36,8 +36,8 @@ impl ManiaDifficultyAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> ManiaPerformance<'a> {
|
||||
ManiaPerformance::from_attributes(self)
|
||||
pub fn performance<'a>(self) -> ManiaPerformance<'a> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ impl ManiaPerformanceAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> ManiaPerformance<'a> {
|
||||
ManiaPerformance::from_attributes(self.difficulty)
|
||||
pub fn performance<'a>(self) -> ManiaPerformance<'a> {
|
||||
self.difficulty.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ impl IGameMode for Mania {
|
||||
}
|
||||
|
||||
fn performance(map: ManiaBeatmap<'_>) -> Self::Performance<'_> {
|
||||
ManiaPerformance::from_map(map)
|
||||
ManiaPerformance::new(map)
|
||||
}
|
||||
|
||||
fn gradual_difficulty(
|
||||
|
||||
@@ -163,7 +163,7 @@ mod tests {
|
||||
assert_eq!(next_gradual, next_gradual_3rd);
|
||||
}
|
||||
|
||||
let mut regular_calc = ManiaPerformance::from_map(converted.as_owned())
|
||||
let mut regular_calc = ManiaPerformance::new(converted.as_owned())
|
||||
.difficulty(difficulty.clone())
|
||||
.passed_objects(i as u32)
|
||||
.state(state.clone());
|
||||
|
||||
+108
-75
@@ -1,14 +1,14 @@
|
||||
use std::cmp;
|
||||
|
||||
use crate::{
|
||||
any::{AttributeProvider, Difficulty, DifficultyAttributes, HitResultPriority},
|
||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance},
|
||||
osu::OsuPerformance,
|
||||
util::{map_or_attrs::MapOrAttrs, mods::Mods},
|
||||
Performance,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attributes::{ManiaDifficultyAttributes, ManiaPerformanceAttributes},
|
||||
convert::ManiaBeatmap,
|
||||
score_state::ManiaScoreState,
|
||||
Mania,
|
||||
};
|
||||
@@ -34,59 +34,38 @@ pub struct ManiaPerformance<'map> {
|
||||
impl<'map> ManiaPerformance<'map> {
|
||||
/// Create a new performance calculator for osu!mania maps.
|
||||
///
|
||||
/// Note that creating [`ManiaPerformance`] this way will require to
|
||||
/// perform the costly computation of [`ManiaDifficultyAttributes`]
|
||||
/// internally. If difficulty attributes for the current [`Difficulty`]
|
||||
/// settings are already available, consider using [`from_attributes`] or
|
||||
/// [`try_from_attributes`] instead.
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`ManiaDifficultyAttributes`]
|
||||
/// or [`ManiaPerformanceAttributes`])
|
||||
/// - a beatmap ([`ManiaBeatmap<'map>`])
|
||||
///
|
||||
/// [`from_attributes`]: Self::from_attributes
|
||||
/// [`try_from_attributes`]: Self::try_from_attributes
|
||||
pub const fn from_map(map: ManiaBeatmap<'map>) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Map(map),
|
||||
difficulty: Difficulty::new(),
|
||||
n320: None,
|
||||
n300: None,
|
||||
n200: None,
|
||||
n100: None,
|
||||
n50: None,
|
||||
misses: None,
|
||||
acc: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
/// If a map is given, difficulty attributes will need to be calculated
|
||||
/// internally which is a costly operation. Hence, passing attributes
|
||||
/// should be prefered.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// [`ManiaBeatmap<'map>`]: crate::mania::ManiaBeatmap
|
||||
pub fn new(map_or_attrs: impl IntoModePerformance<'map, Mania>) -> Self {
|
||||
map_or_attrs.into_performance()
|
||||
}
|
||||
|
||||
/// Create a new performance calculator from difficulty attributes.
|
||||
/// Try to create a new performance calculator for osu!mania maps.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
pub const fn from_attributes(attrs: ManiaDifficultyAttributes) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Attrs(attrs),
|
||||
difficulty: Difficulty::new(),
|
||||
n320: None,
|
||||
n300: None,
|
||||
n200: None,
|
||||
n100: None,
|
||||
n50: None,
|
||||
misses: None,
|
||||
acc: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to create a new performance calculator from difficulty attributes.
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu!mania e.g.
|
||||
/// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`]
|
||||
/// of a different mode.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
/// See [`ManiaPerformance::new`] for more information.
|
||||
///
|
||||
/// Returns `None` if `attrs` contained attributes of a different mode.
|
||||
pub fn try_from_attributes(attrs: impl AttributeProvider) -> Option<Self> {
|
||||
if let DifficultyAttributes::Mania(attrs) = attrs.attributes() {
|
||||
Some(Self::from_attributes(attrs))
|
||||
/// [`Converted`]: crate::model::beatmap::Converted
|
||||
/// [`DifficultyAttributes`]: crate::any::DifficultyAttributes
|
||||
/// [`PerformanceAttributes`]: crate::any::PerformanceAttributes
|
||||
pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option<Self> {
|
||||
if let Performance::Mania(calc) = map_or_attrs.into_performance() {
|
||||
Some(calc)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -789,6 +768,21 @@ impl<'map> ManiaPerformance<'map> {
|
||||
|
||||
inner.calculate()
|
||||
}
|
||||
|
||||
pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Mania>) -> Self {
|
||||
Self {
|
||||
map_or_attrs,
|
||||
difficulty: Difficulty::new(),
|
||||
n320: None,
|
||||
n300: None,
|
||||
n200: None,
|
||||
n100: None,
|
||||
n50: None,
|
||||
misses: None,
|
||||
acc: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> TryFrom<OsuPerformance<'map>> for ManiaPerformance<'map> {
|
||||
@@ -796,12 +790,9 @@ impl<'map> TryFrom<OsuPerformance<'map>> for ManiaPerformance<'map> {
|
||||
|
||||
/// Try to create [`ManiaPerformance`] through [`OsuPerformance`].
|
||||
///
|
||||
/// Returns `None` if [`OsuPerformance`] already replaced its internal
|
||||
/// beatmap with [`OsuDifficultyAttributes`], i.e. if
|
||||
/// [`OsuPerformance::from_attributes`] or [`OsuPerformance::generate_state`]
|
||||
/// was called.
|
||||
///
|
||||
/// [`OsuDifficultyAttributes`]: crate::osu::OsuDifficultyAttributes
|
||||
/// Returns `None` if [`OsuPerformance`] does not contain a beatmap, i.e.
|
||||
/// if it was constructed through attributes or
|
||||
/// [`OsuPerformance::generate_state`] was called.
|
||||
fn try_from(mut osu: OsuPerformance<'map>) -> Result<Self, Self::Error> {
|
||||
let MapOrAttrs::Map(converted) = osu.map_or_attrs else {
|
||||
return Err(osu);
|
||||
@@ -843,21 +834,9 @@ impl<'map> TryFrom<OsuPerformance<'map>> for ManiaPerformance<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> From<ManiaBeatmap<'map>> for ManiaPerformance<'map> {
|
||||
fn from(map: ManiaBeatmap<'map>) -> Self {
|
||||
Self::from_map(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ManiaDifficultyAttributes> for ManiaPerformance<'_> {
|
||||
fn from(attrs: ManiaDifficultyAttributes) -> Self {
|
||||
Self::from_attributes(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ManiaPerformanceAttributes> for ManiaPerformance<'_> {
|
||||
fn from(attrs: ManiaPerformanceAttributes) -> Self {
|
||||
Self::from_attributes(attrs.difficulty)
|
||||
impl<'map, T: IntoModePerformance<'map, Mania>> From<T> for ManiaPerformance<'map> {
|
||||
fn from(into: T) -> Self {
|
||||
into.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,8 +922,13 @@ mod tests {
|
||||
use std::{cmp::Ordering, sync::OnceLock};
|
||||
|
||||
use proptest::prelude::*;
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::Beatmap;
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -952,13 +936,14 @@ mod tests {
|
||||
|
||||
const N_OBJECTS: u32 = 594;
|
||||
|
||||
fn beatmap() -> Beatmap {
|
||||
Beatmap::from_path("./resources/1638954.osu").unwrap()
|
||||
}
|
||||
|
||||
fn attrs() -> ManiaDifficultyAttributes {
|
||||
ATTRS
|
||||
.get_or_init(|| {
|
||||
let converted = Beatmap::from_path("./resources/1638954.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Mania>();
|
||||
|
||||
let converted = beatmap().unchecked_into_converted::<Mania>();
|
||||
let attrs = Difficulty::new().with_mode().calculate(&converted);
|
||||
|
||||
assert_eq!(N_OBJECTS, converted.hit_objects.len() as u32);
|
||||
@@ -1294,4 +1279,52 @@ mod tests {
|
||||
|
||||
assert_eq!(state, expected);
|
||||
}
|
||||
|
||||
#[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::try_new(ManiaDifficultyAttributes::default()).unwrap();
|
||||
let _ = ManiaPerformance::try_new(ManiaPerformanceAttributes::default()).unwrap();
|
||||
let _ = ManiaPerformance::try_new(DifficultyAttributes::Mania(
|
||||
ManiaDifficultyAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = ManiaPerformance::try_new(PerformanceAttributes::Mania(
|
||||
ManiaPerformanceAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = ManiaPerformance::try_new(&converted).unwrap();
|
||||
let _ = ManiaPerformance::try_new(converted.as_owned()).unwrap();
|
||||
|
||||
let _ = ManiaPerformance::from(ManiaDifficultyAttributes::default());
|
||||
let _ = ManiaPerformance::from(ManiaPerformanceAttributes::default());
|
||||
let _ = ManiaPerformance::from(&converted);
|
||||
let _ = ManiaPerformance::from(converted);
|
||||
|
||||
let _ = ManiaDifficultyAttributes::default().performance();
|
||||
let _ = ManiaPerformanceAttributes::default().performance();
|
||||
|
||||
map.mode = GameMode::Osu;
|
||||
let converted = map.unchecked_as_converted::<Osu>();
|
||||
|
||||
assert!(ManiaPerformance::try_new(OsuDifficultyAttributes::default()).is_none());
|
||||
assert!(ManiaPerformance::try_new(OsuPerformanceAttributes::default()).is_none());
|
||||
assert!(ManiaPerformance::try_new(DifficultyAttributes::Osu(
|
||||
OsuDifficultyAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(ManiaPerformance::try_new(PerformanceAttributes::Osu(
|
||||
OsuPerformanceAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(ManiaPerformance::try_new(&converted).is_none());
|
||||
assert!(ManiaPerformance::try_new(converted).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ impl Beatmap {
|
||||
}
|
||||
|
||||
/// Create a performance calculator for this [`Beatmap`].
|
||||
pub const fn performance(&self) -> Performance<'_> {
|
||||
Performance::from_map(self)
|
||||
pub fn performance(&self) -> Performance<'_> {
|
||||
Performance::new(self)
|
||||
}
|
||||
|
||||
/// Create a gradual difficulty calculator for this [`Beatmap`].
|
||||
|
||||
@@ -43,8 +43,8 @@ impl OsuDifficultyAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> OsuPerformance<'a> {
|
||||
OsuPerformance::from_attributes(self)
|
||||
pub fn performance<'a>(self) -> OsuPerformance<'a> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ impl OsuPerformanceAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> OsuPerformance<'a> {
|
||||
OsuPerformance::from_attributes(self.difficulty)
|
||||
pub fn performance<'a>(self) -> OsuPerformance<'a> {
|
||||
self.difficulty.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ impl IGameMode for Osu {
|
||||
}
|
||||
|
||||
fn performance(map: OsuBeatmap<'_>) -> Self::Performance<'_> {
|
||||
OsuPerformance::from_map(map)
|
||||
OsuPerformance::new(map)
|
||||
}
|
||||
|
||||
fn gradual_difficulty(difficulty: Difficulty, map: &OsuBeatmap<'_>) -> Self::GradualDifficulty {
|
||||
|
||||
@@ -173,7 +173,7 @@ mod tests {
|
||||
assert_eq!(next_gradual, next_gradual_3rd);
|
||||
}
|
||||
|
||||
let mut regular_calc = OsuPerformance::from_map(converted.as_owned())
|
||||
let mut regular_calc = OsuPerformance::new(converted.as_owned())
|
||||
.difficulty(difficulty.clone())
|
||||
.passed_objects(i as u32)
|
||||
.state(state);
|
||||
|
||||
+104
-71
@@ -3,7 +3,7 @@ use std::cmp;
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::{
|
||||
any::{AttributeProvider, Difficulty, DifficultyAttributes, HitResultPriority, Performance},
|
||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance, Performance},
|
||||
catch::CatchPerformance,
|
||||
mania::ManiaPerformance,
|
||||
taiko::TaikoPerformance,
|
||||
@@ -12,7 +12,6 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
attributes::{OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
convert::OsuBeatmap,
|
||||
score_state::OsuScoreState,
|
||||
Osu,
|
||||
};
|
||||
@@ -35,59 +34,40 @@ pub struct OsuPerformance<'map> {
|
||||
}
|
||||
|
||||
impl<'map> OsuPerformance<'map> {
|
||||
/// Create a new performance calculator for osu!standard maps.
|
||||
/// Create a new performance calculator for osu! maps.
|
||||
///
|
||||
/// Note that creating [`OsuPerformance`] this way will require to
|
||||
/// perform the costly computation of [`OsuDifficultyAttributes`]
|
||||
/// internally. If difficulty attributes for the current [`Difficulty`]
|
||||
/// settings are already available, consider using [`from_attributes`] or
|
||||
/// [`try_from_attributes`] instead.
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`OsuDifficultyAttributes`]
|
||||
/// or [`OsuPerformanceAttributes`])
|
||||
/// - a beatmap ([`OsuBeatmap<'map>`])
|
||||
///
|
||||
/// [`from_attributes`]: Self::from_attributes
|
||||
/// [`try_from_attributes`]: Self::try_from_attributes
|
||||
pub const fn from_map(map: OsuBeatmap<'map>) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Map(map),
|
||||
difficulty: Difficulty::new(),
|
||||
acc: None,
|
||||
combo: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
n50: None,
|
||||
misses: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
/// If a map is given, difficulty attributes will need to be calculated
|
||||
/// internally which is a costly operation. Hence, passing attributes
|
||||
/// should be prefered.
|
||||
///
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Create a new performance calculator from difficulty attributes.
|
||||
/// Try to create a new performance calculator for osu! maps.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
pub const fn from_attributes(attrs: OsuDifficultyAttributes) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Attrs(attrs),
|
||||
difficulty: Difficulty::new(),
|
||||
acc: None,
|
||||
combo: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
n50: None,
|
||||
misses: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to create a new performance calculator from difficulty attributes.
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu! e.g.
|
||||
/// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`]
|
||||
/// of a different mode.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
/// See [`OsuPerformance::new`] for more information.
|
||||
///
|
||||
/// Returns `None` if `attrs` contained attributes of a different mode.
|
||||
pub fn try_from_attributes(attrs: impl AttributeProvider) -> Option<Self> {
|
||||
if let DifficultyAttributes::Osu(attrs) = attrs.attributes() {
|
||||
Some(Self::from_attributes(attrs))
|
||||
/// [`Converted`]: crate::model::beatmap::Converted
|
||||
/// [`DifficultyAttributes`]: crate::any::DifficultyAttributes
|
||||
/// [`PerformanceAttributes`]: crate::any::PerformanceAttributes
|
||||
pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option<Self> {
|
||||
if let Performance::Osu(calc) = map_or_attrs.into_performance() {
|
||||
Some(calc)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -95,13 +75,12 @@ impl<'map> OsuPerformance<'map> {
|
||||
|
||||
/// Attempt to convert the map to the specified mode.
|
||||
///
|
||||
/// Returns `Err(self)` if the internal beatmap was already replaced with
|
||||
/// [`OsuDifficultyAttributes`], i.e. if
|
||||
/// [`OsuPerformance::from_attributes`] or
|
||||
/// Returns `Err(self)` if no beatmap is contained, i.e. if this
|
||||
/// [`OsuPerformance`] was created through attributes or
|
||||
/// [`OsuPerformance::generate_state`] was called.
|
||||
///
|
||||
/// If the given mode should be ignored in case the internal beatmap was
|
||||
/// replaced, use [`mode_or_ignore`] instead.
|
||||
/// If the given mode should be ignored in case of an error, use
|
||||
/// [`mode_or_ignore`] instead.
|
||||
///
|
||||
/// [`mode_or_ignore`]: Self::mode_or_ignore
|
||||
// The `Ok`-variant is larger in size
|
||||
@@ -519,23 +498,25 @@ impl<'map> OsuPerformance<'map> {
|
||||
|
||||
inner.calculate()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> From<OsuBeatmap<'map>> for OsuPerformance<'map> {
|
||||
fn from(map: OsuBeatmap<'map>) -> Self {
|
||||
Self::from_map(map)
|
||||
pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Osu>) -> Self {
|
||||
Self {
|
||||
map_or_attrs,
|
||||
difficulty: Difficulty::new(),
|
||||
acc: None,
|
||||
combo: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
n50: None,
|
||||
misses: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsuDifficultyAttributes> for OsuPerformance<'_> {
|
||||
fn from(attrs: OsuDifficultyAttributes) -> Self {
|
||||
Self::from_attributes(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsuPerformanceAttributes> for OsuPerformance<'_> {
|
||||
fn from(attrs: OsuPerformanceAttributes) -> Self {
|
||||
Self::from_attributes(attrs.difficulty)
|
||||
impl<'map, T: IntoModePerformance<'map, Osu>> From<T> for OsuPerformance<'map> {
|
||||
fn from(into: T) -> Self {
|
||||
into.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,7 +859,11 @@ mod test {
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::Beatmap;
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
taiko::{Taiko, TaikoDifficultyAttributes, TaikoPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -886,13 +871,14 @@ mod test {
|
||||
|
||||
const N_OBJECTS: u32 = 601;
|
||||
|
||||
fn beatmap() -> Beatmap {
|
||||
Beatmap::from_path("./resources/2785319.osu").unwrap()
|
||||
}
|
||||
|
||||
fn attrs() -> OsuDifficultyAttributes {
|
||||
ATTRS
|
||||
.get_or_init(|| {
|
||||
let converted = Beatmap::from_path("./resources/2785319.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Osu>();
|
||||
|
||||
let converted = beatmap().unchecked_into_converted::<Osu>();
|
||||
let attrs = Difficulty::new().with_mode().calculate(&converted);
|
||||
|
||||
assert_eq!(
|
||||
@@ -1139,4 +1125,51 @@ mod test {
|
||||
|
||||
assert_eq!(state, expected);
|
||||
}
|
||||
|
||||
#[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::try_new(OsuDifficultyAttributes::default()).unwrap();
|
||||
let _ = OsuPerformance::try_new(OsuPerformanceAttributes::default()).unwrap();
|
||||
let _ =
|
||||
OsuPerformance::try_new(DifficultyAttributes::Osu(OsuDifficultyAttributes::default()))
|
||||
.unwrap();
|
||||
let _ = OsuPerformance::try_new(PerformanceAttributes::Osu(
|
||||
OsuPerformanceAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = OsuPerformance::try_new(&converted).unwrap();
|
||||
let _ = OsuPerformance::try_new(converted.as_owned()).unwrap();
|
||||
|
||||
let _ = OsuPerformance::from(OsuDifficultyAttributes::default());
|
||||
let _ = OsuPerformance::from(OsuPerformanceAttributes::default());
|
||||
let _ = OsuPerformance::from(&converted);
|
||||
let _ = OsuPerformance::from(converted);
|
||||
|
||||
let _ = OsuDifficultyAttributes::default().performance();
|
||||
let _ = OsuPerformanceAttributes::default().performance();
|
||||
|
||||
map.mode = GameMode::Taiko;
|
||||
let converted = map.unchecked_as_converted::<Taiko>();
|
||||
|
||||
assert!(OsuPerformance::try_new(TaikoDifficultyAttributes::default()).is_none());
|
||||
assert!(OsuPerformance::try_new(TaikoPerformanceAttributes::default()).is_none());
|
||||
assert!(OsuPerformance::try_new(DifficultyAttributes::Taiko(
|
||||
TaikoDifficultyAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(OsuPerformance::try_new(PerformanceAttributes::Taiko(
|
||||
TaikoPerformanceAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(OsuPerformance::try_new(&converted).is_none());
|
||||
assert!(OsuPerformance::try_new(converted).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ impl TaikoDifficultyAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> TaikoPerformance<'a> {
|
||||
TaikoPerformance::from_attributes(self)
|
||||
pub fn performance<'a>(self) -> TaikoPerformance<'a> {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ impl TaikoPerformanceAttributes {
|
||||
}
|
||||
|
||||
/// Returns a builder for performance calculation.
|
||||
pub const fn performance<'a>(self) -> TaikoPerformance<'a> {
|
||||
TaikoPerformance::from_attributes(self.difficulty)
|
||||
pub fn performance<'a>(self) -> TaikoPerformance<'a> {
|
||||
self.difficulty.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ impl IGameMode for Taiko {
|
||||
}
|
||||
|
||||
fn performance(map: TaikoBeatmap<'_>) -> Self::Performance<'_> {
|
||||
TaikoPerformance::from_map(map)
|
||||
TaikoPerformance::new(map)
|
||||
}
|
||||
|
||||
fn gradual_difficulty(
|
||||
|
||||
@@ -175,7 +175,7 @@ mod tests {
|
||||
assert_eq!(next_gradual, next_gradual_3rd);
|
||||
}
|
||||
|
||||
let mut regular_calc = TaikoPerformance::from_map(converted.as_owned())
|
||||
let mut regular_calc = TaikoPerformance::new(converted.as_owned())
|
||||
.difficulty(difficulty.clone())
|
||||
.passed_objects(i as u32)
|
||||
.state(state);
|
||||
|
||||
+106
-71
@@ -1,14 +1,14 @@
|
||||
use std::cmp;
|
||||
|
||||
use crate::{
|
||||
any::{AttributeProvider, Difficulty, DifficultyAttributes, HitResultPriority},
|
||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance},
|
||||
osu::OsuPerformance,
|
||||
util::{map_or_attrs::MapOrAttrs, mods::Mods},
|
||||
Performance,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attributes::{TaikoDifficultyAttributes, TaikoPerformanceAttributes},
|
||||
convert::TaikoBeatmap,
|
||||
score_state::TaikoScoreState,
|
||||
Taiko,
|
||||
};
|
||||
@@ -32,55 +32,38 @@ pub struct TaikoPerformance<'map> {
|
||||
impl<'map> TaikoPerformance<'map> {
|
||||
/// Create a new performance calculator for osu!taiko maps.
|
||||
///
|
||||
/// Note that creating [`TaikoPerformance`] this way will require to
|
||||
/// perform the costly computation of [`TaikoDifficultyAttributes`]
|
||||
/// internally. If difficulty attributes for the current [`Difficulty`]
|
||||
/// settings are already available, consider using [`from_attributes`] or
|
||||
/// [`try_from_attributes`] instead.
|
||||
/// The argument `map_or_attrs` must be either
|
||||
/// - previously calculated attributes ([`TaikoDifficultyAttributes`]
|
||||
/// or [`TaikoPerformanceAttributes`])
|
||||
/// - a beatmap ([`TaikoBeatmap<'map>`])
|
||||
///
|
||||
/// [`from_attributes`]: Self::from_attributes
|
||||
/// [`try_from_attributes`]: Self::try_from_attributes
|
||||
pub const fn from_map(map: TaikoBeatmap<'map>) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Map(map),
|
||||
difficulty: Difficulty::new(),
|
||||
combo: None,
|
||||
acc: None,
|
||||
misses: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
/// If a map is given, difficulty attributes will need to be calculated
|
||||
/// internally which is a costly operation. Hence, passing attributes
|
||||
/// should be prefered.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// [`TaikoBeatmap<'map>`]: crate::taiko::TaikoBeatmap
|
||||
pub fn new(map_or_attrs: impl IntoModePerformance<'map, Taiko>) -> Self {
|
||||
map_or_attrs.into_performance()
|
||||
}
|
||||
|
||||
/// Create a new performance calculator from difficulty attributes.
|
||||
/// Try to create a new performance calculator for osu!taiko maps.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
pub const fn from_attributes(attrs: TaikoDifficultyAttributes) -> Self {
|
||||
Self {
|
||||
map_or_attrs: MapOrAttrs::Attrs(attrs),
|
||||
difficulty: Difficulty::new(),
|
||||
combo: None,
|
||||
acc: None,
|
||||
misses: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to create a new performance calculator from difficulty attributes.
|
||||
/// Returns `None` if `map_or_attrs` does not belong to osu!taiko e.g.
|
||||
/// a [`Converted`], [`DifficultyAttributes`], or [`PerformanceAttributes`]
|
||||
/// of a different mode.
|
||||
///
|
||||
/// Note that `attrs` must have been calculated for the same beatmap and
|
||||
/// [`Difficulty`] settings, otherwise the final attributes will be
|
||||
/// incorrect.
|
||||
/// See [`TaikoPerformance::new`] for more information.
|
||||
///
|
||||
/// Returns `None` if `attrs` contained attributes of a different mode.
|
||||
pub fn try_from_attributes(attrs: impl AttributeProvider) -> Option<Self> {
|
||||
if let DifficultyAttributes::Taiko(attrs) = attrs.attributes() {
|
||||
Some(Self::from_attributes(attrs))
|
||||
/// [`Converted`]: crate::model::beatmap::Converted
|
||||
/// [`DifficultyAttributes`]: crate::any::DifficultyAttributes
|
||||
/// [`PerformanceAttributes`]: crate::any::PerformanceAttributes
|
||||
pub fn try_new(map_or_attrs: impl IntoPerformance<'map>) -> Option<Self> {
|
||||
if let Performance::Taiko(calc) = map_or_attrs.into_performance() {
|
||||
Some(calc)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -326,6 +309,19 @@ impl<'map> TaikoPerformance<'map> {
|
||||
|
||||
inner.calculate()
|
||||
}
|
||||
|
||||
pub(crate) const fn from_map_or_attrs(map_or_attrs: MapOrAttrs<'map, Taiko>) -> Self {
|
||||
Self {
|
||||
map_or_attrs,
|
||||
difficulty: Difficulty::new(),
|
||||
combo: None,
|
||||
acc: None,
|
||||
misses: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
hitresult_priority: HitResultPriority::DEFAULT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> TryFrom<OsuPerformance<'map>> for TaikoPerformance<'map> {
|
||||
@@ -333,12 +329,9 @@ impl<'map> TryFrom<OsuPerformance<'map>> for TaikoPerformance<'map> {
|
||||
|
||||
/// Try to create [`TaikoPerformance`] through [`OsuPerformance`].
|
||||
///
|
||||
/// Returns `None` if [`OsuPerformance`] already replaced its internal
|
||||
/// beatmap with [`OsuDifficultyAttributes`], i.e. if
|
||||
/// [`OsuPerformance::from_attributes`] or [`OsuPerformance::generate_state`]
|
||||
/// was called.
|
||||
///
|
||||
/// [`OsuDifficultyAttributes`]: crate::osu::OsuDifficultyAttributes
|
||||
/// Returns `None` if [`OsuPerformance`] does not contain a beatmap, i.e.
|
||||
/// if it was constructed through attributes or
|
||||
/// [`OsuPerformance::generate_state`] was called.
|
||||
fn try_from(mut osu: OsuPerformance<'map>) -> Result<Self, Self::Error> {
|
||||
let MapOrAttrs::Map(converted) = osu.map_or_attrs else {
|
||||
return Err(osu);
|
||||
@@ -378,21 +371,9 @@ impl<'map> TryFrom<OsuPerformance<'map>> for TaikoPerformance<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> From<TaikoBeatmap<'map>> for TaikoPerformance<'map> {
|
||||
fn from(map: TaikoBeatmap<'map>) -> Self {
|
||||
Self::from_map(map)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaikoDifficultyAttributes> for TaikoPerformance<'_> {
|
||||
fn from(attrs: TaikoDifficultyAttributes) -> Self {
|
||||
Self::from_attributes(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaikoPerformanceAttributes> for TaikoPerformance<'_> {
|
||||
fn from(attrs: TaikoPerformanceAttributes) -> Self {
|
||||
Self::from_attributes(attrs.difficulty)
|
||||
impl<'map, T: IntoModePerformance<'map, Taiko>> From<T> for TaikoPerformance<'map> {
|
||||
fn from(into: T) -> Self {
|
||||
into.into_performance()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,8 +509,13 @@ mod test {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use proptest::prelude::*;
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
use crate::Beatmap;
|
||||
use crate::{
|
||||
any::{DifficultyAttributes, PerformanceAttributes},
|
||||
osu::{Osu, OsuDifficultyAttributes, OsuPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -537,13 +523,14 @@ mod test {
|
||||
|
||||
const MAX_COMBO: u32 = 289;
|
||||
|
||||
fn beatmap() -> Beatmap {
|
||||
Beatmap::from_path("./resources/1028484.osu").unwrap()
|
||||
}
|
||||
|
||||
fn attrs() -> TaikoDifficultyAttributes {
|
||||
ATTRS
|
||||
.get_or_init(|| {
|
||||
let converted = Beatmap::from_path("./resources/1028484.osu")
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<Taiko>();
|
||||
|
||||
let converted = beatmap().unchecked_into_converted::<Taiko>();
|
||||
let attrs = Difficulty::new().with_mode().calculate(&converted);
|
||||
|
||||
assert_eq!(MAX_COMBO, attrs.max_combo);
|
||||
@@ -698,4 +685,52 @@ mod test {
|
||||
|
||||
assert_eq!(state, expected);
|
||||
}
|
||||
|
||||
#[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::try_new(TaikoDifficultyAttributes::default()).unwrap();
|
||||
let _ = TaikoPerformance::try_new(TaikoPerformanceAttributes::default()).unwrap();
|
||||
let _ = TaikoPerformance::try_new(DifficultyAttributes::Taiko(
|
||||
TaikoDifficultyAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = TaikoPerformance::try_new(PerformanceAttributes::Taiko(
|
||||
TaikoPerformanceAttributes::default(),
|
||||
))
|
||||
.unwrap();
|
||||
let _ = TaikoPerformance::try_new(&converted).unwrap();
|
||||
let _ = TaikoPerformance::try_new(converted.as_owned()).unwrap();
|
||||
|
||||
let _ = TaikoPerformance::from(TaikoDifficultyAttributes::default());
|
||||
let _ = TaikoPerformance::from(TaikoPerformanceAttributes::default());
|
||||
let _ = TaikoPerformance::from(&converted);
|
||||
let _ = TaikoPerformance::from(converted);
|
||||
|
||||
let _ = TaikoDifficultyAttributes::default().performance();
|
||||
let _ = TaikoPerformanceAttributes::default().performance();
|
||||
|
||||
map.mode = GameMode::Osu;
|
||||
let converted = map.unchecked_as_converted::<Osu>();
|
||||
|
||||
assert!(TaikoPerformance::try_new(OsuDifficultyAttributes::default()).is_none());
|
||||
assert!(TaikoPerformance::try_new(OsuPerformanceAttributes::default()).is_none());
|
||||
assert!(TaikoPerformance::try_new(DifficultyAttributes::Osu(
|
||||
OsuDifficultyAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(TaikoPerformance::try_new(PerformanceAttributes::Osu(
|
||||
OsuPerformanceAttributes::default()
|
||||
))
|
||||
.is_none());
|
||||
assert!(TaikoPerformance::try_new(&converted).is_none());
|
||||
assert!(TaikoPerformance::try_new(converted).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ impl<'map, M: IGameMode> MapOrAttrs<'map, M> {
|
||||
/// If `self` is of variant `Map`, store `attrs` in `self`, and return a
|
||||
/// mutable reference to it.
|
||||
pub fn insert_attrs(&mut self, attrs: M::DifficultyAttributes) -> &mut M::DifficultyAttributes {
|
||||
// TODO: dont match, just overwrite
|
||||
match self {
|
||||
MapOrAttrs::Map(_) => {
|
||||
*self = Self::Attrs(attrs);
|
||||
@@ -67,3 +68,56 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map, M: IGameMode> From<Converted<'map, M>> for MapOrAttrs<'map, M> {
|
||||
fn from(converted: Converted<'map, M>) -> Self {
|
||||
Self::Map(converted)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_attrs {
|
||||
(
|
||||
$(
|
||||
$module:ident {
|
||||
$mode:ident, $diff:ident, $perf:ident
|
||||
}
|
||||
,)*
|
||||
) => {
|
||||
$(
|
||||
impl From<crate::$module::$diff> for MapOrAttrs<'_, crate::$module::$mode> {
|
||||
fn from(attrs: crate::$module::$diff) -> Self {
|
||||
Self::Attrs(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::$module::$perf> for MapOrAttrs<'_, crate::$module::$mode> {
|
||||
fn from(attrs: crate::$module::$perf) -> Self {
|
||||
Self::Attrs(attrs.difficulty)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
from_attrs!(
|
||||
osu {
|
||||
Osu,
|
||||
OsuDifficultyAttributes,
|
||||
OsuPerformanceAttributes
|
||||
},
|
||||
taiko {
|
||||
Taiko,
|
||||
TaikoDifficultyAttributes,
|
||||
TaikoPerformanceAttributes
|
||||
},
|
||||
catch {
|
||||
Catch,
|
||||
CatchDifficultyAttributes,
|
||||
CatchPerformanceAttributes
|
||||
},
|
||||
mania {
|
||||
Mania,
|
||||
ManiaDifficultyAttributes,
|
||||
ManiaPerformanceAttributes
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user