vsrg 0.3.0

Data structures for vertical scrolling rhythm games
Documentation
//! Common fixtures for relationship tests
//!
//! One pair of note types is shared by every relationship test suite. Each suite
//! should generates its own notes storage via [`crate::generate_notes_storage!`] with the
//! needed auxiliary fields and hooks.

use crate::math::Easing;
use crate::notes::{GroupId, Interval, LongNoteData, NoteGroup, NoteGroups, ShortNoteData};
use crate::rhythm::{
    ClockTime, ScrollSpeedChangeEvent, ScrollSpeedTrack, ScrollSpeedTracks, Tempo,
    TempoChangeEvent, TempoTrack, TempoTracks, Time, TrackId,
};
use soa_rs::{SoaClone, Soars};

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RelShort {
    pub(crate) time: Time,
    pub(crate) gr_id: GroupId,
    pub(crate) pos: u32,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RelLong {
    pub(crate) time: Interval<Time>,
    pub(crate) gr_id: GroupId,
    pub(crate) pos: u32,
}

#[derive(Clone, Copy, Soars, SoaClone)]
pub(crate) struct RelValue(pub(crate) u32);

#[derive(Debug, PartialEq, Clone, Soars, SoaClone, Default)]
pub(crate) struct RelRuntime {
    pub(crate) matching_ids: Vec<(u64, bool)>,
    pub(crate) chord: bool,
    pub(crate) chained: bool,
    pub(crate) linked: bool,
    pub(crate) parent_id: Option<u64>,
}

impl ShortNoteData for RelShort {
    type ValueData = RelValue;
    type RuntimeData = RelRuntime;

    fn time(&self) -> Time {
        self.time
    }

    fn group_id(&self) -> &GroupId {
        &self.gr_id
    }

    fn value_data(&self) -> Self::ValueData {
        RelValue(self.pos)
    }

    fn reconstruct<'a>(
        time: Time,
        data: <Self::ValueData as Soars>::Ref<'a>,
        gr_id: GroupId,
    ) -> Self {
        Self {
            time,
            gr_id,
            pos: *data.0,
        }
    }
}

impl LongNoteData for RelLong {
    type ValueData = RelValue;
    type RuntimeData = RelRuntime;

    fn start_time(&self) -> Time {
        self.time.start()
    }
    fn duration(&self) -> Time {
        generativity::make_guard!(guard);
        let (start, end) = self
            .time
            .start()
            .prove_same_unit(self.time.end(), guard)
            .expect("test interval endpoints use the same time unit");
        (end - start).as_time()
    }

    fn group_id(&self) -> &GroupId {
        &self.gr_id
    }

    fn value_data(&self) -> Self::ValueData {
        RelValue(self.pos)
    }

    fn reconstruct<'a>(
        start_time: Time,
        duration: Time,
        data: <Self::ValueData as Soars>::Ref<'a>,
        gr_id: GroupId,
    ) -> Self {
        generativity::make_guard!(guard);
        let (start, duration) = start_time
            .prove_same_unit(duration, guard)
            .expect("test note timing uses the same time unit");
        let time = Interval::try_new(start_time, (start + duration).as_time()).unwrap();
        Self {
            time,
            gr_id,
            pos: *data.0,
        }
    }
}

/// Note group where the named group `g{i}` uses the named tempo track `t{i}`
/// and the named scroll track `s{i}`.
#[derive(Debug, Clone)]
pub(crate) struct TestGroup {
    tempo_track_id: TrackId,
    scroll_track_id: TrackId,
}

impl NoteGroup for TestGroup {
    fn main_group() -> Self {
        Self {
            tempo_track_id: TrackId::Main,
            scroll_track_id: TrackId::Main,
        }
    }

    fn tempo_track_id(&self) -> &TrackId {
        &self.tempo_track_id
    }

    fn scroll_track_id(&self) -> &TrackId {
        &self.scroll_track_id
    }
}

/// Note groups and tempo/scroll tracks for a test, where the named group `g{i}` uses the named
/// tempo track `t{i}` and the named scroll track `s{i}`, alongside the main group and tracks.
#[derive(Debug, Clone)]
pub(crate) struct World {
    pub(crate) note_groups: NoteGroups<TestGroup>,
    pub(crate) tempo_tracks: TempoTracks,
    pub(crate) scroll_tracks: ScrollSpeedTracks,
}

fn test_tempo_track(bpm: f64) -> TempoTrack {
    TempoTrack::with_events(vec![TempoChangeEvent {
        time: Time::Clock(ClockTime::ZERO),
        tempo: Tempo::from_bpm(bpm).unwrap(),
        ease: Easing::InConst,
    }])
    .unwrap()
}

impl World {
    pub(crate) fn new(named_count: usize) -> Self {
        let tempo_tracks = TempoTracks::with_tracks(
            test_tempo_track(60.0),
            (0..named_count).map(|i| {
                (
                    format!("t{i}").into(),
                    test_tempo_track(60.0 + 10.0 * i as f64),
                )
            }),
        );
        let scroll_tracks = ScrollSpeedTracks::with_tracks(
            ScrollSpeedTrack::new(TrackId::Main),
            (0..named_count).map(|i| {
                (
                    format!("s{i}").into(),
                    ScrollSpeedTrack::with_events(
                        TrackId::Named(format!("t{i}").into()),
                        vec![
                            ScrollSpeedChangeEvent::new(
                                Time::Clock(ClockTime::ZERO),
                                1.0 + i as f64,
                                Easing::InConst,
                            )
                            .unwrap(),
                        ],
                        &tempo_tracks,
                    ),
                )
            }),
        );
        let note_groups = NoteGroups::with_groups(
            TestGroup::main_group(),
            (0..named_count).map(|i| {
                (
                    format!("g{i}").into(),
                    TestGroup {
                        tempo_track_id: TrackId::Named(format!("t{i}").into()),
                        scroll_track_id: TrackId::Named(format!("s{i}").into()),
                    },
                )
            }),
        );

        Self {
            note_groups,
            tempo_tracks,
            scroll_tracks,
        }
    }
}

pub(crate) fn clock_time(ms: f64) -> ClockTime {
    ClockTime::from_milis(ms).expect("test times must be valid")
}

/// A short note at `ms` milliseconds in the main group.
pub(crate) fn short_at(ms: f64, pos: u32) -> RelShort {
    RelShort {
        time: Time::Clock(clock_time(ms)),
        gr_id: GroupId::Main,
        pos,
    }
}

/// A long note over `[start_ms, start_ms + len_ms]` in the main group.
pub(crate) fn long_at(start_ms: f64, len_ms: f64, pos: u32) -> RelLong {
    RelLong {
        time: Interval::try_new(
            Time::Clock(clock_time(start_ms)),
            Time::Clock(clock_time(start_ms + len_ms)),
        )
        .expect("test endpoints use the same time unit"),
        gr_id: GroupId::Main,
        pos,
    }
}