From 978350ffdfbe73cf29717ebb0a63a4dbbf59f46a Mon Sep 17 00:00:00 2001 From: javalsai Date: Sun, 31 May 2026 00:33:05 +0200 Subject: [PATCH] perf: improve top-20 leaderboard algorithm --- src/main.rs | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7d4aff0..c07c40b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,12 +48,14 @@ enum LeaderboardUpdateType { 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_LOSCORES: &str = "loscores.json"; const PATH_PINGSCORES: &str = "pingscores.json"; +const MAX_LEADERBOARD: usize = 20; + #[tokio::main] async fn main() { fn read_file de::Deserialize<'de>>(file_path: &str) -> Arc> { @@ -61,8 +63,17 @@ async fn main() { 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(mut vec: impl DerefMut>) { + 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>> = read_file(PATH_HISCORES); + exact_leaderboard(hiscores.lock().await); let loscores: Arc>> = read_file(PATH_LOSCORES); + exact_leaderboard(loscores.lock().await); let pingscores: Arc>> = read_file(PATH_PINGSCORES); let (tx, rx) = mpsc::channel::(1024); @@ -129,22 +140,29 @@ async fn handle_hiscores( ) { fn update_scoretable> + DerefMut>( score_name: &str, - mut scoretable: G, + mut scoretable_lock: G, name: &str, score: u32, 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}"); - scoretable.push(Entry { - score, - person: name.to_string(), - }); - scoretable.sort(); - scoretable.reverse(); - scoretable.truncate(20); - let file_contents: String = serde_json::to_string(&*scoretable).unwrap(); - drop(scoretable); + scoretable[index_to_insert_at..].rotate_right(1); + let push_out = std::mem::replace( + &mut scoretable[index_to_insert_at], + Entry { + score, + person: name.to_string(), + }, + ); + 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() .write(true) .truncate(true) @@ -152,6 +170,12 @@ async fn handle_hiscores( .unwrap(); file.write_all(file_contents.as_bytes()).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(), + }); } }