Skip to main content

rosu_pp/model/beatmap/
decode.rs

1use std::{cmp, error, fmt, slice};
2
3use rosu_map::{
4    DecodeBeatmap, DecodeState,
5    section::{
6        difficulty::{Difficulty, DifficultyKey, ParseDifficultyError},
7        events::{BreakPeriod, EventType, ParseEventTypeError},
8        general::{GameMode, GeneralKey, ParseGameModeError},
9        hit_objects::{
10            HitObjectType, ParseHitObjectTypeError, PathControlPoint, PathType,
11            hit_samples::{HitSoundType, ParseHitSoundTypeError},
12        },
13        timing_points::{ControlPoint, EffectFlags, ParseEffectFlagsError},
14    },
15    util::{KeyValue, MAX_PARSE_VALUE, ParseNumber, ParseNumberError, Pos, StrExt},
16};
17
18use crate::{
19    model::{
20        control_point::{
21            DifficultyPoint, EffectPoint, TimingPoint, difficulty_point_at, effect_point_at,
22        },
23        hit_object::{HitObject, HitObjectKind, HoldNote, Slider, Spinner},
24    },
25    util::{float_ext::FloatExt, hint::unlikely, sort},
26};
27
28use super::{Beatmap, DEFAULT_SLIDER_LENIENCY};
29
30/// The state of a [`Beatmap`] for [`DecodeBeatmap`].
31pub struct BeatmapState {
32    version: i32,
33    stack_leniency: f32,
34    mode: GameMode,
35    has_approach_rate: bool,
36    difficulty: Difficulty,
37    breaks: Vec<BreakPeriod>,
38    timing_points: Vec<TimingPoint>,
39    difficulty_points: Vec<DifficultyPoint>,
40    effect_points: Vec<EffectPoint>,
41    hit_objects: Vec<HitObject>,
42    hit_sounds: Vec<HitSoundType>,
43
44    pending_control_points_time: f64,
45    pending_timing_point: Option<TimingPoint>,
46    pending_difficulty_point: Option<DifficultyPoint>,
47    pending_effect_point: Option<EffectPoint>,
48
49    curve_points: Vec<PathControlPoint>,
50    vertices: Vec<PathControlPoint>,
51    point_split: Vec<*const str>,
52}
53
54impl BeatmapState {
55    fn add_pending_point<P: Pending>(&mut self, time: f64, point: P, timing_change: bool) {
56        if time.not_eq(self.pending_control_points_time) {
57            self.flush_pending_points();
58        }
59
60        if timing_change {
61            point.push_front(self);
62        } else {
63            point.push_back(self);
64        }
65
66        self.pending_control_points_time = time;
67    }
68
69    fn flush_pending_points(&mut self) {
70        if let Some(point) = self.pending_timing_point.take() {
71            self.add_control_point(point);
72        }
73
74        if let Some(point) = self.pending_difficulty_point.take() {
75            self.add_control_point(point);
76        }
77
78        if let Some(point) = self.pending_effect_point.take() {
79            self.add_control_point(point);
80        }
81    }
82
83    fn add_control_point<P: ControlPoint<Self>>(&mut self, point: P) {
84        if !point.check_already_existing(self) {
85            point.add(self);
86        }
87    }
88
89    fn convert_path_str(&mut self, point_str: &str, offset: Pos) -> Result<(), ParseBeatmapError> {
90        let f = |this: &mut Self, point_split: &[&str]| {
91            let mut start_idx = 0;
92            let mut end_idx = 0;
93            let mut first = true;
94
95            while {
96                end_idx += 1;
97
98                end_idx < point_split.len()
99            } {
100                let is_letter = point_split[end_idx]
101                    .chars()
102                    .next()
103                    .ok_or(ParseBeatmapError::InvalidHitObjectLine)?
104                    .is_ascii_alphabetic();
105
106                if !is_letter {
107                    continue;
108                }
109
110                let end_point = point_split.get(end_idx + 1).copied();
111                this.convert_points(&point_split[start_idx..end_idx], end_point, first, offset)?;
112
113                start_idx = end_idx;
114                first = false;
115            }
116
117            if end_idx > start_idx {
118                this.convert_points(&point_split[start_idx..end_idx], None, first, offset)?;
119            }
120
121            Ok(())
122        };
123
124        self.point_split(point_str.split('|'), f)
125    }
126
127    fn convert_points(
128        &mut self,
129        points: &[&str],
130        end_point: Option<&str>,
131        first: bool,
132        offset: Pos,
133    ) -> Result<(), ParseBeatmapError> {
134        fn read_point(value: &str, start_pos: Pos) -> Result<PathControlPoint, ParseBeatmapError> {
135            let mut v = value
136                .split(':')
137                .map(|s| s.parse_with_limits(f64::from(MAX_COORDINATE_VALUE)));
138
139            let (x, y) = v
140                .next()
141                .zip(v.next())
142                .ok_or(ParseBeatmapError::InvalidHitObjectLine)?;
143
144            let pos = Pos::new(x? as i32 as f32, y? as i32 as f32);
145
146            Ok(PathControlPoint::new(pos - start_pos))
147        }
148
149        fn is_linear(p0: Pos, p1: Pos, p2: Pos) -> bool {
150            ((p1.y - p0.y) * (p2.x - p0.x)).eq((p1.x - p0.x) * (p2.y - p0.y))
151        }
152
153        let mut path_type = points
154            .first()
155            .copied()
156            .map(PathType::new_from_str)
157            .ok_or(ParseBeatmapError::InvalidHitObjectLine)?;
158
159        let read_offset = usize::from(first);
160        let readable_points = points.len() - 1;
161        let end_point_len = usize::from(end_point.is_some());
162
163        self.vertices.clear();
164        self.vertices
165            .reserve(read_offset + readable_points + end_point_len);
166
167        if first {
168            self.vertices.push(PathControlPoint::default());
169        }
170
171        for &point in points.iter().skip(1) {
172            self.vertices.push(read_point(point, offset)?);
173        }
174
175        if let Some(end_point) = end_point {
176            self.vertices.push(read_point(end_point, offset)?);
177        }
178
179        if path_type == PathType::PERFECT_CURVE {
180            if let [a, b, c] = self.vertices.as_slice() {
181                if is_linear(a.pos, b.pos, c.pos) {
182                    path_type = PathType::LINEAR;
183                }
184            } else {
185                path_type = PathType::BEZIER;
186            }
187        }
188
189        self.vertices
190            .first_mut()
191            .ok_or(ParseBeatmapError::InvalidHitObjectLine)?
192            .path_type = Some(path_type);
193
194        let mut start_idx = 0;
195        let mut end_idx = 0;
196
197        while {
198            end_idx += 1;
199
200            end_idx < self.vertices.len() - end_point_len
201        } {
202            if self.vertices[end_idx].pos != self.vertices[end_idx - 1].pos {
203                continue;
204            }
205
206            if path_type == PathType::CATMULL && end_idx > 1 {
207                continue;
208            }
209
210            if end_idx == self.vertices.len() - end_point_len - 1 {
211                continue;
212            }
213
214            self.vertices[end_idx - 1].path_type = Some(path_type);
215
216            self.curve_points.extend(&self.vertices[start_idx..end_idx]);
217
218            start_idx = end_idx + 1;
219        }
220
221        if end_idx > start_idx {
222            self.curve_points.extend(&self.vertices[start_idx..end_idx]);
223        }
224
225        Ok(())
226    }
227
228    fn point_split<'a, I, F, O>(&mut self, point_split: I, f: F) -> O
229    where
230        I: Iterator<Item = &'a str>,
231        F: FnOnce(&mut Self, &[&'a str]) -> O,
232    {
233        self.point_split.extend(point_split.map(std::ptr::from_ref));
234        let ptr = self.point_split.as_ptr();
235        let len = self.point_split.len();
236
237        // SAFETY:
238        // - *const str and &str have the same layout.
239        // - `self.point_split` is cleared after every use, ensuring that it
240        //   does not contain any invalid pointers.
241        let point_split = unsafe { slice::from_raw_parts(ptr.cast(), len) };
242        let res = f(self, point_split);
243        self.point_split.clear();
244
245        res
246    }
247}
248
249impl DecodeState for BeatmapState {
250    fn create(version: i32) -> Self {
251        Self {
252            version,
253            stack_leniency: DEFAULT_SLIDER_LENIENCY,
254            mode: GameMode::Osu,
255            has_approach_rate: false,
256            difficulty: Difficulty::default(),
257            breaks: Vec::new(),
258            timing_points: Vec::with_capacity(1),
259            difficulty_points: Vec::new(),
260            effect_points: Vec::with_capacity(32),
261            hit_objects: Vec::with_capacity(512),
262            hit_sounds: Vec::with_capacity(512),
263            pending_control_points_time: 0.0,
264            pending_timing_point: None,
265            pending_difficulty_point: None,
266            pending_effect_point: None,
267            // mean=13.11 | median=8
268            curve_points: Vec::with_capacity(8),
269            // mean=16.27 | median=8
270            vertices: Vec::with_capacity(8),
271            // mean=19.97 | median=8
272            point_split: Vec::with_capacity(8),
273        }
274    }
275}
276
277impl From<BeatmapState> for Beatmap {
278    fn from(mut state: BeatmapState) -> Self {
279        state.flush_pending_points();
280
281        let Difficulty {
282            mut hp_drain_rate,
283            mut circle_size,
284            mut overall_difficulty,
285            mut approach_rate,
286            mut slider_multiplier,
287            mut slider_tick_rate,
288        } = state.difficulty;
289
290        hp_drain_rate = hp_drain_rate.clamp(0.0, 10.0);
291
292        // * mania uses "circle size" for key count, thus different allowable range
293        circle_size = if state.mode == GameMode::Mania {
294            circle_size.clamp(1.0, 18.0)
295        } else {
296            circle_size.clamp(0.0, 10.0)
297        };
298
299        overall_difficulty = overall_difficulty.clamp(0.0, 10.0);
300        approach_rate = approach_rate.clamp(0.0, 10.0);
301
302        slider_multiplier = slider_multiplier.clamp(0.4, 3.6);
303        slider_tick_rate = slider_tick_rate.clamp(0.5, 8.0);
304
305        let mut sorter = sort::TandemSorter::new_stable(&state.hit_objects, |a, b| {
306            a.start_time.total_cmp(&b.start_time)
307        });
308
309        sorter.sort(&mut state.hit_objects);
310        sorter.sort(&mut state.hit_sounds);
311
312        Beatmap {
313            version: state.version,
314            is_convert: false,
315            stack_leniency: state.stack_leniency,
316            mode: state.mode,
317            ar: approach_rate,
318            cs: circle_size,
319            hp: hp_drain_rate,
320            od: overall_difficulty,
321            slider_multiplier,
322            slider_tick_rate,
323            breaks: state.breaks,
324            timing_points: state.timing_points,
325            difficulty_points: state.difficulty_points,
326            effect_points: state.effect_points,
327            hit_objects: state.hit_objects,
328            hit_sounds: state.hit_sounds,
329        }
330    }
331}
332
333/// All the ways that parsing a [`Beatmap`] can fail.
334#[derive(Debug)]
335pub enum ParseBeatmapError {
336    EffectFlags(ParseEffectFlagsError),
337    EventType(ParseEventTypeError),
338    HitObjectType(ParseHitObjectTypeError),
339    HitSoundType(ParseHitSoundTypeError),
340    InvalidEventLine,
341    InvalidRepeatCount,
342    InvalidTimingPointLine,
343    InvalidHitObjectLine,
344    Mode(ParseGameModeError),
345    Number(ParseNumberError),
346    TimeSignature,
347    TimingControlPointNaN,
348    UnknownHitObjectType,
349}
350
351impl error::Error for ParseBeatmapError {
352    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
353        match self {
354            ParseBeatmapError::EffectFlags(err) => Some(err),
355            ParseBeatmapError::EventType(err) => Some(err),
356            ParseBeatmapError::HitObjectType(err) => Some(err),
357            ParseBeatmapError::HitSoundType(err) => Some(err),
358            ParseBeatmapError::Mode(err) => Some(err),
359            ParseBeatmapError::Number(err) => Some(err),
360            ParseBeatmapError::InvalidEventLine
361            | ParseBeatmapError::InvalidRepeatCount
362            | ParseBeatmapError::InvalidTimingPointLine
363            | ParseBeatmapError::InvalidHitObjectLine
364            | ParseBeatmapError::TimeSignature
365            | ParseBeatmapError::TimingControlPointNaN
366            | ParseBeatmapError::UnknownHitObjectType => None,
367        }
368    }
369}
370
371impl fmt::Display for ParseBeatmapError {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        let s = match self {
374            Self::EffectFlags(_) => "failed to parse effect flags",
375            Self::EventType(_) => "failed to parse event type",
376            Self::HitObjectType(_) => "failed to parse hit object type",
377            Self::HitSoundType(_) => "failed to parse hit sound type",
378            Self::InvalidEventLine => "invalid event line",
379            Self::InvalidRepeatCount => "repeat count is way too high",
380            Self::InvalidTimingPointLine => "invalid timing point line",
381            Self::InvalidHitObjectLine => "invalid hit object line",
382            Self::Mode(_) => "failed to parse mode",
383            Self::Number(_) => "failed to parse number",
384            Self::TimeSignature => "invalid time signature, must be positive integer",
385            Self::TimingControlPointNaN => "beat length cannot be NaN in a timing control point",
386            Self::UnknownHitObjectType => "unknown hit object type",
387        };
388
389        f.write_str(s)
390    }
391}
392
393impl From<ParseEffectFlagsError> for ParseBeatmapError {
394    fn from(err: ParseEffectFlagsError) -> Self {
395        Self::EffectFlags(err)
396    }
397}
398
399impl From<ParseEventTypeError> for ParseBeatmapError {
400    fn from(err: ParseEventTypeError) -> Self {
401        Self::EventType(err)
402    }
403}
404
405impl From<ParseHitObjectTypeError> for ParseBeatmapError {
406    fn from(err: ParseHitObjectTypeError) -> Self {
407        Self::HitObjectType(err)
408    }
409}
410
411impl From<ParseHitSoundTypeError> for ParseBeatmapError {
412    fn from(err: ParseHitSoundTypeError) -> Self {
413        Self::HitSoundType(err)
414    }
415}
416
417impl From<ParseGameModeError> for ParseBeatmapError {
418    fn from(err: ParseGameModeError) -> Self {
419        Self::Mode(err)
420    }
421}
422
423impl From<ParseNumberError> for ParseBeatmapError {
424    fn from(err: ParseNumberError) -> Self {
425        Self::Number(err)
426    }
427}
428
429impl From<ParseDifficultyError> for ParseBeatmapError {
430    fn from(err: ParseDifficultyError) -> Self {
431        match err {
432            ParseDifficultyError::Number(err) => Self::Number(err),
433        }
434    }
435}
436
437const MAX_COORDINATE_VALUE: i32 = 131_072;
438
439impl DecodeBeatmap for Beatmap {
440    type Error = ParseBeatmapError;
441    type State = BeatmapState;
442
443    fn parse_general(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
444        let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
445            return Ok(());
446        };
447
448        match key {
449            GeneralKey::StackLeniency => state.stack_leniency = value.parse_num()?,
450            GeneralKey::Mode => state.mode = value.parse()?,
451            _ => {}
452        }
453
454        Ok(())
455    }
456
457    fn parse_editor(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
458        Ok(())
459    }
460
461    fn parse_metadata(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
462        Ok(())
463    }
464
465    fn parse_difficulty(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
466        let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
467            return Ok(());
468        };
469
470        match key {
471            DifficultyKey::HPDrainRate => state.difficulty.hp_drain_rate = value.parse_num()?,
472            DifficultyKey::CircleSize => state.difficulty.circle_size = value.parse_num()?,
473            DifficultyKey::OverallDifficulty => {
474                state.difficulty.overall_difficulty = value.parse_num()?;
475
476                if !state.has_approach_rate {
477                    state.difficulty.approach_rate = state.difficulty.overall_difficulty;
478                }
479            }
480            DifficultyKey::ApproachRate => {
481                state.difficulty.approach_rate = value.parse_num()?;
482                state.has_approach_rate = true;
483            }
484            DifficultyKey::SliderMultiplier => {
485                state.difficulty.slider_multiplier = f64::parse(value)?;
486            }
487            DifficultyKey::SliderTickRate => state.difficulty.slider_tick_rate = f64::parse(value)?,
488        }
489
490        Ok(())
491    }
492
493    fn parse_events(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
494        let mut split = line.trim_comment().split(',');
495
496        let event_type: EventType = split
497            .next()
498            .ok_or(ParseBeatmapError::InvalidEventLine)?
499            .parse()?;
500
501        if event_type == EventType::Break {
502            let Some((start_time, end_time)) = split.next().zip(split.next()) else {
503                return Err(ParseBeatmapError::InvalidEventLine);
504            };
505
506            let start_time = f64::parse(start_time)?;
507            let end_time = start_time.max(f64::parse(end_time)?);
508
509            state.breaks.push(BreakPeriod {
510                start_time,
511                end_time,
512            });
513        }
514
515        Ok(())
516    }
517
518    fn parse_timing_points(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
519        let mut split = line.trim_comment().split(',');
520
521        let (time, beat_len) = split
522            .next()
523            .zip(split.next())
524            .ok_or(ParseBeatmapError::InvalidTimingPointLine)?;
525
526        let time = time.parse_num::<f64>()?;
527
528        // Manual `str::parse_num::<f64>` so that NaN does not cause an error
529        let beat_len = beat_len
530            .trim()
531            .parse::<f64>()
532            .map_err(ParseNumberError::InvalidFloat)?;
533
534        if unlikely(beat_len < f64::from(-MAX_PARSE_VALUE)) {
535            return Err(ParseNumberError::NumberUnderflow.into());
536        } else if unlikely(beat_len > f64::from(MAX_PARSE_VALUE)) {
537            return Err(ParseNumberError::NumberOverflow.into());
538        }
539
540        let speed_multiplier = if beat_len < 0.0 {
541            100.0 / -beat_len
542        } else {
543            1.0
544        };
545
546        if let Some(numerator) = split.next()
547            && unlikely(i32::parse(numerator)? < 1)
548        {
549            return Err(ParseBeatmapError::TimeSignature);
550        }
551
552        let _ = split.next(); // sample set
553        let _ = split.next(); // custom sample bank
554        let _ = split.next(); // sample volume
555
556        let timing_change = split
557            .next()
558            .is_none_or(|next| matches!(next.chars().next(), Some('1')));
559
560        let kiai = split
561            .next()
562            .map(str::parse::<EffectFlags>)
563            .transpose()?
564            .is_some_and(|flags| flags.has_flag(EffectFlags::KIAI));
565
566        if timing_change {
567            if unlikely(beat_len.is_nan()) {
568                return Err(ParseBeatmapError::TimingControlPointNaN);
569            }
570
571            let timing = TimingPoint::new(time, beat_len);
572            state.add_pending_point(time, timing, timing_change);
573        }
574
575        let difficulty = DifficultyPoint::new(time, beat_len, speed_multiplier);
576        state.add_pending_point(time, difficulty, timing_change);
577
578        let mut effect = EffectPoint::new(time, kiai);
579
580        if matches!(state.mode, GameMode::Taiko | GameMode::Mania) {
581            effect.scroll_speed = speed_multiplier.clamp(0.01, 10.0);
582        }
583
584        state.add_pending_point(time, effect, timing_change);
585
586        state.pending_control_points_time = time;
587
588        Ok(())
589    }
590
591    fn parse_colors(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
592        Ok(())
593    }
594
595    #[expect(clippy::too_many_lines, reason = "staying in-sync with lazer")]
596    fn parse_hit_objects(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
597        let mut split = line.trim_comment().split(',');
598
599        let (Some(x), Some(y), Some(start_time), Some(kind), Some(sound_type)) = (
600            split.next(),
601            split.next(),
602            split.next(),
603            split.next(),
604            split.next(),
605        ) else {
606            return Err(ParseBeatmapError::InvalidHitObjectLine);
607        };
608
609        let pos = Pos {
610            x: x.parse_with_limits(MAX_COORDINATE_VALUE as f32)? as i32 as f32,
611            y: y.parse_with_limits(MAX_COORDINATE_VALUE as f32)? as i32 as f32,
612        };
613
614        let start_time = f64::parse(start_time)?;
615        let hit_object_type: HitObjectType = kind.parse()?;
616
617        let mut sound: HitSoundType = sound_type.parse()?;
618
619        let mut parse_custom_sound = |bank_info: Option<&str>| {
620            let mut split = match bank_info {
621                Some(s) if !s.is_empty() => s.split(':'),
622                _ => return Ok::<_, ParseNumberError>(()),
623            };
624
625            let _ = split.next().map(i32::parse).transpose()?; // normal bank
626            let _ = split.next().map(i32::parse).transpose()?; // additional bank
627            let _ = split.next().map(i32::parse).transpose()?; // custom sample bank
628            let _ = split.next().map(i32::parse).transpose()?; // volume
629
630            // filename
631            match split.next() {
632                None | Some("") => {}
633                // Relevant maps:
634                //   - /b/244784 at 43374
635                Some(_) => sound &= !HitSoundType::NORMAL,
636            }
637
638            Ok(())
639        };
640
641        let kind = if hit_object_type.has_flag(HitObjectType::CIRCLE) {
642            parse_custom_sound(split.next())?;
643
644            HitObjectKind::Circle
645        } else if hit_object_type.has_flag(HitObjectType::SLIDER) {
646            let (point_str, repeat_count) = split
647                .next()
648                .zip(split.next())
649                .ok_or(ParseBeatmapError::InvalidHitObjectLine)?;
650
651            let mut len = None;
652
653            let repeats = repeat_count.parse_num::<i32>()?;
654
655            if unlikely(repeats > 9000) {
656                return Err(ParseBeatmapError::InvalidRepeatCount);
657            }
658
659            let repeats = cmp::max(0, repeats - 1) as usize;
660
661            if let Some(next) = split.next() {
662                let new_len = next
663                    .parse_with_limits(f64::from(MAX_COORDINATE_VALUE))?
664                    .max(0.0);
665
666                if new_len.not_eq(0.0) {
667                    len = Some(new_len);
668                }
669            }
670
671            let node_sounds_str = split.next();
672
673            let _ = split.next(); // node banks
674            parse_custom_sound(split.next())?;
675
676            let mut node_sounds = vec![sound; repeats + 2].into_boxed_slice();
677
678            if let Some(sounds) = node_sounds_str {
679                sounds
680                    .split('|')
681                    .map(|s| s.parse().unwrap_or_default())
682                    .zip(node_sounds.iter_mut())
683                    .for_each(|(parsed, sound)| *sound = parsed);
684            }
685
686            state.convert_path_str(point_str, pos)?;
687            let mut control_points = Vec::with_capacity(state.curve_points.len());
688            control_points.append(&mut state.curve_points);
689
690            let slider = Slider {
691                expected_dist: len,
692                repeats,
693                control_points: control_points.into_boxed_slice(),
694                node_sounds,
695            };
696
697            HitObjectKind::Slider(slider)
698        } else if hit_object_type.has_flag(HitObjectType::SPINNER) {
699            let end_time = split
700                .next()
701                .ok_or(ParseBeatmapError::InvalidHitObjectLine)?
702                .parse_num::<f64>()?;
703
704            parse_custom_sound(split.next())?;
705
706            let duration = (end_time - start_time).max(0.0);
707
708            HitObjectKind::Spinner(Spinner { duration })
709        } else if hit_object_type.has_flag(HitObjectType::HOLD) {
710            let end_time = if let Some(s) = split.next().filter(|s| !s.is_empty()) {
711                let (end_time, bank_info) = s
712                    .split_once(':')
713                    .ok_or(ParseBeatmapError::InvalidHitObjectLine)?;
714
715                parse_custom_sound(Some(bank_info))?;
716
717                end_time.parse_num::<f64>()?.max(start_time)
718            } else {
719                start_time
720            };
721
722            let duration = end_time - start_time;
723
724            HitObjectKind::Hold(HoldNote { duration })
725        } else {
726            return Err(ParseBeatmapError::UnknownHitObjectType);
727        };
728
729        state.hit_objects.push(HitObject {
730            pos,
731            start_time,
732            kind,
733        });
734        state.hit_sounds.push(sound);
735
736        Ok(())
737    }
738
739    fn parse_variables(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
740        Ok(())
741    }
742
743    fn parse_catch_the_beat(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
744        Ok(())
745    }
746
747    fn parse_mania(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
748        Ok(())
749    }
750}
751
752trait Pending: Sized {
753    fn pending(state: &mut BeatmapState) -> &mut Option<Self>;
754
755    fn push_front(self, state: &mut BeatmapState) {
756        let pending = Self::pending(state);
757
758        if pending.is_none() {
759            *pending = Some(self);
760        }
761    }
762
763    fn push_back(self, state: &mut BeatmapState) {
764        *Self::pending(state) = Some(self);
765    }
766}
767
768impl Pending for TimingPoint {
769    fn pending(state: &mut BeatmapState) -> &mut Option<Self> {
770        &mut state.pending_timing_point
771    }
772}
773
774impl Pending for DifficultyPoint {
775    fn pending(state: &mut BeatmapState) -> &mut Option<Self> {
776        &mut state.pending_difficulty_point
777    }
778}
779
780impl Pending for EffectPoint {
781    fn pending(state: &mut BeatmapState) -> &mut Option<Self> {
782        &mut state.pending_effect_point
783    }
784}
785
786impl ControlPoint<BeatmapState> for TimingPoint {
787    fn check_already_existing(&self, _: &BeatmapState) -> bool {
788        false
789    }
790
791    fn add(self, state: &mut BeatmapState) {
792        match state
793            .timing_points
794            .binary_search_by(|probe| probe.time.total_cmp(&self.time))
795        {
796            Err(i) => state.timing_points.insert(i, self),
797            Ok(i) => state.timing_points[i] = self,
798        }
799    }
800}
801
802impl ControlPoint<BeatmapState> for DifficultyPoint {
803    fn check_already_existing(&self, state: &BeatmapState) -> bool {
804        match difficulty_point_at(&state.difficulty_points, self.time) {
805            Some(existing) => self.is_redundant(existing),
806            None => self.is_redundant(&DifficultyPoint::default()),
807        }
808    }
809
810    fn add(self, state: &mut BeatmapState) {
811        match state
812            .difficulty_points
813            .binary_search_by(|probe| probe.time.total_cmp(&self.time))
814        {
815            Err(i) => state.difficulty_points.insert(i, self),
816            Ok(i) => state.difficulty_points[i] = self,
817        }
818    }
819}
820
821impl ControlPoint<BeatmapState> for EffectPoint {
822    fn check_already_existing(&self, state: &BeatmapState) -> bool {
823        self.check_already_existing(&state.effect_points)
824    }
825
826    fn add(self, state: &mut BeatmapState) {
827        self.add(&mut state.effect_points);
828    }
829}
830
831// osu!taiko conversion mutates the list of effect points
832impl ControlPoint<Vec<EffectPoint>> for EffectPoint {
833    fn check_already_existing(&self, effect_points: &Vec<EffectPoint>) -> bool {
834        match effect_point_at(effect_points, self.time) {
835            Some(existing) => self.is_redundant(existing),
836            None => self.is_redundant(&EffectPoint::default()),
837        }
838    }
839
840    fn add(self, effect_points: &mut Vec<EffectPoint>) {
841        match effect_points.binary_search_by(|probe| probe.time.total_cmp(&self.time)) {
842            Err(i) => effect_points.insert(i, self),
843            Ok(i) => effect_points[i] = self,
844        }
845    }
846}