vsrg 0.6.0

Data structures for vertical scrolling rhythm games
Documentation
use crate::rhythm::TrackId;
use std::collections::HashMap;

/// Identifier of a note group.
///
/// # Remarks
/// A note group is a collection of notes that shares the same properties. Vsrg needs at the
/// minimum the group's [`crate::rhythm::TempoTrack`] and [`crate::rhythm::ScrollSpeedTrack`] in
/// order to store the notes correctly for fast lookup. See [`NoteGroup`] for more details.
///
/// A chart always have one main group. Other groups are identified by an unique u64 number.
/// If your game identifies groups by number id, then the number can be returned directly.
/// Otherwise, you can use the hash of whatever you identify your groups by (string, Arc<str>, e.g)
/// as the u64 identifier.
///
/// If a named group ID does not match any stored group's id, then the main group is used instead
/// as a fallback.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GroupId {
    /// The main group.
    Main,
    /// A group identified by an unique u64 identifier.
    Named(u64),
}

/// Represents a note group.
///
/// # Remarks
/// A note group is a collection of notes that shares the same properties. Vsrg needs at the
/// minimum the group's [`crate::rhythm::TempoTrack`] and [`crate::rhythm::ScrollSpeedTrack`] in
/// order to store the notes correctly for fast lookup. This trait provides the getter for
/// the a note group's track id for these properties.
pub trait NoteGroup {
    /// Construct the main group, which access main property tracks.
    ///
    /// [`Self::tempo_track_id`] and [`Self::scroll_track_id`] on the main group should return
    /// [`TrackId::Main`].
    fn main_group() -> Self;
    fn tempo_track_id(&self) -> TrackId;
    fn scroll_track_id(&self) -> TrackId;
}

/// Storage of all note groups of a chart.
#[derive(Debug, Clone)]
pub struct NoteGroups<G> {
    main: G,
    named: HashMap<u64, G>,
}

impl<G: NoteGroup> NoteGroups<G> {
    /// Create an empty storage.
    pub fn new() -> Self {
        Self {
            named: Default::default(),
            main: G::main_group(),
        }
    }

    /// Create storage from an initial list of groups.
    pub fn with_groups(main: G, groups: impl Iterator<Item = (u64, G)>) -> Self {
        let named = groups.into_iter().collect();
        Self { named, main }
    }

    /// List all groups.
    pub fn groups(&self) -> impl Iterator<Item = (GroupId, &G)> {
        std::iter::chain(
            std::iter::once((GroupId::Main, &self.main)),
            self.named.iter().map(|(gid, g)| (GroupId::Named(*gid), g)),
        )
    }

    /// Add a new group with a given Id.
    ///
    /// Does nothing if there's an existing group with the given Id.
    pub fn add_group(&mut self, id: u64, group: G) {
        if self.named.contains_key(&id) {
            return;
        }

        self.named.insert(id, group);
    }

    /// Remove the group with a given Id, if it exists.
    ///
    /// Returns the removed group if it exists.
    pub fn remove_group(&mut self, id: u64) -> Option<G> {
        self.named.remove(&id)
    }

    /// Inserts or replace a group with a given Id.
    ///
    /// # Parameters
    /// - `id`: If [`GroupId::Main`], then the main group is replaced, otherwise it inserts or
    ///   replace into the given Id.
    /// - `group`: The group to insert or replace with.
    pub fn insert_or_replace_group(&mut self, id: GroupId, group: G) -> Option<G> {
        match id {
            GroupId::Named(id) => self.named.insert(id, group),
            GroupId::Main => Some(std::mem::replace(&mut self.main, group)),
        }
    }

    /// Get a mut reference to a group with the given Id.
    ///
    /// # Parameters
    /// - `id`: If [`GroupId::Main`], then the main group is returned, else it returns the group
    ///   with the given Id if exists.
    pub fn get_group_mut(&mut self, id: GroupId) -> Option<&mut G> {
        match id {
            GroupId::Named(id) => self.named.get_mut(&id),
            GroupId::Main => Some(&mut self.main),
        }
    }

    /// Get a reference to a group with the given Id.
    ///
    /// # Parameters
    /// - `id`: If [`GroupId::Main`], then the main group is returned, else it returns the group
    ///   with the given Id if exists.
    pub fn get_group(&self, id: GroupId) -> Option<&G> {
        match id {
            GroupId::Named(id) => self.named.get(&id),
            GroupId::Main => Some(&self.main),
        }
    }

    /// Get a mut reference to a group with the given Id, falling back to main group.
    ///
    /// # Parameters
    /// - `id`: If [`GroupId::Main`], or if no group with the given id exists, then the main group is
    ///   returned, else it returns the group with the given id.
    pub fn get_group_or_main_mut(&mut self, id: GroupId) -> &mut G {
        match id {
            GroupId::Named(id) => self.named.get_mut(&id).unwrap_or(&mut self.main),
            GroupId::Main => &mut self.main,
        }
    }

    /// Get a reference to a group with the given Id, falling back to main group.
    ///
    /// # Parameters
    /// - `id`: If [`GroupId::Main`], or if no group with the given id exists, then the main group is
    ///   returned, else it returns the group with the given id.
    pub fn get_group_or_main(&self, id: GroupId) -> &G {
        match id {
            GroupId::Named(id) => self.named.get(&id).unwrap_or(&self.main),
            GroupId::Main => &self.main,
        }
    }

    /// Get a reference to the main group.
    pub fn main_group(&self) -> &G {
        &self.main
    }

    /// Get a mut reference to the main group.
    pub fn main_group_mut(&mut self) -> &mut G {
        &mut self.main
    }
}

impl<G: Default + NoteGroup> Default for NoteGroups<G> {
    fn default() -> Self {
        Self::new()
    }
}