store osu skills in struct instead of array

This commit is contained in:
MaxOhn
2022-10-22 19:42:16 +02:00
parent ea23a8948b
commit 9acd6efe50
7 changed files with 208 additions and 249 deletions
+3 -22
View File
@@ -1,19 +1,15 @@
use std::{
any::Any,
f64::consts::{FRAC_PI_2, PI},
mem,
};
use std::f64::consts::{FRAC_PI_2, PI};
use crate::osu::difficulty_object::OsuDifficultyObject;
use super::{previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
#[derive(Clone)]
#[derive(Clone, Debug)]
pub(crate) struct Aim {
curr_strain: f64,
curr_section_peak: f64,
curr_section_end: f64,
strain_peaks: Vec<f64>,
pub(crate) strain_peaks: Vec<f64>,
with_sliders: bool,
}
@@ -51,21 +47,6 @@ impl Skill for Aim {
fn difficulty_value(&mut self) -> f64 {
<Self as OsuStrainSkill>::difficulty_value(self)
}
#[inline]
fn as_any(&self) -> &dyn Any {
self
}
#[inline]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline]
fn take_strain_peaks(&mut self) -> Vec<f64> {
mem::take(&mut self.strain_peaks)
}
}
impl StrainSkill for Aim {
+2 -19
View File
@@ -1,5 +1,3 @@
use std::{any::Any, mem};
use crate::{
osu::{difficulty_object::OsuDifficultyObject, osu_object::OsuObjectKind},
Mods,
@@ -7,12 +5,12 @@ use crate::{
use super::{previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
#[derive(Clone)]
#[derive(Clone, Debug)]
pub(crate) struct Flashlight {
curr_strain: f64,
curr_section_peak: f64,
curr_section_end: f64,
strain_peaks: Vec<f64>,
pub(crate) strain_peaks: Vec<f64>,
has_hidden_mod: bool,
scaling_factor: f64,
time_preempt: f64,
@@ -56,21 +54,6 @@ impl Skill for Flashlight {
fn difficulty_value(&mut self) -> f64 {
<Self as StrainSkill>::difficulty_value(self)
}
#[inline]
fn as_any(&self) -> &dyn Any {
self
}
#[inline]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline]
fn take_strain_peaks(&mut self) -> Vec<f64> {
mem::take(&mut self.strain_peaks)
}
}
impl StrainSkill for Flashlight {
+28 -128
View File
@@ -1,145 +1,45 @@
mod aim;
mod flashlight;
mod speed;
mod traits;
use std::{any::Any, cmp::Ordering, mem};
use crate::osu::difficulty_object::OsuDifficultyObject;
pub(crate) use self::{aim::Aim, flashlight::Flashlight, speed::Speed};
pub(crate) use self::{
aim::Aim,
flashlight::Flashlight,
speed::Speed,
traits::{OsuStrainSkill, Skill, StrainSkill},
};
use super::{difficulty_object::OsuDifficultyObject, SECTION_LEN};
pub(crate) trait Skill {
fn process(
&mut self,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
hit_window: f64,
);
fn difficulty_value(&mut self) -> f64;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn take_strain_peaks(&mut self) -> Vec<f64>;
#[derive(Clone, Debug)]
pub(crate) struct Skills {
pub aim: Aim,
pub aim_no_sliders: Aim,
pub speed: Speed,
pub flashlight: Flashlight,
}
pub(crate) trait StrainSkill: Skill + Sized {
const DECAY_WEIGHT: f64 = 0.9;
impl Skills {
pub(crate) fn new(mods: u32, radius: f32, time_preempt: f64, time_fade_in: f64) -> Self {
Self {
aim: Aim::new(true),
aim_no_sliders: Aim::new(false),
speed: Speed::new(),
flashlight: Flashlight::new(mods, radius, time_preempt, time_fade_in),
}
}
fn strain_peaks_mut(&mut self) -> &mut Vec<f64>;
fn curr_section_peak(&mut self) -> &mut f64;
fn curr_section_end(&mut self) -> &mut f64;
fn strain_value_at(
&mut self,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
hit_window: f64,
) -> f64;
fn calculate_initial_strain(
&self,
time: f64,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
) -> f64;
fn process(
pub(crate) fn process(
&mut self,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
hit_window: f64,
) {
// * The first object doesn't generate a strain, so we begin with an incremented section end
if curr.idx == 0 {
let section_len = SECTION_LEN as f64;
*self.curr_section_end() = (curr.start_time / section_len).ceil() * section_len;
}
while curr.start_time > *self.curr_section_end() {
self.save_curr_peak();
{
let section_end = *self.curr_section_end();
self.start_new_section_from(section_end, curr, diff_objects);
}
*self.curr_section_end() += SECTION_LEN as f64;
}
*self.curr_section_peak() = self
.strain_value_at(curr, diff_objects, hit_window)
.max(*self.curr_section_peak());
}
#[inline]
fn save_curr_peak(&mut self) {
let peak = *self.curr_section_peak();
self.strain_peaks_mut().push(peak);
}
#[inline]
fn start_new_section_from(
&mut self,
time: f64,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
) {
// * The maximum strain of the new section is not zero by default
// * This means we need to capture the strain level at the beginning of the new section,
// * and use that as the initial peak level.
*self.curr_section_peak() = self.calculate_initial_strain(time, curr, diff_objects);
}
fn difficulty_value(&mut self) -> f64;
#[inline]
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
let curr_peak = *self.curr_section_peak();
let mut strain_peaks = mem::take(self.strain_peaks_mut());
strain_peaks.push(curr_peak);
strain_peaks
}
}
pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
const REDUCED_SECTION_COUNT: usize = 10;
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
const DIFFICULTY_MULTIPLER: f64 = 1.06;
fn difficulty_value(&mut self) -> f64 {
let mut difficulty = 0.0;
let mut weight = 1.0;
// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
// * These sections will not contribute to the difficulty.
let mut peaks = self.get_curr_strain_peaks();
peaks.retain(|&peak| peak > 0.0);
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
let peak_iter = peaks.iter_mut().take(Self::REDUCED_SECTION_COUNT);
fn lerp(start: f64, end: f64, amount: f64) -> f64 {
start + (end - start) * amount
}
// * We are reducing the highest strains first to account for extreme difficulty spikes
for (i, strain) in peak_iter.enumerate() {
let clamped = (i as f32 / Self::REDUCED_SECTION_COUNT as f32).clamp(0.0, 1.0) as f64;
let scale = (lerp(1.0, 10.0, clamped)).log10();
*strain *= lerp(Self::REDUCED_STRAIN_BASELINE, 1.0, scale);
}
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
// * Difficulty is the weighted sum of the highest strains from every section.
// * We're sorting from highest to lowest strain.
for strain in peaks {
difficulty += strain * weight;
weight *= Self::DECAY_WEIGHT;
}
difficulty * Self::DIFFICULTY_MULTIPLER
<Aim as Skill>::process(&mut self.aim, curr, diff_objects, hit_window);
<Aim as Skill>::process(&mut self.aim_no_sliders, curr, diff_objects, hit_window);
<Speed as Skill>::process(&mut self.speed, curr, diff_objects, hit_window);
<Flashlight as Skill>::process(&mut self.flashlight, curr, diff_objects, hit_window);
}
}
+3 -18
View File
@@ -1,16 +1,16 @@
use std::{any::Any, cmp::Ordering, f64::consts::PI, mem};
use std::{cmp::Ordering, f64::consts::PI};
use crate::osu::difficulty_object::OsuDifficultyObject;
use super::{next, previous, previous_start_time, OsuStrainSkill, Skill, StrainSkill};
#[derive(Clone)]
#[derive(Clone, Debug)]
pub(crate) struct Speed {
curr_strain: f64,
curr_section_peak: f64,
curr_section_end: f64,
curr_rhythm: f64,
strain_peaks: Vec<f64>,
pub(crate) strain_peaks: Vec<f64>,
object_strains: Vec<f64>,
}
@@ -62,21 +62,6 @@ impl Skill for Speed {
fn difficulty_value(&mut self) -> f64 {
<Self as OsuStrainSkill>::difficulty_value(self)
}
#[inline]
fn as_any(&self) -> &dyn Any {
self
}
#[inline]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
#[inline]
fn take_strain_peaks(&mut self) -> Vec<f64> {
mem::take(&mut self.strain_peaks)
}
}
impl StrainSkill for Speed {
+135
View File
@@ -0,0 +1,135 @@
use std::{cmp::Ordering, mem};
use crate::osu::{difficulty_object::OsuDifficultyObject, SECTION_LEN};
pub(crate) trait Skill {
fn process(
&mut self,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
hit_window: f64,
);
fn difficulty_value(&mut self) -> f64;
}
pub(crate) trait StrainSkill: Skill + Sized {
const DECAY_WEIGHT: f64 = 0.9;
fn strain_peaks_mut(&mut self) -> &mut Vec<f64>;
fn curr_section_peak(&mut self) -> &mut f64;
fn curr_section_end(&mut self) -> &mut f64;
fn strain_value_at(
&mut self,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
hit_window: f64,
) -> f64;
fn calculate_initial_strain(
&self,
time: f64,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
) -> f64;
fn process(
&mut self,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
hit_window: f64,
) {
// * The first object doesn't generate a strain, so we begin with an incremented section end
if curr.idx == 0 {
let section_len = SECTION_LEN as f64;
*self.curr_section_end() = (curr.start_time / section_len).ceil() * section_len;
}
while curr.start_time > *self.curr_section_end() {
self.save_curr_peak();
{
let section_end = *self.curr_section_end();
self.start_new_section_from(section_end, curr, diff_objects);
}
*self.curr_section_end() += SECTION_LEN as f64;
}
*self.curr_section_peak() = self
.strain_value_at(curr, diff_objects, hit_window)
.max(*self.curr_section_peak());
}
#[inline]
fn save_curr_peak(&mut self) {
let peak = *self.curr_section_peak();
self.strain_peaks_mut().push(peak);
}
#[inline]
fn start_new_section_from(
&mut self,
time: f64,
curr: &OsuDifficultyObject<'_>,
diff_objects: &[OsuDifficultyObject<'_>],
) {
// * The maximum strain of the new section is not zero by default
// * This means we need to capture the strain level at the beginning of the new section,
// * and use that as the initial peak level.
*self.curr_section_peak() = self.calculate_initial_strain(time, curr, diff_objects);
}
fn difficulty_value(&mut self) -> f64;
#[inline]
fn get_curr_strain_peaks(&mut self) -> Vec<f64> {
let curr_peak = *self.curr_section_peak();
let mut strain_peaks = mem::take(self.strain_peaks_mut());
strain_peaks.push(curr_peak);
strain_peaks
}
}
pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
const REDUCED_SECTION_COUNT: usize = 10;
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
const DIFFICULTY_MULTIPLER: f64 = 1.06;
fn difficulty_value(&mut self) -> f64 {
let mut difficulty = 0.0;
let mut weight = 1.0;
// * Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
// * These sections will not contribute to the difficulty.
let mut peaks = self.get_curr_strain_peaks();
peaks.retain(|&peak| peak > 0.0);
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
let peak_iter = peaks.iter_mut().take(Self::REDUCED_SECTION_COUNT);
fn lerp(start: f64, end: f64, amount: f64) -> f64 {
start + (end - start) * amount
}
// * We are reducing the highest strains first to account for extreme difficulty spikes
for (i, strain) in peak_iter.enumerate() {
let clamped = (i as f32 / Self::REDUCED_SECTION_COUNT as f32).clamp(0.0, 1.0) as f64;
let scale = (lerp(1.0, 10.0, clamped)).log10();
*strain *= lerp(Self::REDUCED_STRAIN_BASELINE, 1.0, scale);
}
peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
// * Difficulty is the weighted sum of the highest strains from every section.
// * We're sorting from highest to lowest strain.
for strain in peaks {
difficulty += strain * weight;
weight *= Self::DECAY_WEIGHT;
}
difficulty * Self::DIFFICULTY_MULTIPLER
}
}