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
30pub 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 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 curve_points: Vec::with_capacity(8),
269 vertices: Vec::with_capacity(8),
271 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 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#[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 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(); let _ = split.next(); let _ = split.next(); 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()?; let _ = split.next().map(i32::parse).transpose()?; let _ = split.next().map(i32::parse).transpose()?; let _ = split.next().map(i32::parse).transpose()?; match split.next() {
632 None | Some("") => {}
633 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(); 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
831impl 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}