refactor: music-db crate

This commit is contained in:
mokurin000
2025-08-02 23:52:46 +08:00
parent f7b3161847
commit 503f5f3f33
11 changed files with 57 additions and 36 deletions

View File

@@ -18,6 +18,7 @@ strum = { workspace = true }
tokio = { workspace = true, optional = true }
compio = { workspace = true, optional = true }
spdlog-rs = { workspace = true }
music-db = { workspace = true }
# (de)serialization
serde = { workspace = true }
@@ -43,8 +44,3 @@ bincode = { version = "2.0.1", optional = true }
# magic macro
crabtime = { git = "https://github.com/wdanilo/crabtime.git", rev = "2ed856f5" }
rustc-hash = "2.1.1"
rust_decimal = { version = "1.37.2", default-features = false, features = [
"serde-with-arbitrary-precision",
"macros",
] }

View File

@@ -9,6 +9,3 @@ pub fn level_name(level: u32) -> &'static str {
_ => "Unknown",
}
}
mod music_db;
pub use music_db::{Level, MusicInfo, query_music, query_music_level, preload_db};

View File

@@ -1,159 +0,0 @@
use std::{fs::OpenOptions, io::BufReader, sync::LazyLock, time::SystemTime};
use rust_decimal::{Decimal, dec, serde::DecimalFromString};
use rustc_hash::FxHashMap;
use serde::Deserialize;
use spdlog::{info, warn};
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicInfo {
pub id: u32,
pub name: String,
pub version: i64,
pub levels: Vec<Level>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Level {
/// 0, 1, 2, 3, 4, 5
pub level: u32,
/// for example: "13.7"
pub difficulty: DecimalFromString,
}
type MusicDB = FxHashMap<u32, MusicInfo>;
pub fn preload_db() {
_ = &*MUSIC_DB;
}
pub fn query_music(music_id: u32) -> Option<&'static MusicInfo> {
MUSIC_DB.as_ref()?.get(&music_id)
}
pub fn query_music_level(music_id: u32, level: u32) -> Option<&'static Level> {
MUSIC_DB
.as_ref()?
.get(&music_id)?
.levels
.iter()
.find(|d| d.level == level)
}
pub static MUSIC_DB: LazyLock<Option<MusicDB>> = LazyLock::new(|| {
let time = SystemTime::now();
info!("loading musicDB...");
let json = OpenOptions::new()
.read(true)
.create(false)
.open("musicDB.json")
.inspect_err(|e| warn!("failed to load musicDB: {e}"))
.ok()?;
let buf_reader = BufReader::new(json);
let db: Vec<MusicInfo> = serde_json::from_reader(buf_reader)
.inspect_err(|e| warn!("failed to load musicDB: {e}"))
.ok()?;
info!(
"loaded musicDB, cost {}ms",
time.elapsed().unwrap_or_default().as_millis()
);
Some(db.into_iter().map(|entry| (entry.id, entry)).collect())
});
impl Level {
/// achievement: xxx.xxxx% * 10000
///
/// This will **NOT** ignore utage level, you can calculate a in-theory DX Rating.
///
/// On invalid input, it returns 0.
pub fn dx_rating(&self, achievement: i32) -> u32 {
let achievement = Decimal::new(achievement as _, 4);
let difficulty_rank: Decimal = self.difficulty.value;
let factor = match () {
// larger than best achievement
_ if achievement > dec!(101.0) => return 0,
_ if achievement >= SSS_PLUS_THRESHOLD => SSS_PLUS_FACTOR,
_ if achievement >= SSS_PRO_THRESHOLD => SSS_PRO_FACTOR,
_ if achievement >= SSS_THRESHOLD => SSS_FACTOR,
_ if achievement >= SS_PLUS_PRO_THRESHOLD => SS_PLUS_PRO_FACTOR,
_ if achievement >= SS_PLUS_THRESHOLD => SS_PLUS_FACTOR,
_ if achievement >= SS_THRESHOLD => SS_FACTOR,
_ if achievement >= S_PLUS_PRO_THRESHOLD => S_PLUS_PRO_FACTOR,
_ if achievement >= S_PLUS_THRESHOLD => S_PLUS_FACTOR,
_ if achievement >= S_THRESHOLD => S_FACTOR,
_ if achievement >= AAA_PRO_THRESHOLD => AAA_PRO_FACTOR,
_ if achievement >= AAA_THRESHOLD => AAA_FACTOR,
_ if achievement >= AA_THRESHOLD => AA_FACTOR,
_ if achievement >= A_THRESHOLD => A_FACTOR,
// lower than A rank, does not get rating.
_ => return 0,
};
// when ach > 100.5%, calculate as 100.5%
(factor * difficulty_rank * achievement.min(Decimal::new(1005, 1)))
.floor()
.try_into()
.unwrap_or_default()
}
}
const SSS_PLUS_THRESHOLD: Decimal = dec!(100.5);
const SSS_PLUS_FACTOR: Decimal = dec!(0.224);
const SSS_PRO_THRESHOLD: Decimal = dec!(100.4999);
const SSS_PRO_FACTOR: Decimal = dec!(0.222);
const SSS_THRESHOLD: Decimal = dec!(100);
const SSS_FACTOR: Decimal = dec!(0.216);
const SS_PLUS_PRO_THRESHOLD: Decimal = dec!(99.9999);
const SS_PLUS_PRO_FACTOR: Decimal = dec!(0.214);
const SS_PLUS_THRESHOLD: Decimal = dec!(99.5);
const SS_PLUS_FACTOR: Decimal = dec!(0.211);
const SS_THRESHOLD: Decimal = dec!(99);
const SS_FACTOR: Decimal = dec!(0.208);
const S_PLUS_PRO_THRESHOLD: Decimal = dec!(98.9999);
const S_PLUS_PRO_FACTOR: Decimal = dec!(0.206);
const S_PLUS_THRESHOLD: Decimal = dec!(98);
const S_PLUS_FACTOR: Decimal = dec!(0.203);
const S_THRESHOLD: Decimal = dec!(97);
const S_FACTOR: Decimal = dec!(0.2);
const AAA_PRO_THRESHOLD: Decimal = dec!(96.9999);
const AAA_PRO_FACTOR: Decimal = dec!(0.176);
const AAA_THRESHOLD: Decimal = dec!(94);
const AAA_FACTOR: Decimal = dec!(0.168);
const AA_THRESHOLD: Decimal = dec!(90);
const AA_FACTOR: Decimal = dec!(0.152);
const A_THRESHOLD: Decimal = dec!(80);
const A_FACTOR: Decimal = dec!(0.136);
/*
TODO: calculate (below) BBB dx rating
[0, 0, 'd'],
[10, 1.6, 'd'],
[20, 3.2, 'd'],
[30, 4.8, 'd'],
[40, 6.4, 'd'],
[50, 8, 'c'],
[60, 9.6, 'b'],
[70, 11.2, 'bb'],
[75, 12.0, 'bbb'],
[79.9999, 12.8, 'bbb'],
*/

View File

@@ -1,10 +1,9 @@
use crate::{
helper::query_music,
title::model::{
dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId},
get_user_music_api::UserMusicDetail,
get_user_rating_api::{MusicRating, UserRating},
},
use music_db::query_music;
use crate::title::model::{
dxrating::{DxCalculatedEntries, DxLevelName, DxMusicRecord, DxSheetId},
get_user_music_api::UserMusicDetail,
get_user_rating_api::{MusicRating, UserRating},
};
impl DxCalculatedEntries {

View File

@@ -1,11 +1,11 @@
use std::fmt::Display;
use music_db::query_music;
use music_db::query_music_level;
use serde::Deserialize;
use serde::Serialize;
use crate::helper::level_name;
use crate::helper::query_music;
use crate::helper::query_music_level;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]

View File

@@ -2,12 +2,12 @@ use std::fmt::Display;
use bincode::Decode;
use bincode::Encode;
use music_db::query_music;
use music_db::query_music_level;
use serde::Deserialize;
use serde::Serialize;
use crate::helper::level_name;
use crate::helper::query_music;
use crate::helper::query_music_level;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]