1use std::{cmp, error, fmt, slice};
2
3use rosu_map::{
4 section::{
5 difficulty::{Difficulty, DifficultyKey, ParseDifficultyError},
6 events::{BreakPeriod, EventType, ParseEventTypeError},
7 general::{GameMode, GeneralKey, ParseGameModeError},
8 hit_objects::{
9 hit_samples::{HitSoundType, ParseHitSoundTypeError},
10 HitObjectType, ParseHitObjectTypeError, PathControlPoint, PathType,
11 },
12 timing_points::{ControlPoint, EffectFlags, ParseEffectFlagsError},
13 },
14 util::{KeyValue, ParseNumber, ParseNumberError, Pos, StrExt, MAX_PARSE_VALUE},
15 DecodeBeatmap, DecodeState,
16};
17
18use crate::{
19 model::{
20 control_point::{
21 difficulty_point_at, effect_point_at, DifficultyPoint, EffectPoint, TimingPoint,
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 if state.mode == GameMode::Mania {
313 sort::osu_legacy(&mut state.hit_objects);
314 }
315
316 Beatmap {
317 version: state.version,
318 is_convert: false,
319 stack_leniency: state.stack_leniency,
320 mode: state.mode,
321 ar: approach_rate,
322 cs: circle_size,
323 hp: hp_drain_rate,
324 od: overall_difficulty,
325 slider_multiplier,
326 slider_tick_rate,
327 breaks: state.breaks,
328 timing_points: state.timing_points,
329 difficulty_points: state.difficulty_points,
330 effect_points: state.effect_points,
331 hit_objects: state.hit_objects,
332 hit_sounds: state.hit_sounds,
333 }
334 }
335}
336
337#[derive(Debug)]
339pub enum ParseBeatmapError {
340 EffectFlags(ParseEffectFlagsError),
341 EventType(ParseEventTypeError),
342 HitObjectType(ParseHitObjectTypeError),
343 HitSoundType(ParseHitSoundTypeError),
344 InvalidEventLine,
345 InvalidRepeatCount,
346 InvalidTimingPointLine,
347 InvalidHitObjectLine,
348 Mode(ParseGameModeError),
349 Number(ParseNumberError),
350 TimeSignature,
351 TimingControlPointNaN,
352 UnknownHitObjectType,
353}
354
355impl error::Error for ParseBeatmapError {
356 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
357 match self {
358 ParseBeatmapError::EffectFlags(err) => Some(err),
359 ParseBeatmapError::EventType(err) => Some(err),
360 ParseBeatmapError::HitObjectType(err) => Some(err),
361 ParseBeatmapError::HitSoundType(err) => Some(err),
362 ParseBeatmapError::Mode(err) => Some(err),
363 ParseBeatmapError::Number(err) => Some(err),
364 ParseBeatmapError::InvalidEventLine
365 | ParseBeatmapError::InvalidRepeatCount
366 | ParseBeatmapError::InvalidTimingPointLine
367 | ParseBeatmapError::InvalidHitObjectLine
368 | ParseBeatmapError::TimeSignature
369 | ParseBeatmapError::TimingControlPointNaN
370 | ParseBeatmapError::UnknownHitObjectType => None,
371 }
372 }
373}
374
375impl fmt::Display for ParseBeatmapError {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 let s = match self {
378 Self::EffectFlags(_) => "failed to parse effect flags",
379 Self::EventType(_) => "failed to parse event type",
380 Self::HitObjectType(_) => "failed to parse hit object type",
381 Self::HitSoundType(_) => "failed to parse hit sound type",
382 Self::InvalidEventLine => "invalid event line",
383 Self::InvalidRepeatCount => "repeat count is way too high",
384 Self::InvalidTimingPointLine => "invalid timing point line",
385 Self::InvalidHitObjectLine => "invalid hit object line",
386 Self::Mode(_) => "failed to parse mode",
387 Self::Number(_) => "failed to parse number",
388 Self::TimeSignature => "invalid time signature, must be positive integer",
389 Self::TimingControlPointNaN => "beat length cannot be NaN in a timing control point",
390 Self::UnknownHitObjectType => "unknown hit object type",
391 };
392
393 f.write_str(s)
394 }
395}
396
397impl From<ParseEffectFlagsError> for ParseBeatmapError {
398 fn from(err: ParseEffectFlagsError) -> Self {
399 Self::EffectFlags(err)
400 }
401}
402
403impl From<ParseEventTypeError> for ParseBeatmapError {
404 fn from(err: ParseEventTypeError) -> Self {
405 Self::EventType(err)
406 }
407}
408
409impl From<ParseHitObjectTypeError> for ParseBeatmapError {
410 fn from(err: ParseHitObjectTypeError) -> Self {
411 Self::HitObjectType(err)
412 }
413}
414
415impl From<ParseHitSoundTypeError> for ParseBeatmapError {
416 fn from(err: ParseHitSoundTypeError) -> Self {
417 Self::HitSoundType(err)
418 }
419}
420
421impl From<ParseGameModeError> for ParseBeatmapError {
422 fn from(err: ParseGameModeError) -> Self {
423 Self::Mode(err)
424 }
425}
426
427impl From<ParseNumberError> for ParseBeatmapError {
428 fn from(err: ParseNumberError) -> Self {
429 Self::Number(err)
430 }
431}
432
433impl From<ParseDifficultyError> for ParseBeatmapError {
434 fn from(err: ParseDifficultyError) -> Self {
435 match err {
436 ParseDifficultyError::Number(err) => Self::Number(err),
437 }
438 }
439}
440
441const MAX_COORDINATE_VALUE: i32 = 131_072;
442
443impl DecodeBeatmap for Beatmap {
444 type Error = ParseBeatmapError;
445 type State = BeatmapState;
446
447 fn parse_general(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
448 let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
449 return Ok(());
450 };
451
452 match key {
453 GeneralKey::StackLeniency => state.stack_leniency = value.parse_num()?,
454 GeneralKey::Mode => state.mode = value.parse()?,
455 _ => {}
456 }
457
458 Ok(())
459 }
460
461 fn parse_editor(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
462 Ok(())
463 }
464
465 fn parse_metadata(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
466 Ok(())
467 }
468
469 fn parse_difficulty(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
470 let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
471 return Ok(());
472 };
473
474 match key {
475 DifficultyKey::HPDrainRate => state.difficulty.hp_drain_rate = value.parse_num()?,
476 DifficultyKey::CircleSize => state.difficulty.circle_size = value.parse_num()?,
477 DifficultyKey::OverallDifficulty => {
478 state.difficulty.overall_difficulty = value.parse_num()?;
479
480 if !state.has_approach_rate {
481 state.difficulty.approach_rate = state.difficulty.overall_difficulty;
482 }
483 }
484 DifficultyKey::ApproachRate => {
485 state.difficulty.approach_rate = value.parse_num()?;
486 state.has_approach_rate = true;
487 }
488 DifficultyKey::SliderMultiplier => {
489 state.difficulty.slider_multiplier = f64::parse(value)?;
490 }
491 DifficultyKey::SliderTickRate => state.difficulty.slider_tick_rate = f64::parse(value)?,
492 }
493
494 Ok(())
495 }
496
497 fn parse_events(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
498 let mut split = line.trim_comment().split(',');
499
500 let event_type: EventType = split
501 .next()
502 .ok_or(ParseBeatmapError::InvalidEventLine)?
503 .parse()?;
504
505 if event_type == EventType::Break {
506 let Some((start_time, end_time)) = split.next().zip(split.next()) else {
507 return Err(ParseBeatmapError::InvalidEventLine);
508 };
509
510 let start_time = f64::parse(start_time)?;
511 let end_time = start_time.max(f64::parse(end_time)?);
512
513 state.breaks.push(BreakPeriod {
514 start_time,
515 end_time,
516 });
517 }
518
519 Ok(())
520 }
521
522 fn parse_timing_points(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
523 let mut split = line.trim_comment().split(',');
524
525 let (time, beat_len) = split
526 .next()
527 .zip(split.next())
528 .ok_or(ParseBeatmapError::InvalidTimingPointLine)?;
529
530 let time = time.parse_num::<f64>()?;
531
532 let beat_len = beat_len
534 .trim()
535 .parse::<f64>()
536 .map_err(ParseNumberError::InvalidFloat)?;
537
538 if unlikely(beat_len < f64::from(-MAX_PARSE_VALUE)) {
539 return Err(ParseNumberError::NumberUnderflow.into());
540 } else if unlikely(beat_len > f64::from(MAX_PARSE_VALUE)) {
541 return Err(ParseNumberError::NumberOverflow.into());
542 }
543
544 let speed_multiplier = if beat_len < 0.0 {
545 100.0 / -beat_len
546 } else {
547 1.0
548 };
549
550 if let Some(numerator) = split.next() {
551 if unlikely(i32::parse(numerator)? < 1) {
552 return Err(ParseBeatmapError::TimeSignature);
553 }
554 }
555
556 let _ = split.next(); let _ = split.next(); let _ = split.next(); let timing_change = split
561 .next()
562 .is_none_or(|next| matches!(next.chars().next(), Some('1')));
563
564 let kiai = split
565 .next()
566 .map(str::parse::<EffectFlags>)
567 .transpose()?
568 .is_some_and(|flags| flags.has_flag(EffectFlags::KIAI));
569
570 if timing_change {
571 if unlikely(beat_len.is_nan()) {
572 return Err(ParseBeatmapError::TimingControlPointNaN);
573 }
574
575 let timing = TimingPoint::new(time, beat_len);
576 state.add_pending_point(time, timing, timing_change);
577 }
578
579 let difficulty = DifficultyPoint::new(time, beat_len, speed_multiplier);
580 state.add_pending_point(time, difficulty, timing_change);
581
582 let mut effect = EffectPoint::new(time, kiai);
583
584 if matches!(state.mode, GameMode::Taiko | GameMode::Mania) {
585 effect.scroll_speed = speed_multiplier.clamp(0.01, 10.0);
586 }
587
588 state.add_pending_point(time, effect, timing_change);
589
590 state.pending_control_points_time = time;
591
592 Ok(())
593 }
594
595 fn parse_colors(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
596 Ok(())
597 }
598
599 #[allow(clippy::too_many_lines)]
600 fn parse_hit_objects(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
601 let mut split = line.trim_comment().split(',');
602
603 let (Some(x), Some(y), Some(start_time), Some(kind), Some(sound_type)) = (
604 split.next(),
605 split.next(),
606 split.next(),
607 split.next(),
608 split.next(),
609 ) else {
610 return Err(ParseBeatmapError::InvalidHitObjectLine);
611 };
612
613 let pos = Pos {
614 x: x.parse_with_limits(MAX_COORDINATE_VALUE as f32)? as i32 as f32,
615 y: y.parse_with_limits(MAX_COORDINATE_VALUE as f32)? as i32 as f32,
616 };
617
618 let start_time = f64::parse(start_time)?;
619 let hit_object_type: HitObjectType = kind.parse()?;
620
621 let mut sound: HitSoundType = sound_type.parse()?;
622
623 let mut parse_custom_sound = |bank_info: Option<&str>| {
624 let mut split = match bank_info {
625 Some(s) if !s.is_empty() => s.split(':'),
626 _ => return Ok::<_, ParseNumberError>(()),
627 };
628
629 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() {
636 None | Some("") => {}
637 Some(_) => sound &= !HitSoundType::NORMAL,
640 }
641
642 Ok(())
643 };
644
645 let kind = if hit_object_type.has_flag(HitObjectType::CIRCLE) {
646 parse_custom_sound(split.next())?;
647
648 HitObjectKind::Circle
649 } else if hit_object_type.has_flag(HitObjectType::SLIDER) {
650 let (point_str, repeat_count) = split
651 .next()
652 .zip(split.next())
653 .ok_or(ParseBeatmapError::InvalidHitObjectLine)?;
654
655 let mut len = None;
656
657 let repeats = repeat_count.parse_num::<i32>()?;
658
659 if unlikely(repeats > 9000) {
660 return Err(ParseBeatmapError::InvalidRepeatCount);
661 }
662
663 let repeats = cmp::max(0, repeats - 1) as usize;
664
665 if let Some(next) = split.next() {
666 let new_len = next
667 .parse_with_limits(f64::from(MAX_COORDINATE_VALUE))?
668 .max(0.0);
669
670 if new_len.not_eq(0.0) {
671 len = Some(new_len);
672 }
673 }
674
675 let node_sounds_str = split.next();
676
677 let _ = split.next(); parse_custom_sound(split.next())?;
679
680 let mut node_sounds = vec![sound; repeats + 2].into_boxed_slice();
681
682 if let Some(sounds) = node_sounds_str {
683 sounds
684 .split('|')
685 .map(|s| s.parse().unwrap_or_default())
686 .zip(node_sounds.iter_mut())
687 .for_each(|(parsed, sound)| *sound = parsed);
688 }
689
690 state.convert_path_str(point_str, pos)?;
691 let mut control_points = Vec::with_capacity(state.curve_points.len());
692 control_points.append(&mut state.curve_points);
693
694 let slider = Slider {
695 expected_dist: len,
696 repeats,
697 control_points: control_points.into_boxed_slice(),
698 node_sounds,
699 };
700
701 HitObjectKind::Slider(slider)
702 } else if hit_object_type.has_flag(HitObjectType::SPINNER) {
703 let end_time = split
704 .next()
705 .ok_or(ParseBeatmapError::InvalidHitObjectLine)?
706 .parse_num::<f64>()?;
707
708 parse_custom_sound(split.next())?;
709
710 let duration = (end_time - start_time).max(0.0);
711
712 HitObjectKind::Spinner(Spinner { duration })
713 } else if hit_object_type.has_flag(HitObjectType::HOLD) {
714 let end_time = if let Some(s) = split.next().filter(|s| !s.is_empty()) {
715 let (end_time, bank_info) = s
716 .split_once(':')
717 .ok_or(ParseBeatmapError::InvalidHitObjectLine)?;
718
719 parse_custom_sound(Some(bank_info))?;
720
721 end_time.parse_num::<f64>()?.max(start_time)
722 } else {
723 start_time
724 };
725
726 let duration = end_time - start_time;
727
728 HitObjectKind::Hold(HoldNote { duration })
729 } else {
730 return Err(ParseBeatmapError::UnknownHitObjectType);
731 };
732
733 state.hit_objects.push(HitObject {
734 pos,
735 start_time,
736 kind,
737 });
738 state.hit_sounds.push(sound);
739
740 Ok(())
741 }
742
743 fn parse_variables(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
744 Ok(())
745 }
746
747 fn parse_catch_the_beat(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
748 Ok(())
749 }
750
751 fn parse_mania(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
752 Ok(())
753 }
754}
755
756trait Pending: Sized {
757 fn pending(state: &mut BeatmapState) -> &mut Option<Self>;
758
759 fn push_front(self, state: &mut BeatmapState) {
760 let pending = Self::pending(state);
761
762 if pending.is_none() {
763 *pending = Some(self);
764 }
765 }
766
767 fn push_back(self, state: &mut BeatmapState) {
768 *Self::pending(state) = Some(self);
769 }
770}
771
772impl Pending for TimingPoint {
773 fn pending(state: &mut BeatmapState) -> &mut Option<Self> {
774 &mut state.pending_timing_point
775 }
776}
777
778impl Pending for DifficultyPoint {
779 fn pending(state: &mut BeatmapState) -> &mut Option<Self> {
780 &mut state.pending_difficulty_point
781 }
782}
783
784impl Pending for EffectPoint {
785 fn pending(state: &mut BeatmapState) -> &mut Option<Self> {
786 &mut state.pending_effect_point
787 }
788}
789
790impl ControlPoint<BeatmapState> for TimingPoint {
791 fn check_already_existing(&self, _: &BeatmapState) -> bool {
792 false
793 }
794
795 fn add(self, state: &mut BeatmapState) {
796 match state
797 .timing_points
798 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
799 {
800 Err(i) => state.timing_points.insert(i, self),
801 Ok(i) => state.timing_points[i] = self,
802 }
803 }
804}
805
806impl ControlPoint<BeatmapState> for DifficultyPoint {
807 fn check_already_existing(&self, state: &BeatmapState) -> bool {
808 match difficulty_point_at(&state.difficulty_points, self.time) {
809 Some(existing) => self.is_redundant(existing),
810 None => self.is_redundant(&DifficultyPoint::default()),
811 }
812 }
813
814 fn add(self, state: &mut BeatmapState) {
815 match state
816 .difficulty_points
817 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
818 {
819 Err(i) => state.difficulty_points.insert(i, self),
820 Ok(i) => state.difficulty_points[i] = self,
821 }
822 }
823}
824
825impl ControlPoint<BeatmapState> for EffectPoint {
826 fn check_already_existing(&self, state: &BeatmapState) -> bool {
827 self.check_already_existing(&state.effect_points)
828 }
829
830 fn add(self, state: &mut BeatmapState) {
831 self.add(&mut state.effect_points);
832 }
833}
834
835impl ControlPoint<Vec<EffectPoint>> for EffectPoint {
837 fn check_already_existing(&self, effect_points: &Vec<EffectPoint>) -> bool {
838 match effect_point_at(effect_points, self.time) {
839 Some(existing) => self.is_redundant(existing),
840 None => self.is_redundant(&EffectPoint::default()),
841 }
842 }
843
844 fn add(self, effect_points: &mut Vec<EffectPoint>) {
845 match effect_points.binary_search_by(|probe| probe.time.total_cmp(&self.time)) {
846 Err(i) => effect_points.insert(i, self),
847 Ok(i) => effect_points[i] = self,
848 }
849 }
850}