use std::{
borrow::Cow,
fmt::{Debug, Formatter, Result as FmtResult},
};
use crate::{
Beatmap, Difficulty,
any::CalculateError,
model::mode::{ConvertError, IGameMode},
};
pub enum MapOrAttrs<'map, M: IGameMode> {
Map(Cow<'map, Beatmap>),
Attrs(M::DifficultyAttributes),
}
impl<M: IGameMode> MapOrAttrs<'_, M> {
pub fn insert_attrs(&mut self, difficulty: &Difficulty) -> Result<(), ConvertError> {
match self {
Self::Map(map) => *self = Self::Attrs(difficulty.calculate_for_mode::<M>(map)?),
Self::Attrs(_) => {}
}
Ok(())
}
pub fn checked_insert_attrs(&mut self, difficulty: &Difficulty) -> Result<(), CalculateError> {
match self {
Self::Map(map) => *self = Self::Attrs(difficulty.checked_calculate_for_mode::<M>(map)?),
Self::Attrs(_) => {}
}
Ok(())
}
pub const unsafe fn get_attrs(&self) -> &M::DifficultyAttributes {
match self {
Self::Attrs(attrs) => attrs,
Self::Map(_) => unsafe { std::hint::unreachable_unchecked() },
}
}
pub unsafe fn into_attrs(self) -> M::DifficultyAttributes {
match self {
Self::Attrs(attrs) => attrs,
Self::Map(_) => unsafe { std::hint::unreachable_unchecked() },
}
}
}
impl<M> Clone for MapOrAttrs<'_, M>
where
M: IGameMode,
M::DifficultyAttributes: Clone,
{
fn clone(&self) -> Self {
match self {
Self::Map(map) => Self::Map(map.clone()),
Self::Attrs(attrs) => Self::Attrs(attrs.clone()),
}
}
}
impl<M> Debug for MapOrAttrs<'_, M>
where
M: IGameMode,
M::DifficultyAttributes: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
struct SlimBeatmap<'a>(&'a Beatmap);
impl Debug for SlimBeatmap<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let Beatmap {
version,
is_convert,
stack_leniency,
mode,
ar,
cs,
hp,
od,
slider_multiplier,
slider_tick_rate,
breaks,
timing_points,
difficulty_points,
effect_points,
hit_objects,
hit_sounds,
} = self.0;
f.debug_struct("Beatmap")
.field("version", version)
.field("is_convert", is_convert)
.field("stack_leniency", stack_leniency)
.field("mode", mode)
.field("ar", ar)
.field("cs", cs)
.field("hp", hp)
.field("od", od)
.field("slider_multiplier", slider_multiplier)
.field("slider_tick_rate", slider_tick_rate)
.field("breaks", &breaks.len())
.field("timing_points", &timing_points.len())
.field("difficulty_points", &difficulty_points.len())
.field("effect_points", &effect_points.len())
.field("hit_objects", &hit_objects.len())
.field("hit_sounds", &hit_sounds.len())
.finish()
}
}
match self {
Self::Map(map) => f.debug_tuple("Map").field(&SlimBeatmap(map)).finish(),
Self::Attrs(attrs) => f.debug_tuple("Attrs").field(attrs).finish(),
}
}
}
impl<M> PartialEq for MapOrAttrs<'_, M>
where
M: IGameMode,
M::DifficultyAttributes: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Map(a), Self::Map(b)) => a == b,
(Self::Attrs(a), Self::Attrs(b)) => a == b,
_ => false,
}
}
}
impl<'map, M: IGameMode> From<&'map Beatmap> for MapOrAttrs<'map, M> {
fn from(map: &'map Beatmap) -> Self {
Self::Map(Cow::Borrowed(map))
}
}
impl<M: IGameMode> From<Beatmap> for MapOrAttrs<'_, M> {
fn from(map: Beatmap) -> Self {
Self::Map(Cow::Owned(map))
}
}
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
},
);