diff --git a/youmubot-osu/src/discord/db.rs b/youmubot-osu/src/discord/db.rs index 78def35..9cf38a0 100644 --- a/youmubot-osu/src/discord/db.rs +++ b/youmubot-osu/src/discord/db.rs @@ -111,19 +111,6 @@ impl OsuUserBests { } impl OsuUserBests { - pub async fn by_beatmap(&self, beatmap_id: u64, mode: Mode) -> Result> { - 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( &self, user: impl Into, diff --git a/youmubot-osu/src/discord/mod.rs b/youmubot-osu/src/discord/mod.rs index 04db150..35b13bf 100644 --- a/youmubot-osu/src/discord/mod.rs +++ b/youmubot-osu/src/discord/mod.rs @@ -33,7 +33,7 @@ use db::{OsuLastBeatmap, OsuSavedUsers, OsuUser, OsuUserBests}; use embeds::{beatmap_embed, score_embed, user_embed}; use hook::SHORT_LINK_REGEX; 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. pub(crate) struct OsuClient; @@ -64,11 +64,6 @@ pub async fn setup( data.insert::(OsuLastBeatmap::new(sql_client.clone())); data.insert::(OsuUserBests::new(sql_client.clone())); - // Locks - data.insert::( - server_rank::update_lock::UpdateLock::default(), - ); - // API client let http_client = data.get::().unwrap().clone(); let mk_osu_client = || async { @@ -120,7 +115,7 @@ pub async fn setup( check, top, server_rank, - update_leaderboard, + show_leaderboard, clean_cache )] #[default_command(std)] diff --git a/youmubot-osu/src/discord/server_rank.rs b/youmubot-osu/src/discord/server_rank.rs index 1eba0c1..66304c5 100644 --- a/youmubot-osu/src/discord/server_rank.rs +++ b/youmubot-osu/src/discord/server_rank.rs @@ -1,22 +1,18 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, str::FromStr, sync::Arc}; -use super::{ - db::{OsuSavedUsers, OsuUserBests}, - ModeArg, OsuClient, -}; +use super::{db::OsuSavedUsers, ModeArg, OsuClient}; use crate::{ discord::{ display::ScoreListStyle, oppai_cache::{Accuracy, BeatmapCache}, - BeatmapWithMode, }, - models::{Mode, Mods, Score}, + models::{Mode, Mods}, request::UserID, }; use serenity::{ framework::standard::{macros::command, Args, CommandResult}, - model::{channel::Message, id::UserId}, + model::channel::Message, utils::MessageBuilder, }; use youmubot_prelude::*; @@ -166,39 +162,6 @@ pub async fn server_rank(ctx: &Context, m: &Message, mut args: Args) -> CommandR 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>); - - pub struct UpdateLockGuard<'a>(&'a UpdateLock, GuildId); - - impl TypeMapKey for UpdateLock { - type Value = UpdateLock; - } - - impl UpdateLock { - pub fn get(&self, guild: GuildId) -> Option { - 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)] enum OrderBy { PP, @@ -229,21 +192,13 @@ impl std::str::FromStr for OrderBy { #[description = "See the server's ranks on the last seen beatmap"] #[max_args(2)] #[only_in(guilds)] -pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult { - let sort_order = args.single::().unwrap_or_default(); +pub async fn show_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> CommandResult { + let order = args.single::().unwrap_or_default(); let style = args.single::().unwrap_or_default(); - let guild = m.guild_id.unwrap(); let data = ctx.data.read().await; - let update_lock = data.get::().unwrap(); - let update_lock = match update_lock.get(guild) { - None => { - m.reply(&ctx, "Another update is running.").await?; - return Ok(()); - } - Some(v) => v, - }; - let (bm, mods) = match super::load_beatmap(ctx, m).await { + + let (bm, _) = match super::load_beatmap(ctx, m).await { Some((bm, mods_def)) => { let mods = args.find::().ok().or(mods_def).unwrap_or(Mods::NOMOD); (bm, mods) @@ -253,135 +208,77 @@ pub async fn update_leaderboard(ctx: &Context, m: &Message, mut args: Args) -> C return Ok(()); } }; - let mode = bm.1; - let member_cache = data.get::().unwrap(); - // Signal that we are running. - let running_reaction = m.react(&ctx, '⌛').await?; - // Run a check on everyone in the server basically. - let all_server_users: Vec<(UserId, Vec)> = { - let osu = data.get::().unwrap(); + let osu = data.get::().unwrap().clone(); + + // Get oppai map. + let mode = bm.1; + let oppai = data.get::().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 .get::() .unwrap() .all() .await? .into_iter() - .map(|osu_user| (osu_user.user_id, osu_user.id)); - let beatmap_id = bm.0.beatmap_id; - osu_users - .map(|(user_id, osu_id)| { - member_cache - .query(&ctx, user_id, guild) - .map(move |t| t.map(|_| (user_id, osu_id))) + .map(|v| (v.user_id, v)) + .collect::>(); + let mut scores = guild + .members_iter(&ctx) + .filter_map(|mem| { + future::ready( + mem.ok() + .and_then(|m| osu_users.get(&m.user.id).map(|ou| (m.distinct(), ou.id))), + ) }) - .collect::>() - .filter_map(future::ready) - .filter_map(|(member, osu_id)| async move { - let scores = osu - .scores(beatmap_id, |f| f.user(UserID::ID(osu_id)).mode(mode)) - .await - .ok(); - scores - .filter(|s| !s.is_empty()) - .map(|scores| (member, scores)) + .filter_map(|(mem, osu_id)| { + osu.scores(bm.0.beatmap_id, move |f| { + f.user(UserID::ID(osu_id)).mode(bm.1) + }) + .map(|r| Some((mem, r.ok()?))) }) .collect::>() .await - }; - let updated_users = all_server_users.len(); - // Update everything. - { - let db = data.get::().unwrap(); - all_server_users .into_iter() - .map(|(u, scores)| db.save(u, mode, scores)) - .collect::>() - .try_collect::<()>() - .await?; - } - // Signal update complete. - running_reaction.delete(&ctx).await.ok(); - m.reply( - &ctx, - format!( - "update for beatmap (`{}`) complete, {} users updated.", - bm.0.short_link(if bm.mode() != bm.1 { Some(bm.1) } else { None }, None), - updated_users - ), - ) - .await - .ok(); - drop(update_lock); - show_leaderboard(ctx, m, bm, mods, sort_order, style).await -} + .flat_map(|(mem, scores)| { + let mem = Arc::new(mem); + scores + .into_iter() + .filter_map(|score| { + let pp = score.pp.or_else(|| { + oppai_map + .get_pp_from( + mode, + Some(score.max_combo as usize), + Accuracy::ByCount( + score.count_300, + score.count_100, + score.count_50, + score.count_miss, + ), + score.mods, + ) + .ok() + })?; + Some((pp, mem.clone(), score)) + }) + .collect::>() + }) + .collect::>(); -async fn show_leaderboard( - ctx: &Context, - m: &Message, - bm: BeatmapWithMode, - mods: Mods, - order: OrderBy, - style: ScoreListStyle, -) -> CommandResult { - let data = ctx.data.read().await; + running_reaction.delete(&ctx).await?; - // Get oppai map. - let mode = bm.1; - let oppai = data.get::().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::().unwrap(); - let scores = { - const NO_SCORES: &str = "No scores have been recorded for this beatmap."; - - let scores = data - .get::() - .unwrap() - .by_beatmap(bm.0.beatmap_id, bm.1) - .await?; if scores.is_empty() { m.reply(&ctx, NO_SCORES).await?; 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::>() - .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::>() - .await; match order { OrderBy::PP => scores.sort_by(|(a, _, _), (b, _, _)| { b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)