perf: improve top-20 leaderboard algorithm

This commit is contained in:
2026-05-31 00:33:05 +02:00
parent 8bee404f80
commit 978350ffdf
+36 -12
View File
@@ -48,12 +48,14 @@ enum LeaderboardUpdateType {
Increment { loscore: u32 }, Increment { loscore: u32 },
} }
static CHANCE: f64 = 1.0 / 3.0; const CHANCE: f64 = 1.0 / 3.0;
const PATH_HISCORES: &str = "hiscores.json"; const PATH_HISCORES: &str = "hiscores.json";
const PATH_LOSCORES: &str = "loscores.json"; const PATH_LOSCORES: &str = "loscores.json";
const PATH_PINGSCORES: &str = "pingscores.json"; const PATH_PINGSCORES: &str = "pingscores.json";
const MAX_LEADERBOARD: usize = 20;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
fn read_file<T: for<'de> de::Deserialize<'de>>(file_path: &str) -> Arc<Mutex<T>> { fn read_file<T: for<'de> de::Deserialize<'de>>(file_path: &str) -> Arc<Mutex<T>> {
@@ -61,8 +63,17 @@ async fn main() {
Arc::new(Mutex::new(serde_json::from_str(&file_contents).unwrap())) Arc::new(Mutex::new(serde_json::from_str(&file_contents).unwrap()))
} }
/// Makes the vector at `vec` one with a capacity of exactly [`MAX_LEADERBOARD`] if `vec` is
/// smaller or equal.
fn exact_leaderboard<T>(mut vec: impl DerefMut<Target = Vec<T>>) {
let mut old_vec = std::mem::replace(&mut *vec, Vec::with_capacity(MAX_LEADERBOARD));
old_vec.drain(..).for_each(|e| vec.push(e));
}
let hiscores: Arc<Mutex<Vec<Entry>>> = read_file(PATH_HISCORES); let hiscores: Arc<Mutex<Vec<Entry>>> = read_file(PATH_HISCORES);
exact_leaderboard(hiscores.lock().await);
let loscores: Arc<Mutex<Vec<Entry>>> = read_file(PATH_LOSCORES); let loscores: Arc<Mutex<Vec<Entry>>> = read_file(PATH_LOSCORES);
exact_leaderboard(loscores.lock().await);
let pingscores: Arc<Mutex<HashMap<String, (u64, u32)>>> = read_file(PATH_PINGSCORES); let pingscores: Arc<Mutex<HashMap<String, (u64, u32)>>> = read_file(PATH_PINGSCORES);
let (tx, rx) = mpsc::channel::<LeaderboardUpdate>(1024); let (tx, rx) = mpsc::channel::<LeaderboardUpdate>(1024);
@@ -129,22 +140,29 @@ async fn handle_hiscores(
) { ) {
fn update_scoretable<G: Deref<Target = Vec<Entry>> + DerefMut>( fn update_scoretable<G: Deref<Target = Vec<Entry>> + DerefMut>(
score_name: &str, score_name: &str,
mut scoretable: G, mut scoretable_lock: G,
name: &str, name: &str,
score: u32, score: u32,
file_path: &str, file_path: &str,
) { ) {
if scoretable.last().is_none_or(|e| score > e.score) { let scoretable = &mut *scoretable_lock;
if let Some(index_to_insert_at) = scoretable.iter().position(|e| score > e.score) {
println!("New {score_name} {score} by {name}"); println!("New {score_name} {score} by {name}");
scoretable.push(Entry { scoretable[index_to_insert_at..].rotate_right(1);
score, let push_out = std::mem::replace(
person: name.to_string(), &mut scoretable[index_to_insert_at],
}); Entry {
scoretable.sort(); score,
scoretable.reverse(); person: name.to_string(),
scoretable.truncate(20); },
let file_contents: String = serde_json::to_string(&*scoretable).unwrap(); );
drop(scoretable); if scoretable.len() < MAX_LEADERBOARD {
scoretable.push(push_out);
}
let file_contents: String = serde_json::to_string(&*scoretable_lock).unwrap();
drop(scoretable_lock);
let mut file = fs::OpenOptions::new() let mut file = fs::OpenOptions::new()
.write(true) .write(true)
.truncate(true) .truncate(true)
@@ -152,6 +170,12 @@ async fn handle_hiscores(
.unwrap(); .unwrap();
file.write_all(file_contents.as_bytes()).unwrap(); file.write_all(file_contents.as_bytes()).unwrap();
file.flush().unwrap(); file.flush().unwrap();
} else if scoretable.len() < MAX_LEADERBOARD {
println!("New {score_name} {score} by {name}");
scoretable.push(Entry {
score,
person: name.to_string(),
});
} }
} }