move sound field of HitObject to Beatmap

This commit is contained in:
MaxOhn
2022-01-24 23:17:04 +01:00
parent 0989a34413
commit f689a21026
10 changed files with 111 additions and 54 deletions
+1
View File
@@ -1,6 +1,7 @@
## Upcoming ## Upcoming
- osu: Fix panic on maps with 0 objects - osu: Fix panic on maps with 0 objects
- [BREAKING] Store `HitObject::sound` in `Beatmap::sounds` instead to reduce the struct size
# v0.4.0 # v0.4.0
-2
View File
@@ -12,8 +12,6 @@ pub struct HitObject {
pub start_time: f64, pub start_time: f64,
/// The type of the object. /// The type of the object.
pub kind: HitObjectKind, pub kind: HitObjectKind,
/// The hitsound of the object. Used as color in osu!taiko.
pub sound: u8,
} }
impl HitObject { impl HitObject {
+4 -1
View File
@@ -608,8 +608,8 @@ macro_rules! parse_hitobjects_body {
pos, pos,
start_time: time, start_time: time,
kind, kind,
sound,
}); });
$self.sounds.push(sound);
prev_time = time; prev_time = time;
$buf.clear(); $buf.clear();
@@ -813,6 +813,9 @@ pub struct Beatmap {
pub tick_rate: f64, pub tick_rate: f64,
/// All hitobjects of the beatmap. /// All hitobjects of the beatmap.
pub hit_objects: Vec<HitObject>, pub hit_objects: Vec<HitObject>,
/// Store the sounds for all objects in their own Vec to minimize the struct size.
/// Hitsounds are only used in osu!taiko in which they represent color.
pub sounds: Vec<u8>,
#[cfg(not(feature = "sliders"))] #[cfg(not(feature = "sliders"))]
/// Beats per minute /// Beats per minute
+9 -10
View File
@@ -1,11 +1,10 @@
use super::{closest_rhythm, HitObjectRhythm}; use super::{closest_rhythm, taiko_object::TaikoObject, HitObjectRhythm};
use crate::parse::HitObject;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct DifficultyObject<'o> { pub(crate) struct DifficultyObject<'o> {
pub(crate) idx: usize, pub(crate) idx: usize,
pub(crate) base: &'o HitObject, pub(crate) base: TaikoObject<'o>,
pub(crate) prev: &'o HitObject, pub(crate) prev: TaikoObject<'o>,
pub(crate) delta: f64, pub(crate) delta: f64,
pub(crate) rhythm: &'static HitObjectRhythm, pub(crate) rhythm: &'static HitObjectRhythm,
pub(crate) start_time: f64, pub(crate) start_time: f64,
@@ -15,13 +14,13 @@ impl<'o> DifficultyObject<'o> {
#[inline] #[inline]
pub(crate) fn new( pub(crate) fn new(
idx: usize, idx: usize,
base: &'o HitObject, base: TaikoObject<'o>,
prev: &'o HitObject, prev: TaikoObject<'o>,
prev_prev: &HitObject, prev_prev: TaikoObject<'o>,
clock_rate: f64, clock_rate: f64,
) -> Self { ) -> Self {
let delta = (base.start_time - prev.start_time) / clock_rate; let delta = (base.h.start_time - prev.h.start_time) / clock_rate;
let rhythm = closest_rhythm(delta, prev, prev_prev, clock_rate); let rhythm = closest_rhythm(delta, prev.h, prev_prev.h, clock_rate);
Self { Self {
idx, idx,
@@ -29,7 +28,7 @@ impl<'o> DifficultyObject<'o> {
prev, prev,
delta, delta,
rhythm, rhythm,
start_time: base.start_time / clock_rate, start_time: base.h.start_time / clock_rate,
} }
} }
} }
+27 -18
View File
@@ -1,7 +1,6 @@
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
iter::{self, Enumerate, Skip, Zip}, iter::{self, Enumerate, Skip, Zip},
slice::Iter,
}; };
use crate::{ use crate::{
@@ -14,7 +13,11 @@ use crate::{
Beatmap, Mods, Beatmap, Mods,
}; };
use super::{skill::Skills, TaikoDifficultyAttributes}; use super::{
skill::Skills,
taiko_object::{IntoTaikoObjectIter, TaikoObjectIter},
TaikoDifficultyAttributes,
};
/// Gradually calculate the difficulty attributes of an osu!taiko map. /// Gradually calculate the difficulty attributes of an osu!taiko map.
/// ///
@@ -49,7 +52,7 @@ use super::{skill::Skills, TaikoDifficultyAttributes};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TaikoGradualDifficultyAttributes<'map> { pub struct TaikoGradualDifficultyAttributes<'map> {
pub(crate) idx: usize, pub(crate) idx: usize,
difficulty_objects: TaikoObjectIter<'map>, difficulty_objects: GradualTaikoObjectIter<'map>,
cheese: Vec<bool>, cheese: Vec<bool>,
skills: Skills, skills: Skills,
curr_section_end: f64, curr_section_end: f64,
@@ -64,7 +67,7 @@ impl<'map> TaikoGradualDifficultyAttributes<'map> {
let skills = Skills::new(); let skills = Skills::new();
let clock_rate = mods.speed(); let clock_rate = mods.speed();
let difficulty_objects = TaikoObjectIter::new(&map.hit_objects, clock_rate); let difficulty_objects = GradualTaikoObjectIter::new(map, clock_rate);
Self { Self {
idx: 0, idx: 0,
@@ -274,8 +277,8 @@ impl ExactSizeIterator for TaikoGradualDifficultyAttributes<'_> {
} }
type InnerIter<'map> = Zip< type InnerIter<'map> = Zip<
Zip<Skip<Enumerate<Iter<'map, HitObject>>>, Skip<Iter<'map, HitObject>>>, Zip<Skip<Enumerate<TaikoObjectIter<'map>>>, Skip<TaikoObjectIter<'map>>>,
Iter<'map, HitObject>, TaikoObjectIter<'map>,
>; >;
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -306,7 +309,7 @@ impl SimpleObject {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct TaikoObjectIter<'map> { struct GradualTaikoObjectIter<'map> {
hit_objects: InnerIter<'map>, hit_objects: InnerIter<'map>,
max_combo: usize, max_combo: usize,
clock_rate: f64, clock_rate: f64,
@@ -314,17 +317,23 @@ struct TaikoObjectIter<'map> {
second_object: SimpleObject, second_object: SimpleObject,
} }
impl<'map> TaikoObjectIter<'map> { impl<'map> GradualTaikoObjectIter<'map> {
fn new(hit_objects: &'map [HitObject], clock_rate: f64) -> Self { fn new(map: &'map Beatmap, clock_rate: f64) -> Self {
let first_object = hit_objects.get(0).map_or(SimpleObject::Empty, From::from); let first_object = map
let second_object = hit_objects.get(1).map_or(SimpleObject::Empty, From::from); .hit_objects
.get(0)
.map_or(SimpleObject::Empty, From::from);
let second_object = map
.hit_objects
.get(1)
.map_or(SimpleObject::Empty, From::from);
let hit_objects = hit_objects let hit_objects = map
.iter() .taiko_objects()
.enumerate() .enumerate()
.skip(2) .skip(2)
.zip(hit_objects.iter().skip(1)) .zip(map.taiko_objects().skip(1))
.zip(hit_objects.iter()); .zip(map.taiko_objects());
Self { Self {
hit_objects, hit_objects,
@@ -336,12 +345,12 @@ impl<'map> TaikoObjectIter<'map> {
} }
} }
impl<'map> Iterator for TaikoObjectIter<'map> { impl<'map> Iterator for GradualTaikoObjectIter<'map> {
type Item = DifficultyObject<'map>; type Item = DifficultyObject<'map>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let (((idx, base), prev), prev_prev) = self.hit_objects.next()?; let (((idx, base), prev), prev_prev) = self.hit_objects.next()?;
self.max_combo += base.is_circle() as usize; self.max_combo += base.h.is_circle() as usize;
Some(DifficultyObject::new( Some(DifficultyObject::new(
idx, idx,
@@ -358,7 +367,7 @@ impl<'map> Iterator for TaikoObjectIter<'map> {
} }
} }
impl ExactSizeIterator for TaikoObjectIter<'_> { impl ExactSizeIterator for GradualTaikoObjectIter<'_> {
#[inline] #[inline]
fn len(&self) -> usize { fn len(&self) -> usize {
self.hit_objects.len() self.hit_objects.len()
+6 -5
View File
@@ -10,6 +10,7 @@ mod rim;
mod skill; mod skill;
mod skill_kind; mod skill_kind;
mod stamina_cheese; mod stamina_cheese;
mod taiko_object;
use difficulty_object::DifficultyObject; use difficulty_object::DifficultyObject;
pub use gradual_difficulty::*; pub use gradual_difficulty::*;
@@ -20,6 +21,7 @@ pub use pp::*;
use rim::Rim; use rim::Rim;
use skill_kind::SkillKind; use skill_kind::SkillKind;
use stamina_cheese::StaminaCheeseDetector; use stamina_cheese::StaminaCheeseDetector;
use taiko_object::IntoTaikoObjectIter;
use crate::taiko::skill::Skills; use crate::taiko::skill::Skills;
use crate::{Beatmap, Mods, Strains}; use crate::{Beatmap, Mods, Strains};
@@ -118,14 +120,13 @@ fn calculate_skills(
} }
let mut hit_objects = map let mut hit_objects = map
.hit_objects .taiko_objects()
.iter()
.take(take) .take(take)
.enumerate() .enumerate()
.skip(2) .skip(2)
.zip(map.hit_objects.iter().skip(1)) .zip(map.taiko_objects().skip(1))
.zip(map.hit_objects.iter()) .zip(map.taiko_objects())
.inspect(|(((_, base), _), _)| max_combo += base.is_circle() as usize) .inspect(|(((_, base), _), _)| max_combo += base.h.is_circle() as usize)
.map(|(((idx, base), prev), prev_prev)| { .map(|(((idx, base), prev), prev_prev)| {
DifficultyObject::new(idx, base, prev, prev_prev, clock_rate) DifficultyObject::new(idx, base, prev, prev_prev, clock_rate)
}); });
+1 -8
View File
@@ -1,16 +1,9 @@
use crate::parse::{HitObject, HitSound}; use crate::parse::HitSound;
pub(crate) trait Rim { pub(crate) trait Rim {
fn is_rim(&self) -> bool; fn is_rim(&self) -> bool;
} }
impl Rim for HitObject {
#[inline]
fn is_rim(&self) -> bool {
self.sound.clap() || self.sound.whistle()
}
}
impl Rim for u8 { impl Rim for u8 {
#[inline] #[inline]
fn is_rim(&self) -> bool { fn is_rim(&self) -> bool {
+5 -5
View File
@@ -67,9 +67,9 @@ impl SkillKind {
prev_is_rim, prev_is_rim,
current_mono_len, current_mono_len,
} => { } => {
let prev_is_circle = current.prev.is_circle(); let prev_is_circle = current.prev.h.is_circle();
let base_is_circle = current.base.is_circle(); let base_is_circle = current.base.h.is_circle();
let curr_is_rim = current.base.is_rim(); let curr_is_rim = current.base.sound.is_rim();
if !(current.delta < 1000.0 && prev_is_circle && base_is_circle) { if !(current.delta < 1000.0 && prev_is_circle && base_is_circle) {
mono_history.clear(); mono_history.clear();
@@ -146,7 +146,7 @@ impl SkillKind {
notes_since_rhythm_change, notes_since_rhythm_change,
current_strain, current_strain,
} => { } => {
let base_is_circle = current.base.is_circle(); let base_is_circle = current.base.h.is_circle();
if !base_is_circle { if !base_is_circle {
*current_strain = 0.0; *current_strain = 0.0;
@@ -218,7 +218,7 @@ impl SkillKind {
note_pair_duration_history, note_pair_duration_history,
off_hand_object_duration, off_hand_object_duration,
} => { } => {
let base_is_circle = current.base.is_circle(); let base_is_circle = current.base.h.is_circle();
if !base_is_circle { if !base_is_circle {
return 0.0; return 0.0;
+5 -5
View File
@@ -1,5 +1,5 @@
use super::{LimitedQueue, Rim}; use super::{LimitedQueue, Rim};
use crate::{parse::HitObject, Beatmap}; use crate::Beatmap;
const ROLL_MIN_REPETITIONS: usize = 12; const ROLL_MIN_REPETITIONS: usize = 12;
const TL_MIN_REPETITIONS: isize = 16; const TL_MIN_REPETITIONS: isize = 16;
@@ -31,7 +31,7 @@ impl StaminaCheeseDetector for Beatmap {
let mut index_before_last_repeat = -1; let mut index_before_last_repeat = -1;
let mut last_mark_end = 0; let mut last_mark_end = 0;
for (i, h) in self.hit_objects.iter().enumerate() { for (i, &h) in self.sounds.iter().enumerate() {
history.push(h); history.push(h);
if !history.full() { if !history.full() {
@@ -62,8 +62,8 @@ impl StaminaCheeseDetector for Beatmap {
let mut tl_len = -2; let mut tl_len = -2;
let mut last_mark_end = 0; let mut last_mark_end = 0;
for (i, h) in self.hit_objects.iter().enumerate().skip(parity).step_by(2) { for (i, &sound) in self.sounds.iter().enumerate().skip(parity).step_by(2) {
if h.is_rim() == is_rin { if sound.is_rim() == is_rin {
tl_len += 2; tl_len += 2;
} else { } else {
tl_len = -2; tl_len = -2;
@@ -94,7 +94,7 @@ fn mark_as_cheese(start: usize, end: usize, cheese: &mut [bool]) {
} }
#[inline] #[inline]
fn contains_pattern_repeat(history: &LimitedQueue<&HitObject>, pattern_len: usize) -> bool { fn contains_pattern_repeat(history: &LimitedQueue<u8>, pattern_len: usize) -> bool {
for (&curr, &to_compare) in history.iter().zip(history.iter().skip(pattern_len)) { for (&curr, &to_compare) in history.iter().zip(history.iter().skip(pattern_len)) {
if curr.is_rim() != to_compare.is_rim() { if curr.is_rim() != to_compare.is_rim() {
return false; return false;
+53
View File
@@ -0,0 +1,53 @@
use std::slice::Iter;
use crate::{parse::HitObject, Beatmap};
#[derive(Copy, Clone, Debug)]
pub(crate) struct TaikoObject<'h> {
pub(crate) h: &'h HitObject,
pub(crate) sound: u8,
}
pub(crate) trait IntoTaikoObjectIter {
fn taiko_objects(&self) -> TaikoObjectIter<'_>;
}
#[derive(Clone, Debug)]
pub(crate) struct TaikoObjectIter<'m> {
hit_objects: Iter<'m, HitObject>,
sounds: Iter<'m, u8>,
}
impl IntoTaikoObjectIter for Beatmap {
#[inline]
fn taiko_objects(&self) -> TaikoObjectIter<'_> {
TaikoObjectIter {
hit_objects: self.hit_objects.iter(),
sounds: self.sounds.iter(),
}
}
}
impl<'m> Iterator for TaikoObjectIter<'m> {
type Item = TaikoObject<'m>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
Some(TaikoObject {
h: self.hit_objects.next()?,
sound: *self.sounds.next()?,
})
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.hit_objects.size_hint()
}
}
impl ExactSizeIterator for TaikoObjectIter<'_> {
#[inline]
fn len(&self) -> usize {
self.hit_objects.len()
}
}