vsrg 0.3.0

Data structures for vertical scrolling rhythm games
Documentation
use crate::notes::{Interval, NoteMut, NoteRef};
use crate::rhythm::ClockTime;

impl<Note: super::LongNoteData> super::LongNoteTypeStorage<Note> {
    /// Iterate over every note state in group order and then storage order.
    pub fn all_notes_ref(&self) -> impl Iterator<Item = NoteRef<'_, Note>> {
        self.time_sorted_groups
            .iter()
            .flat_map(|group| (0..group.note_id.len()).map(move |index| group.state_at(index)))
    }

    /// Visit every runtime state once, synchronizing the scroll-sorted copy.
    pub fn all_notes_mut(&mut self, mut f: impl FnMut(NoteMut<'_, Note>)) {
        for group_index in 0..self.time_sorted_groups.len() {
            for index in 0..self.time_sorted_groups[group_index].note_id.len() {
                let id = self.time_sorted_groups[group_index].note_id[index];
                self.mutate_at(self.note_id_lookup[&id], &mut f);
            }
        }
    }

    /// Replace a note's runtime state in both the time- and scroll-sorted copies.
    ///
    /// Does nothing if the id is not present.
    pub fn set_note_state(&mut self, id: u64, note_state: Note::RuntimeData) {
        let Some(&loc) = self.note_id_lookup.get(&id) else {
            return;
        };
        super::edit::soa_set_element(
            &mut self.time_sorted_groups[loc.group_index].runtime_data,
            loc.index_in_time_sorted_group,
            note_state.clone(),
        );
        super::edit::soa_set_element(
            &mut self.scroll_sorted_groups[loc.group_index].runtime_data,
            loc.index_in_scroll_sorted_group,
            note_state,
        );
    }

    /// Query for notes from the notes storage by ids.
    pub fn query_notes(&self, ids: impl Iterator<Item = u64>) -> impl Iterator<Item = (u64, Note)> {
        ids.flat_map(|id| self.note_id_lookup.get(&id))
            .map(|&i| self.note_at(i))
    }

    /// Get references to note state from the notes storage by ids.
    pub fn query_notes_ref<'a>(
        &'a self,
        ids: impl Iterator<Item = u64>,
    ) -> impl Iterator<Item = NoteRef<'a, Note>> {
        ids.flat_map(|id| self.note_id_lookup.get(&id))
            .map(|&i| self.note_state_at(i))
    }

    /// Get mutable references to note state from the notes storage by ids.
    ///
    /// Mutable references cannot be returned from an iterator, so each note state is
    /// lent to `f` for the duration of one call instead. The previous state is dropped
    /// before the next note is visited, so duplicate ids in `ids` are allowed.
    ///
    /// Only the note's runtime state components are mutable; the serializable note value
    /// components, timing, and grouping state that the notes storage sorts by are not, so arbitrary
    /// mutations keep the notes storage consistent. The callback runs once per visit on the
    /// time-sorted copy; its updated runtime state is then cloned into the scroll-sorted copy.
    pub fn query_notes_mut(
        &mut self,
        ids: impl Iterator<Item = u64>,
        mut f: impl FnMut(NoteMut<'_, Note>),
    ) {
        for id in ids {
            let Some(&i) = self.note_id_lookup.get(&id) else {
                continue;
            };
            self.mutate_at(i, &mut f);
        }
    }

    /// Get notes overlapping an inclusive timing range, in group order.
    /// Within each group, results follow the interval tree traversal order.
    pub fn notes_overlapping_range(
        &self,
        range: Interval<ClockTime>,
    ) -> impl Iterator<Item = (u64, Note)> {
        self.range_locations(range).map(|i| self.note_at(i))
    }

    /// Get references to notes's state that overlaps a timing range.
    pub fn notes_ref_overlapping_range<'a>(
        &'a self,
        range: Interval<ClockTime>,
    ) -> impl Iterator<Item = NoteRef<'a, Note>> {
        self.range_locations(range).map(|i| self.note_state_at(i))
    }

    /// Get mutable references to notes's state that overlaps a timing range.
    ///
    /// Like [`Self::query_notes_mut`], each note state is lent to `f` one at a
    /// time.
    pub fn notes_mut_overlapping_range(
        &mut self,
        range: Interval<ClockTime>,
        mut f: impl FnMut(NoteMut<'_, Note>),
    ) {
        let locations: Vec<_> = self.range_locations(range).collect();
        for i in locations {
            self.mutate_at(i, &mut f);
        }
    }

    /// Reconstruct the note stored at time-sorted index `i`.
    fn note_at(&self, i: super::NoteLocation) -> (u64, Note) {
        let state = self.note_state_at(i);
        (
            state.id,
            Note::reconstruct(
                state.start_time,
                state.duration,
                state.value_data,
                state.group_id,
            ),
        )
    }

    /// Get the state of the note stored at time-sorted index `i`.
    fn note_state_at(&self, loc: super::NoteLocation) -> NoteRef<'_, Note> {
        self.time_sorted_groups[loc.group_index].state_at(loc.index_in_time_sorted_group)
    }

    fn range_locations(
        &self,
        range: Interval<ClockTime>,
    ) -> impl Iterator<Item = super::NoteLocation> + '_ {
        self.time_sorted_groups.iter().flat_map(move |group| {
            group
                .query_time_intersecting(range)
                .map(|i| self.note_id_lookup[&group.note_id[i]])
        })
    }

    fn mutate_at(&mut self, loc: super::NoteLocation, f: &mut impl FnMut(NoteMut<'_, Note>)) {
        let group = &mut self.time_sorted_groups[loc.group_index];
        let i = loc.index_in_time_sorted_group;
        f(group.state_mut_at(i));
        let runtime = group.runtime_data.swap_remove(i);
        super::edit::soa_set_element(
            &mut self.scroll_sorted_groups[loc.group_index].runtime_data,
            loc.index_in_scroll_sorted_group,
            runtime.clone(),
        );
        group.runtime_data.push(runtime);
        let last = group.runtime_data.len() - 1;
        group.runtime_data.swap(i, last);
    }
}

impl<Note: super::LongNoteData> super::TimeIndexedLongNoteGroup<Note> {
    fn state_at(&self, i: usize) -> NoteRef<'_, Note> {
        let id = self.note_id[i];
        let start_time = self.start_time[i];
        let duration = self.duration[i];
        let clock_time = self.clock_time[i];
        let value_data = self.value_data.get(i).expect("index should be valid");
        let runtime_data = self.runtime_data.get(i).expect("index should be valid");
        let group_id = self.group_id.clone();
        NoteRef {
            id,
            start_time,
            duration,
            clock_time,
            group_id,
            value_data,
            runtime_data,
        }
    }

    /// Get the mutable state of the note stored at time-sorted index `i`.
    fn state_mut_at(&mut self, i: usize) -> NoteMut<'_, Note> {
        let id = self.note_id[i];
        let start_time = self.start_time[i];
        let duration = self.duration[i];
        let clock_time = self.clock_time[i];
        let group_id = self.group_id.clone();
        let value_data = self.value_data.get(i).expect("index should be valid");
        let runtime_data = self.runtime_data.get_mut(i).expect("index should be valid");
        NoteMut {
            id,
            start_time,
            duration,
            clock_time,
            group_id,
            value_data,
            runtime_data,
        }
    }
}