initial rewrite
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
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 {
|
||||
u64::from(self.byte)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write(&mut self, _: &[u8]) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
use std::{
|
||||
iter::{self, Copied},
|
||||
slice,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct CompactVec {
|
||||
inner: Vec<Entry>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl CompactVec {
|
||||
const ACCEPTABLE_DIFFERENCE: f64 = 1e-16;
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, num: f64) {
|
||||
self.push_n(num, 1);
|
||||
}
|
||||
|
||||
pub(crate) fn push_n(&mut self, num: f64, n: usize) {
|
||||
if let Some(last) = self
|
||||
.inner
|
||||
.last_mut()
|
||||
.filter(|entry| (entry.value - num).abs() <= Self::ACCEPTABLE_DIFFERENCE)
|
||||
{
|
||||
last.count += n;
|
||||
} else if n > 0 {
|
||||
self.inner.push(Entry::new(num, n));
|
||||
}
|
||||
|
||||
self.len += n;
|
||||
}
|
||||
|
||||
pub(crate) fn retain<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(f64) -> bool,
|
||||
{
|
||||
self.inner.retain(|entry| f(entry.value));
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> Iter<'_> {
|
||||
Iter::new(self)
|
||||
}
|
||||
|
||||
pub(crate) fn to_vec(&self) -> Vec<f64> {
|
||||
let mut nums = Vec::with_capacity(self.len);
|
||||
|
||||
for entry in self.inner.iter() {
|
||||
nums.extend(iter::repeat(entry.value).take(entry.count));
|
||||
}
|
||||
|
||||
nums
|
||||
}
|
||||
|
||||
pub(crate) fn sum(&self) -> f64 {
|
||||
self.inner
|
||||
.iter()
|
||||
.fold(0.0, |sum, entry| sum + entry.value * entry.count as f64)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Entry {
|
||||
value: f64,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
const fn new(value: f64, count: usize) -> Self {
|
||||
Self { value, count }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Iter<'a> {
|
||||
iter: Copied<slice::Iter<'a, Entry>>,
|
||||
curr: Option<Entry>,
|
||||
}
|
||||
|
||||
impl<'a> Iter<'a> {
|
||||
fn new(vec: &'a CompactVec) -> Self {
|
||||
let mut iter = vec.inner.iter().copied();
|
||||
|
||||
Self {
|
||||
curr: iter.next(),
|
||||
iter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Iter<'_> {
|
||||
type Item = f64;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let curr = self.curr.as_mut()?;
|
||||
|
||||
if curr.count == 0 {
|
||||
self.curr = self.iter.next();
|
||||
} else {
|
||||
curr.count -= 1;
|
||||
|
||||
return Some(curr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn compact_vec() -> CompactVec {
|
||||
let mut vec = CompactVec::default();
|
||||
vec.push_n(123.456, 3);
|
||||
vec.push(0.0);
|
||||
vec.push_n(0.0, 2);
|
||||
vec.push(2.0);
|
||||
vec.push_n(3.0, 2);
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
let vec = compact_vec();
|
||||
|
||||
assert_eq!(vec.inner.len(), 4);
|
||||
assert_eq!(vec.len, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_vec() {
|
||||
let to_vec = compact_vec().to_vec();
|
||||
|
||||
assert_eq!(
|
||||
to_vec,
|
||||
vec![123.456, 123.456, 123.456, 0.0, 0.0, 0.0, 2.0, 3.0, 3.0]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
let vec = compact_vec();
|
||||
let mut iter = vec.iter();
|
||||
|
||||
assert_eq!(iter.next(), Some(123.456));
|
||||
assert_eq!(iter.next(), Some(123.456));
|
||||
assert_eq!(iter.next(), Some(123.456));
|
||||
assert_eq!(iter.next(), Some(0.0));
|
||||
assert_eq!(iter.next(), Some(0.0));
|
||||
assert_eq!(iter.next(), Some(0.0));
|
||||
assert_eq!(iter.next(), Some(2.0));
|
||||
assert_eq!(iter.next(), Some(3.0));
|
||||
assert_eq!(iter.next(), Some(3.0));
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
}
|
||||
+28
-19
@@ -1,33 +1,42 @@
|
||||
pub(crate) trait FloatExt: Sized {
|
||||
pub 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;
|
||||
|
||||
/// `self == other`
|
||||
fn eq(self, other: Self) -> bool;
|
||||
|
||||
/// `self != other`
|
||||
fn not_eq(self, other: Self) -> bool;
|
||||
}
|
||||
|
||||
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()
|
||||
macro_rules! impl_float_ext {
|
||||
( $ty:ty ) => {
|
||||
impl FloatExt for $ty {
|
||||
fn round_even(self) -> Self {
|
||||
if self.fract().abs().eq(0.5) {
|
||||
2.0 * (self / 2.0).round()
|
||||
} else {
|
||||
self.round()
|
||||
}
|
||||
}
|
||||
|
||||
fn eq(self, other: Self) -> bool {
|
||||
(self - other).abs() < <$ty>::EPSILON
|
||||
}
|
||||
|
||||
fn not_eq(self, other: Self) -> bool {
|
||||
(self - other).abs() >= <$ty>::EPSILON
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_float_ext!(f32);
|
||||
impl_float_ext!(f64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::model::hit_object::HitObject;
|
||||
|
||||
const QUICK_SORT_DEPTH_THRESHOLD: usize = 32;
|
||||
|
||||
/// [Legacy sorting algorithm](https://github.com/ppy/osu/blob/e669e28dc9b6d79d82a36053e6a279de8dafddd1/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs#L19)
|
||||
pub fn legacy_sort(keys: &mut [HitObject]) {
|
||||
if keys.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
depth_limited_quick_sort(keys, 0, keys.len() - 1, QUICK_SORT_DEPTH_THRESHOLD);
|
||||
}
|
||||
|
||||
fn depth_limited_quick_sort(
|
||||
keys: &mut [HitObject],
|
||||
mut left: usize,
|
||||
mut right: usize,
|
||||
mut depth_limit: usize,
|
||||
) {
|
||||
loop {
|
||||
if depth_limit == 0 {
|
||||
heap_sort(keys, left, right);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let mut i = left;
|
||||
let mut j = right;
|
||||
|
||||
let mid = i + ((j - i) >> 1);
|
||||
|
||||
if keys[i] > keys[mid] {
|
||||
keys.swap(i, mid);
|
||||
}
|
||||
|
||||
if keys[i] > keys[j] {
|
||||
keys.swap(i, j);
|
||||
}
|
||||
|
||||
if keys[mid] > keys[j] {
|
||||
keys.swap(mid, j);
|
||||
}
|
||||
|
||||
loop {
|
||||
while keys[i] < keys[mid] {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while keys[mid] < keys[j] {
|
||||
j -= 1;
|
||||
}
|
||||
|
||||
match i.cmp(&j) {
|
||||
Ordering::Less => keys.swap(i, j),
|
||||
Ordering::Equal => {}
|
||||
Ordering::Greater => break,
|
||||
}
|
||||
|
||||
i += 1;
|
||||
j = j.saturating_sub(1);
|
||||
|
||||
if i > j {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
depth_limit -= 1;
|
||||
|
||||
if j.saturating_sub(left) <= right - i {
|
||||
if left < j {
|
||||
depth_limited_quick_sort(keys, left, j, depth_limit);
|
||||
}
|
||||
|
||||
left = i;
|
||||
} else {
|
||||
if i < right {
|
||||
depth_limited_quick_sort(keys, i, right, depth_limit);
|
||||
}
|
||||
|
||||
right = j;
|
||||
}
|
||||
|
||||
if left >= right {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn heap_sort(keys: &mut [HitObject], lo: usize, hi: usize) {
|
||||
let n = hi - lo + 1;
|
||||
|
||||
for i in (1..=n / 2).rev() {
|
||||
down_heap(keys, i, n, lo);
|
||||
}
|
||||
|
||||
for i in (2..=n).rev() {
|
||||
keys.swap(lo, lo + i - 1);
|
||||
down_heap(keys, 1, i - 1, lo);
|
||||
}
|
||||
}
|
||||
|
||||
fn down_heap(keys: &mut [HitObject], mut i: usize, n: usize, lo: usize) {
|
||||
while i <= n / 2 {
|
||||
let mut child = 2 * i;
|
||||
|
||||
if child < n && keys[lo + child - 1] < keys[lo + child] {
|
||||
child += 1;
|
||||
}
|
||||
|
||||
if keys[lo + i - 1] >= keys[lo + child - 1] {
|
||||
break;
|
||||
}
|
||||
|
||||
keys.swap(lo + i - 1, lo + child - 1);
|
||||
i = child;
|
||||
}
|
||||
}
|
||||
+154
-34
@@ -1,26 +1,21 @@
|
||||
use std::{
|
||||
iter::{Cycle, Skip, Take},
|
||||
ops::Index,
|
||||
slice::Iter,
|
||||
};
|
||||
use std::{mem, ops::Index, slice::Iter};
|
||||
|
||||
/// Efficient counterpart to osu!'s [`LimitedCapacityQueue`]
|
||||
/// i.e. an indexed queue with limited capacity.
|
||||
/// 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.
|
||||
/// [`LimitedQueue`] will use an internal array as queue which is stored on
|
||||
/// the stack. Hence, if `size_of<T>() * N` is very large, 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> {
|
||||
pub 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.
|
||||
/// Amount of elements in the queue. This is equal to `end + 1` if the
|
||||
/// queue is not full, or `N` otherwise.
|
||||
len: usize,
|
||||
}
|
||||
|
||||
@@ -28,7 +23,6 @@ impl<T, const N: usize> Default for LimitedQueue<T, N>
|
||||
where
|
||||
T: Copy + Clone + Default,
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
end: N - 1,
|
||||
@@ -42,27 +36,33 @@ impl<T, const N: usize> LimitedQueue<T, N>
|
||||
where
|
||||
T: Copy + Clone + Default,
|
||||
{
|
||||
pub(crate) fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> LimitedQueue<T, N> {
|
||||
pub(crate) fn push(&mut self, elem: T) {
|
||||
pub fn push(&mut self, elem: T) {
|
||||
self.end = (self.end + 1) % N;
|
||||
self.queue[self.end] = elem;
|
||||
self.len += usize::from(self.len < N);
|
||||
}
|
||||
|
||||
pub(crate) const fn is_empty(&self) -> bool {
|
||||
#[cfg(test)]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub(crate) const fn len(&self) -> usize {
|
||||
pub const fn is_full(&self) -> bool {
|
||||
self.len == N
|
||||
}
|
||||
|
||||
pub const fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
pub(crate) const fn last(&self) -> Option<&T> {
|
||||
#[cfg(test)]
|
||||
pub const fn last(&self) -> Option<&T> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -70,28 +70,85 @@ impl<T, const N: usize> LimitedQueue<T, N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> LimitedQueueIter<'_, T> {
|
||||
self.queue
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip(usize::from(self.len == N) * (self.end + 1))
|
||||
.take(self.len)
|
||||
pub fn as_slices(&self) -> (&[T], &[T]) {
|
||||
if self.is_full() {
|
||||
(&self.queue[self.end + 1..N], &self.queue[0..=self.end])
|
||||
} else {
|
||||
(&[], &self.queue[0..self.len])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> LimitedQueueIter<'_, T> {
|
||||
let (head, tail) = self.as_slices();
|
||||
|
||||
LimitedQueueIter {
|
||||
head: head.iter(),
|
||||
tail: tail.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type LimitedQueueIter<'a, T> = Take<Skip<Cycle<Iter<'a, T>>>>;
|
||||
pub struct LimitedQueueIter<'a, T> {
|
||||
head: Iter<'a, T>,
|
||||
tail: Iter<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for LimitedQueueIter<'a, T> {
|
||||
type Item = &'a T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let val @ Some(_) = self.head.next() {
|
||||
val
|
||||
} else {
|
||||
mem::swap(&mut self.head, &mut self.tail);
|
||||
|
||||
self.head.next()
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.len();
|
||||
|
||||
(len, Some(len))
|
||||
}
|
||||
|
||||
fn last(mut self) -> Option<Self::Item> {
|
||||
self.next_back()
|
||||
}
|
||||
|
||||
fn nth(&mut self, mut n: usize) -> Option<Self::Item> {
|
||||
if self.head.len() <= n {
|
||||
n -= self.head.len();
|
||||
mem::swap(&mut self.head, &mut self.tail);
|
||||
self.tail = [].iter();
|
||||
}
|
||||
|
||||
self.head.nth(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DoubleEndedIterator for LimitedQueueIter<'_, T> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if let val @ Some(_) = self.tail.next_back() {
|
||||
val
|
||||
} else {
|
||||
mem::swap(&mut self.head, &mut self.tail);
|
||||
|
||||
self.tail.next_back()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExactSizeIterator for LimitedQueueIter<'_, T> {
|
||||
fn len(&self) -> usize {
|
||||
self.head.len() + self.tail.len()
|
||||
}
|
||||
}
|
||||
|
||||
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 + usize::from(self.len == N) * (self.end + 1)) % N;
|
||||
|
||||
&self.queue[idx]
|
||||
@@ -136,4 +193,67 @@ mod test {
|
||||
assert_eq!(queue[0], 2);
|
||||
assert_eq!(queue[3], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_slices() {
|
||||
let mut queue = LimitedQueue::<u8, 3>::default();
|
||||
assert_eq!(queue.as_slices(), ([].as_slice(), [].as_slice()));
|
||||
|
||||
// Start by filling the tail slice
|
||||
queue.push(1);
|
||||
assert_eq!(queue.as_slices(), ([].as_slice(), [1].as_slice()));
|
||||
queue.push(2);
|
||||
assert_eq!(queue.as_slices(), ([].as_slice(), [1, 2].as_slice()));
|
||||
queue.push(3);
|
||||
assert_eq!(queue.as_slices(), ([].as_slice(), [1, 2, 3].as_slice()));
|
||||
|
||||
// The buffer is full and wraps around so now it uses the head slice
|
||||
queue.push(4);
|
||||
assert_eq!(queue.as_slices(), ([2, 3].as_slice(), [4].as_slice()));
|
||||
queue.push(5);
|
||||
assert_eq!(queue.as_slices(), ([3].as_slice(), [4, 5].as_slice()));
|
||||
queue.push(6);
|
||||
assert_eq!(queue.as_slices(), ([].as_slice(), [4, 5, 6].as_slice()));
|
||||
|
||||
queue.push(7);
|
||||
assert_eq!(queue.as_slices(), ([5, 6].as_slice(), [7].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_nth() {
|
||||
const CAPACITY: usize = 5;
|
||||
const LIMIT: usize = 11;
|
||||
|
||||
let mut queue = LimitedQueue::<u8, CAPACITY>::default();
|
||||
|
||||
// The queue is not yet full
|
||||
for n in 0..CAPACITY {
|
||||
queue.push(n as u8);
|
||||
|
||||
for i in 0..=n {
|
||||
assert_eq!(queue.iter().nth(i), Some(&(i as u8)));
|
||||
}
|
||||
|
||||
for i in n + 1..CAPACITY {
|
||||
assert_eq!(queue.iter().nth(i), None);
|
||||
}
|
||||
}
|
||||
|
||||
// The queue is full
|
||||
for n in CAPACITY..LIMIT {
|
||||
queue.push(n as u8);
|
||||
|
||||
let (head, tail) = queue.as_slices();
|
||||
|
||||
for i in 0..head.len() {
|
||||
assert_eq!(queue.iter().nth(i), Some(&head[i]));
|
||||
}
|
||||
|
||||
for i in 0..tail.len() {
|
||||
assert_eq!(queue.iter().nth(head.len() + i), Some(&tail[i]));
|
||||
}
|
||||
|
||||
assert_eq!(queue.iter().nth(head.len() + tail.len() + 1), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::fmt::{Debug, Formatter, Result as FmtResult};
|
||||
|
||||
use crate::model::{beatmap::Converted, mode::IGameMode};
|
||||
|
||||
pub enum MapOrAttrs<'map, M: IGameMode> {
|
||||
Map(Converted<'map, M>),
|
||||
Attrs(M::DifficultyAttributes),
|
||||
}
|
||||
|
||||
impl<'map, M: IGameMode> MapOrAttrs<'map, M> {
|
||||
/// Return a mutable reference to the attributes.
|
||||
///
|
||||
/// If `self` is of variant `Map`, store `attrs` in `self`, and return a
|
||||
/// mutable reference to it.
|
||||
pub fn attrs_or_insert(
|
||||
&mut self,
|
||||
attrs: M::DifficultyAttributes,
|
||||
) -> &mut M::DifficultyAttributes {
|
||||
match self {
|
||||
MapOrAttrs::Map(_) => {
|
||||
*self = Self::Attrs(attrs);
|
||||
|
||||
let Self::Attrs(ref mut other) = self else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
other
|
||||
}
|
||||
MapOrAttrs::Attrs(ref mut other) => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Clone for MapOrAttrs<'_, M>
|
||||
where
|
||||
M: IGameMode,
|
||||
M::DifficultyAttributes: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Map(converted) => Self::Map(converted.clone()),
|
||||
Self::Attrs(attrs) => Self::Attrs(attrs.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Debug for MapOrAttrs<'_, M>
|
||||
where
|
||||
M: IGameMode,
|
||||
M::DifficultyAttributes: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
match self {
|
||||
Self::Map(converted) => f.debug_tuple("Map").field(converted).finish(),
|
||||
Self::Attrs(attrs) => f.debug_tuple("Attrs").field(attrs).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> PartialEq for MapOrAttrs<'_, M>
|
||||
where
|
||||
M: IGameMode,
|
||||
M::DifficultyAttributes: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Map(a), Self::Map(b)) => a == b,
|
||||
(Self::Attrs(a), Self::Attrs(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
use crate::Beatmap;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum MapOrElse<M, E> {
|
||||
Map(M),
|
||||
Else(E),
|
||||
}
|
||||
|
||||
impl<M, E> MapOrElse<M, E> {
|
||||
/// Return a mutable reference to `Else`.
|
||||
///
|
||||
/// If `self` is of variant `Map`, store `other` in `self`, and return a
|
||||
/// mutable reference to it.
|
||||
pub(crate) fn else_or_insert(&mut self, other: E) -> &mut E {
|
||||
match self {
|
||||
MapOrElse::Map(_) => {
|
||||
*self = Self::Else(other);
|
||||
|
||||
let Self::Else(ref mut other) = self else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
other
|
||||
}
|
||||
MapOrElse::Else(ref mut other) => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map, E> From<&'map Beatmap> for MapOrElse<MapRef<'map>, E> {
|
||||
fn from(map: &'map Beatmap) -> Self {
|
||||
Self::Map(MapRef(map))
|
||||
}
|
||||
}
|
||||
|
||||
/// References don't implement [`Deref`] so we implement a wrapper type.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct MapRef<'map>(&'map Beatmap);
|
||||
|
||||
impl<'map> MapRef<'map> {
|
||||
pub(crate) const fn into_inner(self) -> &'map Beatmap {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Beatmap> for MapRef<'_> {
|
||||
fn as_ref(&self) -> &Beatmap {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
+7
-15
@@ -1,15 +1,7 @@
|
||||
mod byte_hasher;
|
||||
mod compact_vec;
|
||||
mod float_ext;
|
||||
mod limited_queue;
|
||||
mod map_or_else;
|
||||
mod tandem_sort;
|
||||
|
||||
pub(crate) use self::{
|
||||
byte_hasher::ByteHasher,
|
||||
compact_vec::CompactVec,
|
||||
float_ext::FloatExt,
|
||||
limited_queue::LimitedQueue,
|
||||
map_or_else::{MapOrElse, MapRef},
|
||||
tandem_sort::TandemSorter,
|
||||
};
|
||||
pub mod float_ext;
|
||||
pub mod legacy_sort;
|
||||
pub mod limited_queue;
|
||||
pub mod map_or_attrs;
|
||||
pub mod mods;
|
||||
pub mod random;
|
||||
pub mod tandem_sort;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
pub trait Mods: Copy {
|
||||
fn nf(self) -> bool;
|
||||
fn ez(self) -> bool;
|
||||
fn td(self) -> bool;
|
||||
fn hd(self) -> bool;
|
||||
fn hr(self) -> bool;
|
||||
fn dt(self) -> bool;
|
||||
fn rx(self) -> bool;
|
||||
fn ht(self) -> bool;
|
||||
fn fl(self) -> bool;
|
||||
fn so(self) -> bool;
|
||||
|
||||
fn clock_rate(self) -> f64 {
|
||||
if self.dt() {
|
||||
1.5
|
||||
} else if self.ht() {
|
||||
0.75
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
fn od_ar_hp_multiplier(self) -> f64 {
|
||||
if self.hr() {
|
||||
1.4
|
||||
} else if self.ez() {
|
||||
0.5
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_mods_fn {
|
||||
( $fn_name:ident, $bits:expr ) => {
|
||||
fn $fn_name(self) -> bool {
|
||||
self & ($bits) != 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Mods for u32 {
|
||||
impl_mods_fn!(nf, 1 << 0);
|
||||
impl_mods_fn!(ez, 1 << 1);
|
||||
impl_mods_fn!(td, 1 << 2);
|
||||
impl_mods_fn!(hd, 1 << 3);
|
||||
impl_mods_fn!(hr, 1 << 4);
|
||||
impl_mods_fn!(dt, 1 << 6);
|
||||
impl_mods_fn!(rx, 1 << 7);
|
||||
impl_mods_fn!(ht, 1 << 8);
|
||||
impl_mods_fn!(fl, 1 << 10);
|
||||
impl_mods_fn!(so, 1 << 12);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
const INT_TO_REAL: f64 = 1.0 / (i32::MAX as f64 + 1.0);
|
||||
const INT_MASK: u32 = 0x7F_FF_FF_FF;
|
||||
|
||||
pub struct Random {
|
||||
x: u32,
|
||||
y: u32,
|
||||
z: u32,
|
||||
w: u32,
|
||||
bit_buf: u32,
|
||||
bit_idx: i32,
|
||||
}
|
||||
|
||||
impl Random {
|
||||
pub const fn new(seed: i32) -> Self {
|
||||
Self {
|
||||
x: seed as u32,
|
||||
y: 842_502_087,
|
||||
z: 3_579_807_591,
|
||||
w: 273_326_509,
|
||||
bit_buf: 0,
|
||||
bit_idx: 32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_unsigned(&mut self) -> u32 {
|
||||
let t = self.x ^ (self.x << 11);
|
||||
self.x = self.y;
|
||||
self.y = self.z;
|
||||
self.z = self.w;
|
||||
self.w = self.w ^ (self.w >> 19) ^ t ^ (t >> 8);
|
||||
|
||||
self.w
|
||||
}
|
||||
|
||||
pub fn next_int(&mut self) -> i32 {
|
||||
(INT_MASK & self.gen_unsigned()) as i32
|
||||
}
|
||||
|
||||
pub fn next_double(&mut self) -> f64 {
|
||||
INT_TO_REAL * f64::from(self.next_int())
|
||||
}
|
||||
|
||||
pub fn next_int_range(&mut self, min: i32, max: i32) -> i32 {
|
||||
(f64::from(min) + self.next_double() * f64::from(max - min)) as i32
|
||||
}
|
||||
|
||||
pub fn next_double_range(&mut self, min: f64, max: f64) -> i32 {
|
||||
(min + self.next_double() * (max - min)) as i32
|
||||
}
|
||||
|
||||
pub fn next_bool(&mut self) -> bool {
|
||||
if self.bit_idx == 32 {
|
||||
self.bit_buf = self.gen_unsigned();
|
||||
self.bit_idx = 1;
|
||||
} else {
|
||||
self.bit_idx += 1;
|
||||
self.bit_buf >>= 1;
|
||||
}
|
||||
|
||||
(self.bit_buf & 1) == 1
|
||||
}
|
||||
}
|
||||
+25
-25
@@ -1,37 +1,39 @@
|
||||
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 {
|
||||
/// Stores the sorted order for an initial list so that multiple
|
||||
/// lists can be sorted based on that order.
|
||||
pub struct TandemSorter {
|
||||
indices: Box<[usize]>,
|
||||
should_reset: bool,
|
||||
}
|
||||
|
||||
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], stable: bool) -> Self
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
let mut indices: Box<[usize]> = (0..).take(slice.len()).collect();
|
||||
|
||||
let closure =
|
||||
|&i: &usize, &j: &usize| slice[i].partial_cmp(&slice[j]).unwrap_or(Ordering::Equal);
|
||||
pub fn new<T>(slice: &[T], cmp: fn(&T, &T) -> Ordering, stable: bool) -> Self {
|
||||
let mut indices: Box<[usize]> = (0..slice.len()).collect();
|
||||
let sort_by = |&i: &usize, &j: &usize| cmp(&slice[i], &slice[j]);
|
||||
|
||||
if stable {
|
||||
indices.sort_by(closure);
|
||||
indices.sort_by(sort_by);
|
||||
} else {
|
||||
indices.sort_unstable_by(closure);
|
||||
indices.sort_unstable_by(sort_by);
|
||||
}
|
||||
|
||||
Self { indices }
|
||||
Self {
|
||||
indices,
|
||||
should_reset: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]) {
|
||||
pub fn sort<T>(&mut self, slice: &mut [T]) {
|
||||
if self.should_reset {
|
||||
self.toggle_marks();
|
||||
self.should_reset = false;
|
||||
}
|
||||
|
||||
for i in 0..self.indices.len() {
|
||||
let i_idx = self.indices[i];
|
||||
|
||||
@@ -52,22 +54,21 @@ impl TandemSorter {
|
||||
|
||||
self.indices[j] = Self::toggle_mark_idx(j_idx);
|
||||
}
|
||||
|
||||
self.should_reset = true;
|
||||
}
|
||||
|
||||
/// This method must be called inbetween sorting slices.
|
||||
pub(crate) fn toggle_marks(&mut self) {
|
||||
fn toggle_marks(&mut self) {
|
||||
for idx in self.indices.iter_mut() {
|
||||
*idx = Self::toggle_mark_idx(*idx);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn idx_is_marked(idx: usize) -> bool {
|
||||
// Check if first bit is set
|
||||
idx.leading_zeros() == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn toggle_mark_idx(idx: usize) -> usize {
|
||||
// Flip the first bit
|
||||
idx ^ !(usize::MAX >> 1)
|
||||
@@ -81,13 +82,12 @@ mod tests {
|
||||
#[test]
|
||||
fn sort() {
|
||||
let mut base = vec![9, 7, 8, 1, 4, 3, 5, 2];
|
||||
let mut sorter = TandemSorter::new(&base, false);
|
||||
let mut other = "hello World".chars().collect::<Vec<_>>();
|
||||
let mut sorter = TandemSorter::new(&base, u8::cmp, false);
|
||||
|
||||
sorter.sort(&mut base);
|
||||
assert_eq!(base, vec![1, 2, 3, 4, 5, 7, 8, 9]);
|
||||
|
||||
sorter.toggle_marks();
|
||||
let mut other = "hello World".chars().collect::<Vec<_>>();
|
||||
sorter.sort(&mut other);
|
||||
assert_eq!(other, "lo oWelhrld".chars().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user