use std::{fmt::Debug, fs, num::NonZeroI32}; use rosu_map::{ section::{ general::GameMode, hit_objects::{ HitObject, HitObjectKind, HitObjectSlider, PathControlPoint, PathType, SliderPath, }, }, util::Pos, Beatmap, }; use test_log::test; #[test] fn stability() { let mut bytes = Vec::with_capacity(4096); for entry in fs::read_dir("./resources").unwrap() { let entry = entry.unwrap(); let filename = entry.file_name(); let filename = filename.to_str().unwrap(); if !(filename.ends_with(".osu") || filename.ends_with(".osb")) { continue; } let mut decoded = Beatmap::from_path(entry.path()) .unwrap_or_else(|e| panic!("Failed to decode beatmap {filename:?}: {e:?}")); bytes.clear(); decoded .encode(&mut bytes) .unwrap_or_else(|e| panic!("Failed to encode beatmap {filename:?}: {e:?}")); let decoded_after_encode = Beatmap::from_bytes(&bytes).unwrap_or_else(|e| { panic!("Failed to decode beatmap after encoding {filename:?}: {e:?}") }); assert_eq_list( &decoded.control_points.timing_points, &decoded_after_encode.control_points.timing_points, filename, ); assert_eq_list( &decoded.control_points.effect_points, &decoded_after_encode.control_points.effect_points, filename, ); assert_eq_list( &decoded.hit_objects, &decoded_after_encode.hit_objects, filename, ); assert_eq!( decoded.custom_colors, decoded_after_encode.custom_colors, "{filename:?}" ); assert_eq!( decoded.custom_combo_colors, decoded_after_encode.custom_combo_colors, "{filename:?}" ); } #[track_caller] fn assert_eq_list(expected: &[T], actual: &[T], filename: &str) { if let Some(idx) = expected.iter().zip(actual).position(|(a, b)| a != b) { panic!( "[{idx}] filename: {filename:?}\nleft:\n{:?}\nright:\n{:?}", expected[idx], actual[idx] ); } } } #[test] fn bspline_curve_type() { let control_points = vec![ PathControlPoint { pos: Pos::new(0.0, 0.0), path_type: Some(PathType::new_b_spline(NonZeroI32::new(3).unwrap())), }, PathControlPoint { pos: Pos::new(50.0, 50.0), path_type: None, }, PathControlPoint { pos: Pos::new(100.0, 100.0), path_type: Some(PathType::new_b_spline(NonZeroI32::new(3).unwrap())), }, PathControlPoint { pos: Pos::new(150.0, 150.0), path_type: None, }, ]; let path = SliderPath::new(GameMode::Taiko, control_points, None); let slider = HitObjectSlider { pos: Pos::new(0.0, 0.0), new_combo: false, combo_offset: 0, path, node_samples: Vec::new(), repeat_count: 0, velocity: 0.0, }; let hit_object = HitObject { start_time: 0.0, kind: HitObjectKind::Slider(slider), samples: Vec::new(), }; let mut map = Beatmap { hit_objects: vec![hit_object], ..Default::default() }; let mut bytes = Vec::with_capacity(512); map.encode(&mut bytes).unwrap(); let decoded_after_encode = Beatmap::from_bytes(&bytes).unwrap(); let HitObjectKind::Slider(ref expected) = map.hit_objects[0].kind else { unreachable!() }; let HitObjectKind::Slider(ref actual) = decoded_after_encode.hit_objects[0].kind else { unreachable!() }; assert_eq!(actual.path.control_points().len(), 4); assert_eq!(expected.path.control_points(), actual.path.control_points()); } #[test] fn multi_segment_slider_with_floating_point_error() { let control_points = vec![ PathControlPoint { pos: Pos::new(0.0, 0.0), path_type: Some(PathType::BEZIER), }, PathControlPoint { pos: Pos::new(0.5, 0.5), path_type: None, }, PathControlPoint { pos: Pos::new(0.51, 0.51), path_type: None, }, PathControlPoint { pos: Pos::new(1.0, 1.0), path_type: Some(PathType::BEZIER), }, PathControlPoint { pos: Pos::new(2.0, 2.0), path_type: None, }, ]; let path = SliderPath::new(GameMode::Taiko, control_points, None); let slider = HitObjectSlider { pos: Pos::new(0.6, 0.6), new_combo: false, combo_offset: 0, path, node_samples: Vec::new(), repeat_count: 0, velocity: 0.0, }; let hit_object = HitObject { start_time: 0.0, kind: HitObjectKind::Slider(slider), samples: Vec::new(), }; let mut map = Beatmap { hit_objects: vec![hit_object], ..Default::default() }; let mut bytes = Vec::with_capacity(512); map.encode(&mut bytes).unwrap(); let decoded_after_encode = Beatmap::from_bytes(&bytes).unwrap(); let HitObjectKind::Slider(ref decoded_slider) = decoded_after_encode.hit_objects[0].kind else { unreachable!() }; assert_eq!(decoded_slider.path.control_points().len(), 5); }