feat: added struct ManiaOwnedGradualDifficulty

This commit is contained in:
MaxOhn
2023-11-09 10:34:13 +01:00
parent 70d589fb3d
commit 1aa6bcdb7a
3 changed files with 142 additions and 49 deletions
+139 -47
View File
@@ -49,8 +49,112 @@ use super::{
#[cfg_attr(docsrs, doc(cfg(feature = "gradual")))] #[cfg_attr(docsrs, doc(cfg(feature = "gradual")))]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ManiaGradualDifficulty<'map> { pub struct ManiaGradualDifficulty<'map> {
pub(crate) idx: usize,
map: Cow<'map, Beatmap>, map: Cow<'map, Beatmap>,
inner: ManiaGradualDifficultyInner,
}
impl<'map> ManiaGradualDifficulty<'map> {
/// Create a new difficulty attributes iterator for osu!mania maps.
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
let map = map.convert_mode(GameMode::Mania);
let is_convert = matches!(map, Cow::Owned(_));
let inner = ManiaGradualDifficultyInner::new(map.as_ref(), is_convert, mods);
Self { map, inner }
}
pub(crate) fn idx(&self) -> usize {
self.inner.idx
}
}
impl Iterator for ManiaGradualDifficulty<'_> {
type Item = ManiaDifficultyAttributes;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next(&self.map.hit_objects)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.inner.nth(n, &self.map.hit_objects)
}
}
impl ExactSizeIterator for ManiaGradualDifficulty<'_> {
#[inline]
fn len(&self) -> usize {
self.inner.len()
}
}
/// Gradually calculate the difficulty attributes of an osu!mania map.
///
/// Check [`ManiaGradualDifficulty`] for more information. This struct does the same
/// but includes an up-front clone to avoid being bound to a lifetime.
#[cfg_attr(docsrs, doc(cfg(feature = "gradual")))]
#[derive(Clone, Debug)]
pub struct ManiaOwnedGradualDifficulty {
hit_objects: Vec<HitObject>,
inner: ManiaGradualDifficultyInner,
}
impl ManiaOwnedGradualDifficulty {
/// Create a new owned difficulty attributes iterator for osu!mania maps.
pub fn new(map: &Beatmap, mods: u32) -> Self {
let map = map.convert_mode(GameMode::Mania);
let is_convert = matches!(map, Cow::Owned(_));
let inner = ManiaGradualDifficultyInner::new(&map, is_convert, mods);
let hit_objects = match map {
Cow::Owned(map) => map.hit_objects,
Cow::Borrowed(map) => map.hit_objects.clone(),
};
Self { hit_objects, inner }
}
#[allow(unused)]
pub(crate) fn idx(&self) -> usize {
self.inner.idx
}
}
impl Iterator for ManiaOwnedGradualDifficulty {
type Item = ManiaDifficultyAttributes;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next(&self.hit_objects)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.inner.nth(n, &self.hit_objects)
}
}
impl ExactSizeIterator for ManiaOwnedGradualDifficulty {
#[inline]
fn len(&self) -> usize {
self.inner.len()
}
}
#[derive(Clone, Debug)]
struct ManiaGradualDifficultyInner {
pub(crate) idx: usize,
hit_window: f64, hit_window: f64,
strain: Strain, strain: Strain,
diff_objects: Box<[ManiaDifficultyObject]>, diff_objects: Box<[ManiaDifficultyObject]>,
@@ -58,10 +162,8 @@ pub struct ManiaGradualDifficulty<'map> {
clock_rate: f64, clock_rate: f64,
} }
impl<'map> ManiaGradualDifficulty<'map> { impl ManiaGradualDifficultyInner {
/// Create a new difficulty attributes iterator for osu!mania maps. fn new(map: &Beatmap, is_convert: bool, mods: u32) -> Self {
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
let map = map.convert_mode(GameMode::Mania);
let total_columns = map.cs.round_even().max(1.0); let total_columns = map.cs.round_even().max(1.0);
let clock_rate = mods.clock_rate(); let clock_rate = mods.clock_rate();
let strain = Strain::new(total_columns as usize); let strain = Strain::new(total_columns as usize);
@@ -69,11 +171,11 @@ impl<'map> ManiaGradualDifficulty<'map> {
let BeatmapHitWindows { od: hit_window, .. } = map let BeatmapHitWindows { od: hit_window, .. } = map
.attributes() .attributes()
.mods(mods) .mods(mods)
.converted(matches!(map, Cow::Owned(_))) .converted(is_convert)
.clock_rate(clock_rate) .clock_rate(clock_rate)
.hit_windows(); .hit_windows();
let mut params = ObjectParameters::new(map.as_ref()); let mut params = ObjectParameters::new(map);
let mut curr_combo = 0; let mut curr_combo = 0;
let mut hit_objects = map.hit_objects.iter(); let mut hit_objects = map.hit_objects.iter();
@@ -81,7 +183,7 @@ impl<'map> ManiaGradualDifficulty<'map> {
Some(h) => { Some(h) => {
let hit_object = ManiaObject::new(h, total_columns, &mut params); let hit_object = ManiaObject::new(h, total_columns, &mut params);
Self::increment_combo_raw( increment_combo_raw(
h, h,
hit_object.start_time, hit_object.start_time,
hit_object.end_time, hit_object.end_time,
@@ -93,7 +195,6 @@ impl<'map> ManiaGradualDifficulty<'map> {
None => { None => {
return Self { return Self {
idx: 0, idx: 0,
map,
hit_window, hit_window,
strain, strain,
diff_objects: Box::from([]), diff_objects: Box::from([]),
@@ -118,7 +219,6 @@ impl<'map> ManiaGradualDifficulty<'map> {
Self { Self {
idx: 0, idx: 0,
map,
hit_window, hit_window,
strain, strain,
diff_objects: diff_objects.into_boxed_slice(), diff_objects: diff_objects.into_boxed_slice(),
@@ -127,32 +227,7 @@ impl<'map> ManiaGradualDifficulty<'map> {
} }
} }
fn increment_combo( fn next(&mut self, hit_objects: &[HitObject]) -> Option<ManiaDifficultyAttributes> {
h: &HitObject,
diff_obj: &ManiaDifficultyObject,
curr_combo: &mut usize,
clock_rate: f64,
) {
Self::increment_combo_raw(
h,
diff_obj.start_time * clock_rate,
diff_obj.end_time * clock_rate,
curr_combo,
);
}
fn increment_combo_raw(h: &HitObject, start_time: f64, end_time: f64, curr_combo: &mut usize) {
match h.kind {
HitObjectKind::Circle => *curr_combo += 1,
_ => *curr_combo += 1 + ((end_time - start_time) / 100.0) as usize,
}
}
}
impl Iterator for ManiaGradualDifficulty<'_> {
type Item = ManiaDifficultyAttributes;
fn next(&mut self) -> Option<Self::Item> {
// The first difficulty object belongs to the second note since each difficulty // The first difficulty object belongs to the second note since each difficulty
// object requires the current and the last note. Hence, if we're still on the first // object requires the current and the last note. Hence, if we're still on the first
// object, we don't have a difficulty object yet and just skip processing. // object, we don't have a difficulty object yet and just skip processing.
@@ -160,9 +235,9 @@ impl Iterator for ManiaGradualDifficulty<'_> {
let curr = self.diff_objects.get(self.idx - 1)?; let curr = self.diff_objects.get(self.idx - 1)?;
self.strain.process(curr, &self.diff_objects); self.strain.process(curr, &self.diff_objects);
let h = &self.map.hit_objects[self.idx]; let h = &hit_objects[self.idx];
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate); increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
} else if self.map.hit_objects.is_empty() { } else if hit_objects.is_empty() {
return None; return None;
} }
@@ -175,18 +250,17 @@ impl Iterator for ManiaGradualDifficulty<'_> {
}) })
} }
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len(); let len = self.len();
(len, Some(len)) (len, Some(len))
} }
fn nth(&mut self, n: usize) -> Option<Self::Item> { fn nth(&mut self, n: usize, hit_objects: &[HitObject]) -> Option<ManiaDifficultyAttributes> {
let skip_iter = self let skip_iter = self
.diff_objects .diff_objects
.iter() .iter()
.zip(self.map.hit_objects.iter().skip(1)) .zip(hit_objects.iter().skip(1))
.skip(self.idx.saturating_sub(1)); .skip(self.idx.saturating_sub(1));
let mut take = n.min(self.len().saturating_sub(1)); let mut take = n.min(self.len().saturating_sub(1));
@@ -198,18 +272,36 @@ impl Iterator for ManiaGradualDifficulty<'_> {
} }
for (curr, h) in skip_iter.take(take) { for (curr, h) in skip_iter.take(take) {
Self::increment_combo(h, curr, &mut self.curr_combo, self.clock_rate); increment_combo(h, curr, &mut self.curr_combo, self.clock_rate);
self.strain.process(curr, &self.diff_objects); self.strain.process(curr, &self.diff_objects);
self.idx += 1; self.idx += 1;
} }
self.next() self.next(hit_objects)
} }
}
impl ExactSizeIterator for ManiaGradualDifficulty<'_> {
#[inline]
fn len(&self) -> usize { fn len(&self) -> usize {
self.diff_objects.len() + 1 - self.idx self.diff_objects.len() + 1 - self.idx
} }
} }
fn increment_combo(
h: &HitObject,
diff_obj: &ManiaDifficultyObject,
curr_combo: &mut usize,
clock_rate: f64,
) {
increment_combo_raw(
h,
diff_obj.start_time * clock_rate,
diff_obj.end_time * clock_rate,
curr_combo,
);
}
fn increment_combo_raw(h: &HitObject, start_time: f64, end_time: f64, curr_combo: &mut usize) {
match h.kind {
HitObjectKind::Circle => *curr_combo += 1,
_ => *curr_combo += 1 + ((end_time - start_time) / 100.0) as usize,
}
}
+1 -1
View File
@@ -118,7 +118,7 @@ impl<'map> ManiaGradualPerformance<'map> {
.clone() .clone()
.attributes(difficulty) .attributes(difficulty)
.state(state) .state(state)
.passed_objects(self.difficulty.idx) .passed_objects(self.difficulty.idx())
.calculate(); .calculate();
Some(performance) Some(performance)
+2 -1
View File
@@ -17,7 +17,8 @@ pub use self::{mania_object::ManiaObject, pp::*, score_state::ManiaScoreState};
#[cfg(feature = "gradual")] #[cfg(feature = "gradual")]
pub use self::{ pub use self::{
gradual_difficulty::ManiaGradualDifficulty, gradual_performance::ManiaGradualPerformance, gradual_difficulty::{ManiaGradualDifficulty, ManiaOwnedGradualDifficulty},
gradual_performance::ManiaGradualPerformance,
}; };
pub(crate) use self::mania_object::ObjectParameters; pub(crate) use self::mania_object::ObjectParameters;