gdlib 0.3.3

Rust library for editing Geometry Dash savefiles
Documentation
//! Unit tests for the crate
use std::time::Instant;

use crate::{
    gdlevel::Level,
    gdobj::{
        Event, GDObjAttributes, GDObjConfig, Group, MoveEasing,
        ids::{objects::TRIGGER_ADVANCED_RANDOM, properties::RANDOM_PROBABILITIES_LIST},
        misc::default_block,
        triggers::{self, DefaultMove, advanced_random_trigger, move_trigger},
    },
    rand::check_seed_advanced_random,
};

fn benchmark<F: Fn() -> R, R>(name: &str, f: F) -> R {
    let start = Instant::now();
    let result = f();
    println!(
        "{name}: {:.3}ms",
        start.elapsed().as_micros() as f64 / 1000.0
    );
    return result;
}

#[test]
fn read_objs() {
    let level = Level::from_gmd("test_gmds/All Object IDs.gmd").unwrap();
    let data = level.get_decrypted_data().unwrap();

    for (idx, obj) in data.objects.iter().enumerate() {
        println!("{idx}: {obj:?}");
    }
}

#[test]
fn move_constructor() {
    let mut level = Level::new("move trigger t3st", "gdlib", None, None);
    level.add_object(move_trigger(
        &GDObjConfig::default().pos(45.0, 45.0),
        triggers::MoveMode::Default(DefaultMove {
            dx: 45.0,
            dy: 54.0,
            x_lock: None,
            y_lock: None,
        }),
        17.38,
        679,
        false,
        true,
        Some((MoveEasing::ElasticInOut, 1.50)),
    ));

    level
        .export_to_gmd("test_gmds/generated_move_trigger_test.gmd")
        .unwrap();
}

#[test]
fn level_display_test() {
    let level = Level::from_gmd("test_gmds/big.gmd").unwrap();
    println!("Level info: {level}");
    println!(
        "Unused groups: {:?}",
        level.get_decrypted_data().unwrap().get_unused_groups()
    );
    println!(
        "Used groups: {:?}",
        level.get_decrypted_data().unwrap().get_used_groups()
    );
}

#[test]
fn obj_properties() {
    let config = GDObjConfig::new()
        .editor_layer_1(4)
        .set_attribute_flag(GDObjAttributes::dont_fade, true)
        .groups([2, 3, 1738])
        .set_attribute_flag(GDObjAttributes::extra_sticky, true)
        .set_attribute_flag(GDObjAttributes::no_glow, true)
        .set_z_layer(crate::gdobj::ZLayer::B3)
        .set_base_colour(crate::gdobj::ColourChannel::Background);

    let block = default_block(&config);
    let mut level = Level::new("porpeties", "gdlib", None, None);
    level.add_object(block);

    level
        .export_to_gmd("test_gmds/generated_properties.gmd")
        .unwrap();
}

#[test]
fn adv_random() {
    let mut level = Level::new("adv random", "gdlib", None, None);
    level.add_object(advanced_random_trigger(
        &GDObjConfig::default().pos(45.0, 45.0),
        vec![(50, 10), (60, 20), (70, 5), (80, 25), (90, 2)],
    ));
    let _ = level.export_to_gmd("test_gmds/generated_adv_random.gmd");
}

#[test]
fn big_level_parse() {
    let level = Level::from_gmd("test_gmds/big.gmd").unwrap();
    benchmark("Big level parse", || level.get_decrypted_data());
}

#[test]
fn ref_vs_copy_benchmark() {
    let count = 50;
    let mut ref_time: u128 = 0;
    let mut copy_time: u128 = 0;

    let level = Level::from_gmd("test_gmds/All Object IDs.gmd").unwrap();
    for _ in 0..count {
        {
            let start = Instant::now();
            let _ = level.get_decrypted_data();
            copy_time += start.elapsed().as_nanos();
        }
        {
            let mut level = Level::from_gmd("test_gmds/All Object IDs.gmd").unwrap();
            let start = Instant::now();
            let _ = level.get_decrypted_data_ref();
            ref_time += start.elapsed().as_nanos();
        }
    }
    let objs = level.get_decrypted_data().unwrap().objects.len();
    let avg_copy_time = copy_time as f64 / (1_000 * count) as f64;
    let avg_ref_time = ref_time as f64 / (1_000 * count) as f64;

    println!(
        "Objects: {objs}; {count} tests\nAverage copy time: {:.3}us\nAverage ref time: {:.3}us\nAverage copy time per object: {:.3}us\nAverage ref time per object: {:.3}us",
        avg_copy_time,
        avg_ref_time,
        avg_copy_time / objs as f64,
        avg_ref_time / objs as f64,
    );
}

#[test]
fn serialise_level_benchmark() {
    let mut level = Level::from_gmd("test_gmds/big.gmd").unwrap();
    level.decrypt_level_data();
    let _ = benchmark("big.gmd serialise", || {
        level.export_to_gmd("test_gmds/generated_big2.gmd")
    });
}

#[test]
fn event_trigger_test() {
    let mut level = Level::new("event trigger test", "gdlib", None, None);
    let cfg = GDObjConfig::new().pos(45.0, 45.0);
    level.add_object(crate::gdobj::triggers::event_trigger(
        &cfg,
        123,
        vec![Event::BallSwitch, Event::FallSpeedLow],
        0,
        triggers::ExtraID2::All,
    ));
}

#[test]
fn advanced_random_predict() {
    let level = Level::from_gmd("test_gmds/advrand test.gmd").unwrap();
    // find adv random trigger
    let data = level.get_decrypted_data().unwrap();
    let adv_rand = data
        .objects
        .iter()
        .find(|o| o.id == TRIGGER_ADVANCED_RANDOM)
        .unwrap();

    // get probabilities table
    let probabilities = adv_rand.get_property(RANDOM_PROBABILITIES_LIST).unwrap();

    // set input params
    let seed = 123;
    // predict outcome
    assert_eq!(
        check_seed_advanced_random(seed, &probabilities).unwrap(),
        Group::Regular(1)
    );
}

#[test]
#[ignore]
fn _temp_read_objs() {
    let level = Level::from_gmd("GMDS/spawn remap.gmd").unwrap();
    let data = level.get_decrypted_data().unwrap();

    for (idx, obj) in data.objects.iter().enumerate() {
        println!("{idx}: {obj:?}");
    }
}