vsrg 0.1.0

Data structures for vertical scrolling rhythm games
Documentation
use crate::notes::NoteState;
use crate::notes::generator::notes_storage::{NotesStorage, NotesStorageEditHook};
use crate::notes::long::LongNoteTypeStorage;
use crate::notes::short::ShortNoteTypeStorage;

use super::test_common::{TestLong, TestShort, World, long_data, short_data};

// Verify that mutations made in `pre_add`/`pre_update` hooks are stored.
// `MutatePosHook` adds 1.0 to the note's `pos`.

crate::generate_notes_storage! {
    enum TestNoteType,

    #[derive(PartialEq)]
    enum TestNote,

    #[derive(Debug, PartialEq)]
    enum TestNoteSnapshot,

    enum TestNoteRef,

    enum TestNoteMut,

    #[derive(Debug, Clone)]
    struct TestNotesStorage {
        note_type Short(TestShort) => shorts: ShortNoteTypeStorage<TestShort>,
        note_type Long(TestLong) => longs: LongNoteTypeStorage<TestLong>,
        edit_hook MutatePosHook { amt: 1.0 },
    }
}

impl TestNotesStorage {
    fn health_check(&self) {
        self.shorts.health_check();
        self.longs.health_check();
    }
}

struct MutatePosHook {
    amt: f32,
}

#[test]
fn owned_conversions_release_notes_storage_borrows() {
    fn shared<S: NotesStorage>(
        storage: &S,
        ids: &[(u64, S::NoteTypeEnum)],
    ) -> Vec<S::NoteSnapshotEnum> {
        storage
            .query_notes_ref(ids.iter().cloned())
            .map(|note| note.to_snapshot())
            .collect()
    }
    fn mutable<S: NotesStorage>(
        storage: &mut S,
        ids: &[(u64, S::NoteTypeEnum)],
    ) -> Vec<S::NoteSnapshotEnum> {
        let mut notes = Vec::new();
        storage.query_notes_mut(ids.iter().cloned(), |note| notes.push(note.to_snapshot()));
        notes
    }
    use crate::notes::{GroupId, Interval};
    use crate::rhythm::{BeatTime, ClockTime, Time};
    let mut world = World::new(7);
    let mut short = short_data(2.0);
    short.time = Time::Beat(BeatTime::new(4.0).unwrap());
    short.gr_id = GroupId::Named("g6".into()); // 120 BPM
    let mut long = long_data(5.0);
    long.time = Interval::try_new(
        Time::Beat(BeatTime::new(2.0).unwrap()),
        Time::Beat(BeatTime::new(6.0).unwrap()),
    )
    .expect("test endpoints use the same time unit");
    long.gr_id = short.gr_id.clone();
    let mut storage = TestNotesStorage::with_notes(
        vec![TestNote::Short(short), TestNote::Long(long)],
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
    );
    let ids = [(1, TestNoteType::Short), (1, TestNoteType::Long)];
    storage.query_notes_mut(ids.into_iter(), |note| match note {
        TestNoteMut::Short(n) => *n.runtime_data.0 = 12.0,
        TestNoteMut::Long(n) => *n.runtime_data.0 = 34.0,
    });
    let expected: Vec<_> = storage.query_notes(ids.into_iter()).collect();
    let seconds = |value| ClockTime::from_seconds(value).unwrap();
    assert_eq!(
        expected[0].clock_time_interval(),
        Interval::new(seconds(2.0), seconds(2.0))
    );
    assert_eq!(
        expected[1].clock_time_interval(),
        Interval::new(seconds(1.0), seconds(3.0))
    );
    let shared_notes = shared(&storage, &ids);
    let mutable_notes = mutable(&mut storage, &ids);
    let shared_inherent: Vec<_> = storage
        .query_notes_ref(ids.into_iter())
        .map(|note| note.to_snapshot())
        .collect();
    let mut mutable_inherent = Vec::new();
    storage.query_notes_mut(ids.into_iter(), |note| {
        mutable_inherent.push(note.to_snapshot());
        match note {
            TestNoteMut::Short(n) => *n.runtime_data.0 = 56.0,
            TestNoteMut::Long(n) => *n.runtime_data.0 = 78.0,
        }
    });
    assert!(matches!(&expected[0], TestNoteSnapshot::Short(n) if n.runtime_data.0 == 12.0));
    assert!(matches!(&expected[1], TestNoteSnapshot::Long(n) if n.runtime_data.0 == 34.0));
    assert_eq!(shared_inherent, expected);
    assert_eq!(mutable_inherent, expected);
    let current: Vec<_> = storage.query_notes(ids.into_iter()).collect();
    assert!(matches!(&current[0], TestNoteSnapshot::Short(n) if n.runtime_data.0 == 56.0));
    assert!(matches!(&current[1], TestNoteSnapshot::Long(n) if n.runtime_data.0 == 78.0));

    let mutate = |storage: &mut TestNotesStorage, notes: &[TestNoteSnapshot]| {
        assert_eq!(notes.len(), 2);
        assert_eq!(
            notes[0].clock_time_interval().start(),
            ClockTime::from_seconds(2.0).unwrap()
        );
        storage.remove_note(TestNoteType::Short, notes[0].id());
    };
    mutate(&mut storage, &shared_notes);
    assert_eq!(shared_notes, expected);
    assert_eq!(mutable_notes, expected);
    assert_eq!(storage.query_notes(ids.into_iter()).count(), 1);
}

impl NotesStorageEditHook<TestNotesStorage> for MutatePosHook {
    fn pre_add(&mut self, _storage: &mut TestNotesStorage, notes: &mut [TestNote]) {
        for note in notes {
            match note {
                TestNote::Short(n) => n.pos += self.amt,
                TestNote::Long(n) => n.pos += self.amt,
            }
        }
    }

