Simplify lb

This commit is contained in:
Natsu Kagami 2024-02-14 04:23:09 +01:00 committed by Natsu Kagami
parent 48bd49b3bd
commit c2562fbbca
3 changed files with 63 additions and 184 deletions

View file

@ -111,19 +111,6 @@ impl OsuUserBests {
} }
impl OsuUserBests { impl OsuUserBests {
pub async fn by_beatmap(&self, beatmap_id: u64, mode: Mode) -> Result<Vec<(UserId, Score)>> {
let scores = models::UserBestScore::by_map(beatmap_id as i64, mode as u8, &self.0).await?;
Ok(scores
.into_iter()
.map(|us| {
(
UserId(us.user_id as u64),
bincode::deserialize(&us.score[..]).unwrap(),
)
})
.collect())
}
pub async fn save( pub async fn save(
&self, &self,
user: impl Into<UserId>, user: impl Into<UserId>,

View file

@ -33,7 +33,7 @@ use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserBests};
use embeds::{beatmap_embed, score_embed, user_embed}; use embeds::{beatmap_embed, score_embed, user_embed};
use hook::SHORT_LINK_REGEX; use hook::SHORT_LINK_REGEX;
pub use hook::{dot_osu_hook, hook}; pub use hook::{dot_osu_hook, hook};
use server_rank::{SERVER_RANK_COMMAND, UPDATE_LEADERBOARD_COMMAND}; use server_rank::{SERVER_RANK_COMMAND, SHOW_LEADERBOARD_COMMAND};
/// The osu! client. /// The osu! client.
pub(crate) struct OsuClient; pub(crate) struct OsuClient;
@ -64,11 +64,6 @@ pub async fn setup(
data.insert::<OsuLastBeatmap>(OsuLastBeatmap::new(sql_client.clone())); data.insert::<OsuLastBeatmap>(OsuLastBeatmap::new(sql_client.clone()));
data.insert::<OsuUserBests>(OsuUserBests::new(sql_client.clone())); data.insert::<OsuUserBests>(OsuUserBests::new(sql_client.clone()));
// Locks
data.insert::<server_rank::update_lock::UpdateLock>(
server_rank::update_lock::UpdateLock::default(),
);
// API client // API client
let http_client = data.get::<HTTPClient>().unwrap().clone(); let http_client = data.get::<HTTPClient>().unwrap().clone();
let mk_osu_client = || async { let mk_osu_client = || async {
@ -120,7 +115,7 @@ pub async fn setup(
check, check,
top, top,
server_rank, server_rank,
update_leaderboard, show_leaderboard,
clean_cache clean_cache
)] )]
#[default_command(std)] #[default_command(std)]

View file

@ -1,22 +1,18 @@
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr, sync::Arc};
use super::{ use super::{db::OsuSavedUsers, ModeArg, OsuClient};
db::{OsuSavedUsers, OsuUserBests},
ModeArg, OsuClient,
};
use crate::{ use crate::{
discord::{ discord::{
display::ScoreListStyle, display::ScoreListStyle,
oppai_cache::{Accuracy, BeatmapCache}, oppai_cache::{Accuracy, BeatmapCache},
BeatmapWithMode,
}, },
models::{Mode, Mods, Score}, models::{Mode, Mods},
request::UserID, request::UserID,
}; };
use serenity::{ use serenity::{
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
model::{channel::Message, id::UserId}, model::channel::Message,
utils::MessageBuilder, utils::MessageBuilder,
}; };
use youmubot_prelude::*; use youmubot_prelude::*;
@ -166,39 +162,6 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR
Ok(()) Ok(())
} }
pub(crate) mod update_lock {
use serenity::{model::id::GuildId, prelude::TypeMapKey};
use std::collections::HashSet;
use std::sync::Mutex;
#[derive(Debug, Default)]
pub struct UpdateLock(Mutex<HashSet<GuildId>>);
pub struct UpdateLockGuard<'a>(&'a UpdateLock, GuildId);
impl TypeMapKey for UpdateLock {
type Value = UpdateLock;
}
impl UpdateLock {
pub fn get(&self, guild: GuildId) -> Option<UpdateLockGuard> {
let mut set = self.0.lock().unwrap();
if set.contains(&guild) {
None
} else {
set.insert(guild);
Some(UpdateLockGuard(self, guild))
}
}
}
impl<'a> Drop for UpdateLockGuard<'a> {
fn drop(&mut self) {
let mut set = self.0 .0.lock().unwrap();
set.remove(&self.1);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OrderBy { enum OrderBy {
PP, PP,
@ -229,21 +192,13 @@ impl std::str::FromStr for OrderBy {
#[description = "See the server's ranks on the last seen beatmap"] #[description = "See the server's ranks on the last seen beatmap"]
#[max_args(2)] #[max_args(2)]
#[only_in(guilds)] #[only_in(guilds)]
pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult { pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult {
let sort_order = args.single::<OrderBy>().unwrap_or_default(); let order = args.single::<OrderBy>().unwrap_or_default();
let style = args.single::<ScoreListStyle>().unwrap_or_default(); let style = args.single::<ScoreListStyle>().unwrap_or_default();
let guild = m.guild_id.unwrap();
let data = ctx.data.read().await; let data = ctx.data.read().await;
let update_lock = data.get::<update_lock::UpdateLock>().unwrap();
let update_lock = match update_lock.get(guild) { let (bm, _) = match super::load_beatmap(ctx, m).await {
None => {
m.reply(&ctx, "Another update is running.").await?;
return Ok(());
}
Some(v) => v,
};
let (bm, mods) = match super::load_beatmap(ctx, m).await {
Some((bm, mods_def)) => { Some((bm, mods_def)) => {
let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD); let mods = args.find::<Mods>().ok().or(mods_def).unwrap_or(Mods::NOMOD);
(bm, mods) (bm, mods)
@ -253,135 +208,77 @@ pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> C
return Ok(()); return Ok(());
} }
}; };
let mode = bm.1;
let member_cache = data.get::<MemberCache>().unwrap();
// Signal that we are running.
let running_reaction = m.react(&ctx, '⌛').await?;
// Run a check on everyone in the server basically. let osu = data.get::<OsuClient>().unwrap().clone();
let all_server_users: Vec<(UserId, Vec<Score>)> = {
let osu = data.get::<OsuClient>().unwrap(); // Get oppai map.
let mode = bm.1;
let oppai = data.get::<BeatmapCache>().unwrap();
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
let guild = m.guild_id.expect("Guild-only command");
let scores = {
const NO_SCORES: &str = "No scores have been recorded for this beatmap.";
// Signal that we are running.
let running_reaction = m.react(&ctx, '⌛').await?;
let osu_users = data let osu_users = data
.get::<OsuSavedUsers>() .get::<OsuSavedUsers>()
.unwrap() .unwrap()
.all() .all()
.await? .await?
.into_iter() .into_iter()
.map(|osu_user| (osu_user.user_id, osu_user.id)); .map(|v| (v.user_id, v))
let beatmap_id = bm.0.beatmap_id; .collect::<HashMap<_, _>>();
osu_users let mut scores = guild
.map(|(user_id, osu_id)| { .members_iter(&ctx)
member_cache .filter_map(|mem| {
.query(&ctx, user_id, guild) future::ready(
.map(move |t| t.map(|_| (user_id, osu_id))) mem.ok()
.and_then(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id))),
)
}) })
.collect::<stream::FuturesUnordered<_>>() .filter_map(|(mem, osu_id)| {
.filter_map(future::ready) osu.scores(bm.0.beatmap_id, move |f| {
.filter_map(|(member, osu_id)| async move { f.user(UserID::ID(osu_id)).mode(bm.1)
let scores = osu })
.scores(beatmap_id, |f| f.user(UserID::ID(osu_id)).mode(mode)) .map(|r| Some((mem, r.ok()?)))
.await
.ok();
scores
.filter(|s| !s.is_empty())
.map(|scores| (member, scores))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.await .await
};
let updated_users = all_server_users.len();
// Update everything.
{
let db = data.get::<OsuUserBests>().unwrap();
all_server_users
.into_iter() .into_iter()
.map(|(u, scores)| db.save(u, mode, scores)) .flat_map(|(mem, scores)| {
.collect::<stream::FuturesUnordered<_>>() let mem = Arc::new(mem);
.try_collect::<()>() scores
.await?; .into_iter()
} .filter_map(|score| {
// Signal update complete. let pp = score.pp.or_else(|| {
running_reaction.delete(&ctx).await.ok(); oppai_map
m.reply( .get_pp_from(
&ctx, mode,
format!( Some(score.max_combo as usize),
"update for beatmap (`{}`) complete, {} users updated.", Accuracy::ByCount(
bm.0.short_link(if bm.mode() != bm.1 { Some(bm.1) } else { None }, None), score.count_300,
updated_users score.count_100,
), score.count_50,
) score.count_miss,
.await ),
.ok(); score.mods,
drop(update_lock); )
show_leaderboard(ctx, m, bm, mods, sort_order, style).await .ok()
} })?;
Some((pp, mem.clone(), score))
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
async fn show_leaderboard( running_reaction.delete(&ctx).await?;
ctx: &Context,
m: &Message,
bm: BeatmapWithMode,
mods: Mods,
order: OrderBy,
style: ScoreListStyle,
) -> CommandResult {
let data = ctx.data.read().await;
// Get oppai map.
let mode = bm.1;
let oppai = data.get::<BeatmapCache>().unwrap();
let oppai_map = oppai.get_beatmap(bm.0.beatmap_id).await?;
let get_oppai_pp = move |combo: u64, acc: Accuracy, mods: Mods| {
oppai_map
.get_pp_from(mode, Some(combo as usize), acc, mods)
.ok()
};
let guild = m.guild_id.expect("Guild-only command");
let member_cache = data.get::<MemberCache>().unwrap();
let scores = {
const NO_SCORES: &str = "No scores have been recorded for this beatmap.";
let scores = data
.get::<OsuUserBests>()
.unwrap()
.by_beatmap(bm.0.beatmap_id, bm.1)
.await?;
if scores.is_empty() { if scores.is_empty() {
m.reply(&ctx, NO_SCORES).await?; m.reply(&ctx, NO_SCORES).await?;
return Ok(()); return Ok(());
} }
let mut scores: Vec<(f64, String, Score)> = scores
.into_iter()
.filter(|(_, score)| score.mods.contains(mods))
.map(|(user_id, score)| {
member_cache
.query(&ctx, user_id, guild)
.map(|m| m.map(move |m| (m.distinct(), score)))
})
.collect::<stream::FuturesUnordered<_>>()
.filter_map(future::ready)
.filter_map(|(user, score)| {
future::ready(
score
.pp
.or_else(|| {
get_oppai_pp(
score.max_combo,
Accuracy::ByCount(
score.count_300,
score.count_100,
score.count_50,
score.count_miss,
),
score.mods,
)
})
.map(|v| (v, user, score)),
)
})
.collect::<Vec<_>>()
.await;
match order { match order {
OrderBy::PP => scores.sort_by(|(a, _, _), (b, _, _)| { OrderBy::PP => scores.sort_by(|(a, _, _), (b, _, _)| {
b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal) b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)