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.

Deduplicate Fx event serialization.

Also improved tests slightly.

+119 -79
+11
Cargo.lock
··· 160 160 dependencies = [ 161 161 "bevy_color", 162 162 "bevy_reflect", 163 + "indexmap", 164 + "ordered-float", 163 165 "serde", 164 166 "serde_json", 165 167 "simple-easing", ··· 390 392 version = "1.21.3" 391 393 source = "registry+https://github.com/rust-lang/crates.io-index" 392 394 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 395 + 396 + [[package]] 397 + name = "ordered-float" 398 + version = "5.0.0" 399 + source = "registry+https://github.com/rust-lang/crates.io-index" 400 + checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" 401 + dependencies = [ 402 + "num-traits", 403 + ] 393 404 394 405 [[package]] 395 406 name = "parking_lot"
+2
Cargo.toml
··· 18 18 "std", 19 19 ] } 20 20 bevy_reflect = { version = "0.16.1", optional = true, default-features = false } 21 + indexmap = "2.10.0" 22 + ordered-float = "5.0.0" 21 23 serde = { version = "1.0.219", features = ["derive"] } 22 24 serde_json = "1.0.140" 23 25 simple-easing = "1.0.1"
+104 -77
src/difficulty/lightshow/group/fx.rs
··· 9 9 use crate::difficulty::lightshow::filter::Filter; 10 10 use crate::utils::LooseBool; 11 11 use crate::{TransitionType, impl_event_box, impl_event_data, impl_event_group, impl_timed}; 12 + use indexmap::IndexSet; 13 + use ordered_float::OrderedFloat; 12 14 use serde::ser::SerializeStruct; 13 15 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 14 16 ··· 23 25 pub event_boxes: Vec<FxEventBox>, 24 26 } 25 27 28 + /// The format that is actually stored in JSON. 26 29 #[derive(Deserialize)] 27 30 struct FxEventInput { 28 31 #[serde(rename = "vfxEventBoxGroups")] ··· 98 101 where 99 102 S: Serializer, 100 103 { 101 - let mut event_data: Vec<FxEventData> = Vec::new(); 104 + let mut event_data: IndexSet<FxEventDataKey> = IndexSet::new(); 102 105 103 106 // Todo Deduplicate. 104 107 let event_boxes_raw: Vec<_> = self ··· 111 114 .map(|g| { 112 115 let mut ids = Vec::new(); 113 116 for data in &g.data { 114 - ids.push(event_data.len()); 115 - event_data.push(data.clone()); 117 + let data: FxEventDataKey = data.into(); 118 + let index = match event_data.get_index_of(&data) { 119 + Some(index) => index, 120 + None => { 121 + let index = event_data.len(); 122 + event_data.insert(data); 123 + index 124 + } 125 + }; 126 + ids.push(index); 116 127 } 117 128 118 129 FxEventGroupRaw { ··· 136 147 }) 137 148 .collect(); 138 149 139 - // Phase 2: Serialize both arrays as a struct 140 150 let mut state = serializer.serialize_struct("FxEventContainer", 2)?; 141 151 state.serialize_field("vfxEventBoxGroups", &event_boxes_raw)?; 142 - state.serialize_field("_fxEventsCollection", &FxEventArrays { event_data })?; 152 + state.serialize_field( 153 + "_fxEventsCollection", 154 + &FxEventArrays { 155 + event_data: event_data.into_iter().map(FxEventData::from).collect(), 156 + }, 157 + )?; 143 158 state.end() 144 159 } 145 160 } ··· 290 305 pub value: f32, 291 306 } 292 307 308 + /// A `PartialEq` and `Hash` version of [`FxEventData`], allowing for deduplication. 309 + #[derive(Debug, Clone, Eq, PartialEq, Hash)] 310 + struct FxEventDataKey { 311 + beat_offset: OrderedFloat<f32>, 312 + transition_type: TransitionType, 313 + easing: Easing, 314 + value: OrderedFloat<f32>, 315 + } 316 + 317 + impl From<&FxEventData> for FxEventDataKey { 318 + fn from(value: &FxEventData) -> Self { 319 + Self { 320 + beat_offset: value.beat_offset.into(), 321 + transition_type: value.transition_type, 322 + easing: value.easing, 323 + value: value.value.into(), 324 + } 325 + } 326 + } 327 + 328 + impl From<FxEventDataKey> for FxEventData { 329 + fn from(value: FxEventDataKey) -> Self { 330 + Self { 331 + beat_offset: value.beat_offset.into(), 332 + transition_type: value.transition_type, 333 + easing: value.easing, 334 + value: value.value.into(), 335 + } 336 + } 337 + } 338 + 293 339 impl Default for FxEventData { 294 340 fn default() -> Self { 295 341 Self { ··· 306 352 #[cfg(test)] 307 353 mod tests { 308 354 use super::*; 355 + use serde_json::{Value, json}; 309 356 310 - #[test] 311 - fn test_deserialize() { 312 - let json = r#" 357 + fn get_test_container() -> FxEventContainer { 358 + FxEventContainer { 359 + event_boxes: vec![FxEventBox { 360 + beat: 2.0, 361 + group_id: 0, 362 + groups: vec![get_test_group(), get_test_group()], 363 + }], 364 + } 365 + } 366 + 367 + fn get_test_group() -> FxEventGroup { 368 + FxEventGroup { 369 + filter: Default::default(), 370 + beat_dist_type: DistributionType::Wave, 371 + beat_dist_value: 1.0, 372 + fx_dist_type: DistributionType::Wave, 373 + fx_dist_value: 1.0, 374 + fx_dist_effect_first: LooseBool::True, 375 + fx_dist_easing: Some(Easing::None), 376 + data: vec![FxEventData { 377 + beat_offset: 0.0, 378 + transition_type: TransitionType::Transition, 379 + easing: Easing::Linear, 380 + value: 100.0, 381 + }], 382 + } 383 + } 384 + 385 + fn get_test_json() -> Value { 386 + json!( 313 387 { 314 388 "vfxEventBoxGroups": 315 389 [ ··· 341 415 [ 342 416 0 343 417 ] 344 - } 345 - ] 346 - } 347 - ], 348 - "_fxEventsCollection": 349 - { 350 - "_fl": 351 - [ 352 - { 353 - "b": 0.0, 354 - "p": 0, 355 - "i": 0, 356 - "v": 100.0 357 - } 358 - ], 359 - "_il": 360 - [] 361 - } 362 - } 363 - "#; 364 - 365 - let container: FxEventContainer = 366 - serde_json::from_str(json).expect("Failed to deserialize"); 367 - 368 - assert_eq!( 369 - container, 370 - FxEventContainer { 371 - event_boxes: vec![FxEventBox { 372 - beat: 2.0, 373 - group_id: 0, 374 - groups: vec![FxEventGroup { 375 - filter: Default::default(), 376 - beat_dist_type: DistributionType::Wave, 377 - beat_dist_value: 1.0, 378 - fx_dist_type: DistributionType::Wave, 379 - fx_dist_value: 1.0, 380 - fx_dist_effect_first: LooseBool::True, 381 - fx_dist_easing: Some(Easing::None), 382 - data: vec![FxEventData { 383 - beat_offset: 0.0, 384 - transition_type: TransitionType::Transition, 385 - easing: Easing::Linear, 386 - value: 100.0, 387 - }], 388 - }], 389 - }], 390 - } 391 - ); 392 - } 393 - 394 - #[test] 395 - fn test_roundtrip() { 396 - let json = r#" 397 - { 398 - "vfxEventBoxGroups": 399 - [ 400 - { 401 - "b": 2.0, 402 - "g": 0, 403 - "e": 404 - [ 418 + }, 405 419 { 406 420 "f": 407 421 { ··· 439 453 "i": 0, 440 454 "v": 100.0 441 455 } 442 - ], 443 - "_il": 444 - [] 456 + ] 445 457 } 446 458 } 447 - "#; 459 + ) 460 + } 461 + 462 + #[test] 463 + fn test_deserialize() { 464 + let container: FxEventContainer = 465 + serde_json::from_value(get_test_json()).expect("Failed to deserialize"); 466 + 467 + assert_eq!(container, get_test_container()); 468 + } 469 + 470 + #[test] 471 + fn test_serialize() { 472 + let out_json = serde_json::to_value(&get_test_container()).expect("Failed to serialize"); 448 473 449 - // Deserialize into your parsed container 474 + assert_eq!(out_json, get_test_json()); 475 + } 476 + 477 + #[test] 478 + fn test_roundtrip() { 450 479 let container: FxEventContainer = 451 - serde_json::from_str(json).expect("Failed to deserialize"); 480 + serde_json::from_value(get_test_json()).expect("Failed to deserialize"); 452 481 453 - // Serialize it back 454 482 let out_json = serde_json::to_string_pretty(&container).expect("Failed to serialize"); 455 483 456 - // Re-deserialize and compare 457 484 let roundtrip: FxEventContainer = 458 485 serde_json::from_str(&out_json).expect("Re-deserialization failed"); 459 486
+2 -2
src/utils.rs
··· 13 13 ),+ $(,)? 14 14 } 15 15 ) => { 16 - #[derive(Debug, Clone, Eq, PartialEq)] 16 + #[derive(Debug, Clone, Eq, PartialEq, Hash)] 17 17 #[cfg_attr( 18 18 feature = "bevy_reflect", 19 19 derive(bevy_reflect::Reflect), ··· 82 82 ),+ $(,)? 83 83 } 84 84 ) => { 85 - #[derive(Debug, Clone, Eq, PartialEq)] 85 + #[derive(Debug, Clone, Eq, PartialEq, Hash)] 86 86 #[cfg_attr( 87 87 feature = "bevy_reflect", 88 88 derive(bevy_reflect::Reflect),