move sound field of HitObject to Beatmap
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user