vsrg 0.3.0

Data structures for vertical scrolling rhythm games
Documentation
use crate::rhythm::TrackId;
use std::{collections::HashMap, sync::Arc};

/// 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 its name (stored as
/// [`Arc<str>`]).
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GroupId {
    Main,
    Named(Arc<str>),
}

impl GroupId {
    /// Gets the name of the group, or [`None`] if this is the main group.
    pub fn as_deref(&self) -> Option<&str> {
        match self {
            GroupId::Main => None,
            GroupId::Named(name) => Some(name),
        }
    }
}

/// 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<Arc<str>, 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 = (Arc<str>, 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.clone()), 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: Arc<str>, 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: &str) -> 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.as_deref() {
            Some(id) => self.named.get_mut(id),
            None => 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.as_deref() {
            Some(id) => self.named.get(id),
            None => 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_mainmut(&mut self, id: &GroupId) -> &mut G {
        match id.as_deref() {
            Some(id) => self.named.get_mut(id).unwrap_or(&mut self.main),
            None => &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.as_deref() {
            Some(id) => self.named.get(id).unwrap_or(&self.main),
            None => &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()
    }
}