finished mania + restructured a little
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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']
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user