finished mania + restructured a little

This commit is contained in:
MaxOhn
2022-10-19 11:48:11 +02:00
parent 044fe986c9
commit 6ff46367ca
24 changed files with 737 additions and 350 deletions
+54
View File
@@ -0,0 +1,54 @@
use std::hash::{BuildHasher, Hasher};
#[derive(Copy, Clone, Default)]
pub(crate) struct ByteHasher;
impl BuildHasher for ByteHasher {
type Hasher = ByteHash;
#[inline]
fn build_hasher(&self) -> Self::Hasher {
ByteHash { byte: 0 }
}
}
pub(crate) struct ByteHash {
byte: u8,
}
impl Hasher for ByteHash {
#[inline]
fn finish(&self) -> u64 {
self.byte as u64
}
#[inline]
fn write(&mut self, _: &[u8]) {
unreachable!()
}
#[inline]
fn write_u8(&mut self, byte: u8) {
self.byte = byte;
}
}
#[cfg(test)]
mod tests {
use std::hash::{BuildHasher, Hash};
use super::ByteHasher;
#[test]
fn hashes_byte() {
let mut state = ByteHasher.build_hasher();
42_u8.hash(&mut state);
}
#[test]
#[should_panic]
fn doesnt_hash_int() {
let mut state = ByteHasher.build_hasher();
42_i32.hash(&mut state);
}
}
+65
View File
@@ -0,0 +1,65 @@
pub(crate) trait FloatExt: Sized {
// Workaround since rust rounds ties away from 0.0
// while C# rounds them to the nearest even integer.
// See github
// - https://github.com/rust-lang/rust/issues/96710
// - https://github.com/rust-lang/rust/pull/82273
fn round_even(self) -> Self;
}
impl FloatExt for f32 {
#[inline]
fn round_even(self) -> Self {
if (0.5 - self.fract().abs()).abs() <= f32::EPSILON {
2.0 * (self / 2.0).round()
} else {
self.round()
}
}
}
impl FloatExt for f64 {
#[inline]
fn round_even(self) -> Self {
if (0.5 - self.fract().abs()).abs() <= f64::EPSILON {
2.0 * (self / 2.0).round()
} else {
self.round()
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn round_even() {
let values = vec![
(3.0, 3.0),
(3.5, 4.0),
(4.5, 4.0),
(4.500001, 5.0),
(3.499999, 3.0),
(3.500001, 4.0),
(100_000_000_002.5, 100_000_000_002.0),
(-3.0, -3.0),
(-3.5, -4.0),
(-4.5, -4.0),
(-4.500001, -5.0),
(-3.499999, -3.0),
(-3.500001, -4.0),
(-100_000_000_002.5, -100_000_000_002.0),
];
for (value, expected) in values {
let rounded = <f32 as super::FloatExt>::round_even(value);
assert!(
(rounded - expected).abs() <= f32::EPSILON,
"expected {} for {}; got {}",
expected,
value,
rounded,
);
}
}
}
+139
View File
@@ -0,0 +1,139 @@
use std::{
iter::{Cycle, Skip, Take},
ops::Index,
slice::Iter,
};
/// Efficient counterpart to osu!'s [`LimitedCapacityQueue`]
/// i.e. an indexed queue with limited capacity.
///
/// [`LimitedQueue`] will use an internal array as queue which
/// is stored on the stack. Hence, if the size is very large,
/// e.g. `size_of<T>() * N`, consider using a different type
/// since heap allocation might be favorable.
///
/// [`LimitedCapacityQueue`]: https://github.com/ppy/osu/blob/b49a1aab8ac6e16e48dffd03f55635cdc1771adf/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs
#[derive(Clone, Debug)]
pub(crate) struct LimitedQueue<T, const N: usize> {
queue: [T; N],
/// If the queue is not empty, `end` is the index of the last element.
/// Otherwise, it has no meaning.
end: usize,
/// Amount of elements in the queue. This is equal to `end + 1`
/// if the queue is not full, or `N` otherwise.
len: usize,
}
impl<T, const N: usize> Default for LimitedQueue<T, N>
where
T: Copy + Clone + Default,
{
#[inline]
fn default() -> Self {
Self {
end: N - 1,
queue: [T::default(); N],
len: 0,
}
}
}
impl<T, const N: usize> LimitedQueue<T, N>
where
T: Copy + Clone + Default,
{
pub(crate) fn new() -> Self {
Self::default()
}
}
impl<T, const N: usize> LimitedQueue<T, N> {
pub(crate) fn push(&mut self, elem: T) {
self.end = (self.end + 1) % N;
self.queue[self.end] = elem;
self.len += (self.len < N) as usize;
}
pub(crate) fn is_empty(&self) -> bool {
self.len == 0
}
pub(crate) fn len(&self) -> usize {
self.len
}
pub(crate) fn last(&self) -> Option<&T> {
if self.is_empty() {
None
} else {
Some(&self.queue[self.end])
}
}
pub(crate) fn iter(&self) -> LimitedQueueIter<'_, T> {
self.queue
.iter()
.cycle()
.skip((self.len == N) as usize * (self.end + 1))
.take(self.len)
}
}
pub(crate) type LimitedQueueIter<'a, T> = Take<Skip<Cycle<Iter<'a, T>>>>;
impl<T, const N: usize> Index<usize> for LimitedQueue<T, N> {
type Output = T;
#[inline]
fn index(&self, idx: usize) -> &Self::Output {
assert!(
idx < self.len,
"index out of bounds: the len is {} but the index is {idx}",
self.len
);
let idx = (idx + (self.len == N) as usize * (self.end + 1)) % N;
&self.queue[idx]
}
}
#[cfg(test)]
mod test {
use super::LimitedQueue;
#[test]
fn empty() {
let queue = LimitedQueue::<u8, 4>::default();
assert!(queue.is_empty());
assert_eq!(queue.last(), None);
assert_eq!(queue.iter().count(), 0);
}
#[test]
fn single_push() {
let mut queue = LimitedQueue::<u8, 4>::default();
let elem = 42;
queue.push(elem);
assert!(!queue.is_empty());
assert_eq!(queue.len(), 1);
assert_eq!(queue.last(), Some(&elem));
assert!(queue.iter().eq(vec![elem].iter()));
assert_eq!(queue[0], elem);
}
#[test]
fn overfull() {
let mut queue = LimitedQueue::<u8, 4>::default();
for i in 1..=5 {
queue.push(i as u8);
assert_eq!(i.min(4), queue.len());
}
assert_eq!(queue.last(), Some(&5));
assert!(queue.iter().eq(&[2, 3, 4, 5]));
assert_eq!(queue[0], 2);
assert_eq!(queue[3], 5);
}
}
+12
View File
@@ -0,0 +1,12 @@
mod byte_hasher;
mod float_ext;
mod limited_queue;
mod sorted_vec;
mod tandem_sort;
pub use self::sorted_vec::SortedVec;
pub(crate) use self::{
byte_hasher::ByteHasher, float_ext::FloatExt, limited_queue::LimitedQueue,
tandem_sort::TandemSorter,
};
+145
View File
@@ -0,0 +1,145 @@
use std::{
cmp::Ordering,
fmt::{Debug, Formatter, Result as FmtResult},
ops::{Deref, Index},
slice::SliceIndex,
};
use crate::beatmap::{DifficultyPoint, EffectPoint, TimingPoint};
/// A [`Vec`] whose elements are guaranteed to be in order based on the given comparator.
#[derive(Clone)]
pub struct SortedVec<T> {
inner: Vec<T>,
cmp: fn(&T, &T) -> Ordering,
}
impl<T> SortedVec<T> {
/// Same as [`slice::binary_search_by`] with the internal compare function
#[inline]
pub fn find(&self, value: &T) -> Result<usize, usize> {
self.inner
.binary_search_by(|probe| (self.cmp)(probe, value))
}
/// Extracts the inner [`Vec`].
#[inline]
pub fn into_inner(self) -> Vec<T> {
self.inner
}
/// Push a new value into the sorted list.
/// If there is already an element that matches the new value,
/// the old element will be replaced.
pub(crate) fn push(&mut self, value: T) {
match self.find(&value) {
Ok(i) => self.inner[i] = value,
Err(i) if i == self.inner.len() => self.inner.push(value),
Err(i) => self.inner.insert(i, value),
}
}
}
impl<T> Deref for SortedVec<T> {
type Target = [T];
#[inline]
fn deref(&self) -> &Self::Target {
<Vec<T> as Deref>::deref(&self.inner)
}
}
impl<T, I> Index<I> for SortedVec<T>
where
I: SliceIndex<[T]>,
{
type Output = <I as SliceIndex<[T]>>::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
<Vec<T> as Index<I>>::index(&self.inner, index)
}
}
impl<T: Debug> Debug for SortedVec<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
<Vec<T> as Debug>::fmt(&self.inner, f)
}
}
impl<T> Default for SortedVec<T>
where
T: Ord,
{
#[inline]
fn default() -> Self {
Self {
inner: Vec::default(),
cmp: <T as Ord>::cmp,
}
}
}
macro_rules! impl_default_control_point {
( $( $ty:ident ),* ) => {
$(
impl Default for SortedVec<$ty> {
#[inline]
fn default() -> Self {
Self {
inner: Vec::default(),
cmp: |a, b| a.time.partial_cmp(&b.time).unwrap_or(Ordering::Equal),
}
}
}
)*
}
}
impl_default_control_point!(TimingPoint, DifficultyPoint, EffectPoint);
impl SortedVec<DifficultyPoint> {
pub(crate) fn push_if_not_redundant(&mut self, value: DifficultyPoint) {
let is_redundant = match self.find(&value).map_err(|idx| idx.checked_sub(1)) {
Ok(idx) | Err(Some(idx)) => value.is_redundant(&self[idx]),
Err(None) => value.is_redundant(&DifficultyPoint::default()),
};
if !is_redundant {
self.push(value);
}
}
}
#[cfg(test)]
mod tests {
use crate::beatmap::DifficultyPoint;
use super::SortedVec;
#[test]
fn sorts_on_push() {
let mut v = SortedVec {
inner: Vec::new(),
cmp: <i32 as Ord>::cmp,
};
v.push(42);
v.push(13);
v.push(20);
v.push(0);
assert_eq!(&v[..], &[0_i32, 13, 20, 42]);
}
#[test]
fn no_push_if_redundant() {
let mut v = SortedVec::default();
v.push(DifficultyPoint::default());
assert_eq!(v.len(), 1);
v.push_if_not_redundant(DifficultyPoint::default());
assert_eq!(v.len(), 1);
}
}
+91
View File
@@ -0,0 +1,91 @@
use std::cmp::Ordering;
/// Stores the sorted order for an initial list so that multiple
/// lists can be sorted based on that order.
pub(crate) struct TandemSorter {
indices: Vec<usize>,
}
impl TandemSorter {
/// Sort indices based on the given slice.
/// Note that this does **not** sort the given slice.
pub(crate) fn new<T>(slice: &[T]) -> Self
where
T: PartialOrd,
{
let mut indices: Vec<_> = (0..).take(slice.len()).collect();
indices
.sort_unstable_by(|&i, &j| slice[i].partial_cmp(&slice[j]).unwrap_or(Ordering::Equal));
Self { indices }
}
/// Sort the given slice based on the internal ordering.
///
/// If you intend to sort another slice afterwards,
/// don't forget to call [`Self::toggle_marks`] first.
pub(crate) fn sort<T>(&mut self, slice: &mut [T]) {
for i in 0..self.indices.len() {
let i_idx = self.indices[i];
if Self::idx_is_marked(i_idx) {
continue;
}
let mut j = i;
let mut j_idx = i_idx;
// When we loop back to the first index, we stop
while j_idx != i {
self.indices[j] = Self::toggle_mark_idx(j_idx);
slice.swap(j, j_idx);
j = j_idx;
j_idx = self.indices[j];
}
self.indices[j] = Self::toggle_mark_idx(j_idx);
}
}
/// This method must be called inbetween sorting slices.
pub(crate) fn toggle_marks(&mut self) {
for idx in self.indices.iter_mut() {
*idx = Self::toggle_mark_idx(*idx);
}
}
#[inline(always)]
fn idx_is_marked(idx: usize) -> bool {
// Check if first bit is set
idx.leading_zeros() == 0
}
#[inline(always)]
fn toggle_mark_idx(idx: usize) -> usize {
// Flip the first bit
idx ^ !(usize::MAX >> 1)
}
}
#[cfg(test)]
mod tests {
use super::TandemSorter;
#[test]
fn sort() {
let mut base = vec![9, 7, 8, 1, 4, 3, 5, 2];
let mut sorter = TandemSorter::new(&base);
sorter.sort(&mut base);
assert_eq!(base, vec![1, 2, 3, 4, 5, 7, 8, 9]);
sorter.toggle_marks();
let mut other = vec!['h', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'];
sorter.sort(&mut other);
assert_eq!(
other,
vec!['l', 'o', ' ', 'o', 'W', 'e', 'l', 'h', 'r', 'l', 'd']
);
}
}