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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user