    fn pre_update(&mut self, _storage: &mut TestNotesStorage, notes: &mut [(u64, TestNote)]) {
        for (_, note) in notes {
            match note {
                TestNote::Short(n) => n.pos += self.amt,
                TestNote::Long(n) => n.pos += self.amt,
            }
        }
    }
}

#[test]
fn pre_add_and_pre_update_hook_mutations_are_stored() {
    let mut world = World::new(0);
    let mut storage = TestNotesStorage::with_notes(
        vec![TestNote::Short(short_data(1.0))],
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
    );
    let stored: Vec<(u64, TestNote)> = storage
        .query_notes([(1, TestNoteType::Short)].into_iter())
        .map(|snapshot| (snapshot.id(), snapshot.into_owned_note()))
        .collect();
    assert_eq!(stored, vec![(1, TestNote::Short(short_data(2.0)))]);

    storage.add_note(
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
        TestNote::Long(long_data(1.0)),
    );
    let stored: Vec<(u64, TestNote)> = storage
        .query_notes([(1, TestNoteType::Long)].into_iter())
        .map(|snapshot| (snapshot.id(), snapshot.into_owned_note()))
        .collect();
    assert_eq!(stored, vec![(1, TestNote::Long(long_data(2.0)))]);

    storage.add_notes(
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
        vec![TestNote::Short(short_data(20.0))],
    );
    let stored: Vec<(u64, TestNote)> = storage
        .query_notes([(2, TestNoteType::Short)].into_iter())
        .map(|snapshot| (snapshot.id(), snapshot.into_owned_note()))
        .collect();
    assert_eq!(stored, vec![(2, TestNote::Short(short_data(21.0)))]);
    storage.remove_notes(vec![(1, TestNoteType::Long), (2, TestNoteType::Short)]);

    storage.replace_note(
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
        1,
        TestNote::Short(short_data(5.0)),
    );
    let stored: Vec<(u64, TestNote)> = storage
        .query_notes([(1, TestNoteType::Short)].into_iter())
        .map(|snapshot| (snapshot.id(), snapshot.into_owned_note()))
        .collect();
    assert_eq!(stored, vec![(1, TestNote::Short(short_data(6.0)))]);

    storage.replace_notes(
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
        vec![(1, TestNote::Short(short_data(10.0)))],
    );
    let stored: Vec<(u64, TestNote)> = storage
        .query_notes([(1, TestNoteType::Short)].into_iter())
        .map(|snapshot| (snapshot.id(), snapshot.into_owned_note()))
        .collect();
    assert_eq!(stored, vec![(1, TestNote::Short(short_data(11.0)))]);
    storage.health_check();

    let mut extracted = vec![];
    storage.extract_notes(vec![(1, TestNoteType::Short)], |snapshot| {
        extracted.push((snapshot.id(), snapshot.into_owned_note()))
    });
    assert_eq!(extracted, vec![(1, TestNote::Short(short_data(11.0)))]);
    storage.remove_note(TestNoteType::Short, 999);
    storage.health_check();
}

#[test]
fn note_methods_are_available_through_storage_bounds() {
    use crate::notes::{Note, NoteState};

    fn check<S: NotesStorage>(
        storage: &mut S,
        ids: impl Iterator<Item = (u64, S::NoteTypeEnum)> + Clone,
    ) {
        let owned: Vec<_> = storage
            .query_notes(ids.clone())
            .map(|note| {
                (
                    note.start_time(),
                    note.duration(),
                    note.group_id().clone(),
                    note.clock_time_interval(),
                    note.id(),
                )
            })
            .collect();
        let shared: Vec<_> = storage
            .query_notes_ref(ids.clone())
            .map(|note| {
                (
                    note.start_time(),
                    note.duration(),
                    note.group_id().clone(),
                    note.clock_time_interval(),
                    note.id(),
                )
            })
            .collect();
        let mut mutable = Vec::new();
        storage.query_notes_mut(ids, |note| {
            mutable.push((
                note.start_time(),
                note.duration(),
                note.group_id().clone(),
                note.clock_time_interval(),
                note.id(),
            ));
        });
        assert_eq!(owned.len(), 2);
        assert_eq!(owned, shared);
        assert_eq!(shared, mutable);
    }

    let mut world = World::new(0);
    let mut storage = TestNotesStorage::with_notes(
        vec![
            TestNote::Short(short_data(1.0)),
            TestNote::Long(long_data(2.0)),
        ],
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
    );
    check(
        &mut storage,
        [(1, TestNoteType::Short), (1, TestNoteType::Long)].into_iter(),
    );
}

#[test]
fn hooks_mutate_every_value_in_bulk_add_and_replace() {
    let mut world = World::new(0);
    let mut storage = TestNotesStorage::with_notes(
        vec![
            TestNote::Short(short_data(1.0)),
            TestNote::Short(short_data(2.0)),
        ],
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
    );
    let values = |storage: &TestNotesStorage| {
        storage
            .query_notes([(1, TestNoteType::Short), (2, TestNoteType::Short)].into_iter())
            .map(|note| note.into_owned_note())
            .collect::<Vec<_>>()
    };
    assert_eq!(
        values(&storage),
        vec![
            TestNote::Short(short_data(2.0)),
            TestNote::Short(short_data(3.0))
        ]
    );
    storage.replace_notes(
        &mut world.note_groups,
        &mut world.tempo_tracks,
        &mut world.scroll_tracks,
        vec![
            (1, TestNote::Short(short_data(10.0))),
            (2, TestNote::Short(short_data(20.0))),
        ],
    );
    assert_eq!(
        values(&storage),
        vec![
            TestNote::Short(short_data(11.0)),
            TestNote::Short(short_data(21.0))
        ]
    );
}