Beatsaber Rust Utilities: A Beatsaber V3 parsing library.
beatsaber beatmap
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Overcomplicated pass at Fx event parsing.

AlephCubed d51ab0d7 81e21111

+325 -12
+2
src/difficulty.rs
··· 46 46 pub color_event_boxes: Vec<ColorEventBox>, 47 47 #[serde(rename = "lightRotationEventBoxGroups")] 48 48 pub rotation_event_boxes: Vec<RotationEventBox>, 49 + #[serde(flatten)] 50 + pub fx_event_boxes: Option<FxEventContainer>, 49 51 /// > Only present in difficulty file V3.2 or higher. 50 52 #[serde(rename = "lightTranslationEventBoxGroups")] 51 53 pub translation_event_boxes: Option<Vec<TranslationEventBox>>,
+323 -12
src/difficulty/lightshow/group/fx.rs
··· 5 5 use crate::difficulty::lightshow::filter::Filter; 6 6 use crate::utils::LooseBool; 7 7 use crate::{TransitionType, impl_event_box, impl_event_data, impl_event_group, impl_timed}; 8 - use serde::{Deserialize, Serialize}; 8 + use serde::ser::SerializeStruct; 9 + use serde::{Deserialize, Deserializer, Serialize, Serializer}; 10 + 11 + #[derive(Debug, Clone, PartialEq, Default)] 12 + #[cfg_attr( 13 + feature = "bevy_reflect", 14 + derive(bevy_reflect::Reflect), 15 + reflect(Debug, Clone, PartialEq) 16 + )] 17 + pub struct FxEventContainer { 18 + pub event_boxes: Vec<FxEventBox>, 19 + } 20 + 21 + #[derive(Deserialize)] 22 + struct FxEventInput { 23 + #[serde(rename = "vfxEventBoxGroups")] 24 + event_boxes: Vec<FxEventBoxRaw>, 25 + #[serde(rename = "_fxEventsCollection")] 26 + arrays: FxEventArrays, 27 + } 28 + 29 + #[derive(Deserialize, Serialize)] 30 + struct FxEventArrays { 31 + #[serde(rename = "_fl")] 32 + event_data: Vec<FxEventData>, 33 + } 34 + 35 + impl<'de> Deserialize<'de> for FxEventContainer { 36 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 37 + where 38 + D: Deserializer<'de>, 39 + { 40 + let FxEventInput { 41 + event_boxes, 42 + arrays, 43 + } = FxEventInput::deserialize(deserializer)?; 44 + 45 + let event_boxes = event_boxes 46 + .into_iter() 47 + .map(|raw_box| { 48 + let groups = raw_box 49 + .groups 50 + .into_iter() 51 + .map(|g| { 52 + let data = g 53 + .data_ids 54 + .into_iter() 55 + .map(|id| { 56 + arrays.event_data.get(id).cloned().ok_or_else(|| { 57 + serde::de::Error::custom(format!( 58 + "Missing FxEventData with id {}", 59 + id 60 + )) 61 + }) 62 + }) 63 + .collect::<Result<Vec<_>, _>>()?; 64 + 65 + Ok(FxEventGroup { 66 + filter: g.filter, 67 + beat_dist_type: g.beat_dist_type, 68 + beat_dist_value: g.beat_dist_value, 69 + fx_dist_type: g.fx_dist_type, 70 + fx_dist_value: g.fx_dist_value, 71 + fx_dist_effect_first: g.fx_dist_effect_first, 72 + fx_dist_easing: g.fx_dist_easing, 73 + data, 74 + }) 75 + }) 76 + .collect::<Result<Vec<_>, _>>()?; 77 + 78 + Ok(FxEventBox { 79 + beat: raw_box.beat, 80 + group_id: raw_box.group_id, 81 + groups, 82 + }) 83 + }) 84 + .collect::<Result<Vec<_>, _>>()?; 85 + 86 + Ok(FxEventContainer { event_boxes }) 87 + } 88 + } 89 + 90 + // Todo avoid allocations. 91 + impl Serialize for FxEventContainer { 92 + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 93 + where 94 + S: Serializer, 95 + { 96 + let mut event_data: Vec<FxEventData> = Vec::new(); 97 + 98 + // Todo Deduplicate. 99 + let event_boxes_raw: Vec<_> = self 100 + .event_boxes 101 + .iter() 102 + .map(|b| { 103 + let groups_raw: Vec<_> = b 104 + .groups 105 + .iter() 106 + .map(|g| { 107 + let mut ids = Vec::new(); 108 + for data in &g.data { 109 + ids.push(event_data.len()); 110 + event_data.push(data.clone()); 111 + } 112 + 113 + FxEventGroupRaw { 114 + filter: g.filter.clone(), 115 + beat_dist_type: g.beat_dist_type.clone(), 116 + beat_dist_value: g.beat_dist_value, 117 + fx_dist_type: g.fx_dist_type.clone(), 118 + fx_dist_value: g.fx_dist_value, 119 + fx_dist_effect_first: g.fx_dist_effect_first, 120 + fx_dist_easing: g.fx_dist_easing.clone(), 121 + data_ids: ids, 122 + } 123 + }) 124 + .collect(); 125 + 126 + FxEventBoxRaw { 127 + beat: b.beat, 128 + group_id: b.group_id, 129 + groups: groups_raw, 130 + } 131 + }) 132 + .collect(); 133 + 134 + // Phase 2: Serialize both arrays as a struct 135 + let mut state = serializer.serialize_struct("FxEventContainer", 2)?; 136 + state.serialize_field("vfxEventBoxGroups", &event_boxes_raw)?; 137 + state.serialize_field("_fxEventsCollection", &FxEventArrays { event_data })?; 138 + state.end() 139 + } 140 + } 9 141 10 142 /// A collection of [`FxEventGroup`]s that share the same group ID and beat. 11 - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 143 + #[derive(Debug, Clone, PartialEq)] 12 144 #[cfg_attr( 13 145 feature = "bevy_reflect", 14 146 derive(bevy_reflect::Reflect), ··· 16 148 )] 17 149 pub struct FxEventBox { 18 150 /// The time the event takes place. 151 + pub beat: f32, 152 + /// The ID of the collection of objects that this event effects. 153 + pub group_id: i32, 154 + pub groups: Vec<FxEventGroup>, 155 + } 156 + 157 + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 158 + pub struct FxEventBoxRaw { 19 159 #[serde(rename = "b")] 20 160 pub beat: f32, 21 - /// The ID of the collection of objects that this event effects. 22 161 #[serde(rename = "g")] 23 162 pub group_id: i32, 24 163 #[serde(rename = "e")] 25 - pub groups: Vec<FxEventGroup>, 164 + pub groups: Vec<FxEventGroupRaw>, 26 165 } 27 166 28 167 impl Default for FxEventBox { ··· 39 178 impl_event_box!(FxEventBox, FxEventGroup, FxEventData); 40 179 41 180 /// A collection of [`FxEventData`] that share the same [`Filter`] and distribution. 42 - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 181 + #[derive(Debug, Clone, PartialEq)] 43 182 #[cfg_attr( 44 183 feature = "bevy_reflect", 45 184 derive(bevy_reflect::Reflect), 46 185 reflect(Debug, Clone, PartialEq) 47 186 )] 48 187 pub struct FxEventGroup { 49 - #[serde(rename = "f")] 50 188 pub filter: Filter, 51 - #[serde(rename = "d")] 52 189 pub beat_dist_type: DistributionType, 53 190 /// The strength of the beat distribution. Dependent on the [distribution type](Self::beat_dist_type). 54 191 /// 55 192 /// A value of zero will have no effect. 56 - #[serde(rename = "w")] 57 193 pub beat_dist_value: f32, 58 194 /// The strength of the brightness distribution. Dependent on the [distribution type](Self::bright_dist_type). 59 195 /// 60 196 /// A value of zero will have no effect. 197 + pub fx_dist_type: DistributionType, 198 + pub fx_dist_value: f32, 199 + /// Whether the first [`FxEventData`] of the group will be effected by brightness distribution. 200 + pub fx_dist_effect_first: LooseBool, 201 + pub fx_dist_easing: Option<Easing>, 202 + pub data: Vec<FxEventData>, 203 + } 204 + 205 + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 206 + pub struct FxEventGroupRaw { 207 + #[serde(rename = "f")] 208 + pub filter: Filter, 209 + #[serde(rename = "d")] 210 + pub beat_dist_type: DistributionType, 211 + #[serde(rename = "w")] 212 + pub beat_dist_value: f32, 61 213 #[serde(rename = "t")] 62 214 pub fx_dist_type: DistributionType, 63 - #[serde(rename = "r")] 215 + #[serde(rename = "s")] 64 216 pub fx_dist_value: f32, 65 - /// Whether the first [`FxEventData`] of the group will be effected by brightness distribution. 66 217 #[serde(rename = "b")] 67 218 pub fx_dist_effect_first: LooseBool, 68 219 #[serde(rename = "i")] 69 220 pub fx_dist_easing: Option<Easing>, 70 - #[serde(rename = "e")] 71 - pub data: Vec<FxEventData>, 221 + #[serde(rename = "l")] 222 + pub data_ids: Vec<usize>, 72 223 } 73 224 74 225 impl Default for FxEventGroup { ··· 138 289 } 139 290 140 291 impl_event_data!(FxEventData); 292 + 293 + #[cfg(test)] 294 + mod tests { 295 + use super::*; 296 + 297 + #[test] 298 + fn test_deserialize() { 299 + let json = r#" 300 + { 301 + "vfxEventBoxGroups": 302 + [ 303 + { 304 + "b": 2.0, 305 + "g": 0, 306 + "e": 307 + [ 308 + { 309 + "f": 310 + { 311 + "c": 0, 312 + "f": 1, 313 + "p": 1, 314 + "t": 0, 315 + "r": 0, 316 + "n": 0, 317 + "s": 0, 318 + "l": 1.0, 319 + "d": 0 320 + }, 321 + "w": 1.0, 322 + "d": 1, 323 + "s": 1.0, 324 + "t": 1, 325 + "b": 1, 326 + "i": -1, 327 + "l": 328 + [ 329 + 0 330 + ] 331 + } 332 + ] 333 + } 334 + ], 335 + "_fxEventsCollection": 336 + { 337 + "_fl": 338 + [ 339 + { 340 + "b": 0.0, 341 + "p": 0, 342 + "i": 0, 343 + "v": 100.0 344 + } 345 + ], 346 + "_il": 347 + [] 348 + } 349 + } 350 + "#; 351 + 352 + let container: FxEventContainer = 353 + serde_json::from_str(json).expect("Failed to deserialize"); 354 + 355 + assert_eq!( 356 + container, 357 + FxEventContainer { 358 + event_boxes: vec![FxEventBox { 359 + beat: 2.0, 360 + group_id: 0, 361 + groups: vec![FxEventGroup { 362 + filter: Default::default(), 363 + beat_dist_type: DistributionType::Wave, 364 + beat_dist_value: 1.0, 365 + fx_dist_type: DistributionType::Wave, 366 + fx_dist_value: 1.0, 367 + fx_dist_effect_first: LooseBool::True, 368 + fx_dist_easing: Some(Easing::None), 369 + data: vec![FxEventData { 370 + beat_offset: 0.0, 371 + transition_type: TransitionType::Transition, 372 + easing: Easing::Linear, 373 + value: 100.0, 374 + }], 375 + }], 376 + }], 377 + } 378 + ); 379 + } 380 + 381 + #[test] 382 + fn test_roundtrip() { 383 + let json = r#" 384 + { 385 + "vfxEventBoxGroups": 386 + [ 387 + { 388 + "b": 2.0, 389 + "g": 0, 390 + "e": 391 + [ 392 + { 393 + "f": 394 + { 395 + "c": 0, 396 + "f": 1, 397 + "p": 1, 398 + "t": 0, 399 + "r": 0, 400 + "n": 0, 401 + "s": 0, 402 + "l": 1.0, 403 + "d": 0 404 + }, 405 + "w": 1.0, 406 + "d": 1, 407 + "s": 1.0, 408 + "t": 1, 409 + "b": 1, 410 + "i": -1, 411 + "l": 412 + [ 413 + 0 414 + ] 415 + } 416 + ] 417 + } 418 + ], 419 + "_fxEventsCollection": 420 + { 421 + "_fl": 422 + [ 423 + { 424 + "b": 0.0, 425 + "p": 0, 426 + "i": 0, 427 + "v": 100.0 428 + } 429 + ], 430 + "_il": 431 + [] 432 + } 433 + } 434 + "#; 435 + 436 + // Deserialize into your parsed container 437 + let container: FxEventContainer = 438 + serde_json::from_str(json).expect("Failed to deserialize"); 439 + 440 + // Serialize it back 441 + let out_json = serde_json::to_string_pretty(&container).expect("Failed to serialize"); 442 + 443 + // Re-deserialize and compare 444 + let roundtrip: FxEventContainer = 445 + serde_json::from_str(&out_json).expect("Re-deserialization failed"); 446 + 447 + assert_eq!(container, roundtrip, "Round-trip did not match"); 448 + 449 + println!("Round-trip serialization succeeded:\n{}", out_json); 450 + } 451 + }