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