Introduce a Scores stream so we can lazily load top score requests

This commit is contained in:
Natsu Kagami 2025-05-12 23:59:13 +02:00
parent fcf3ed60d2
commit 2756c463d5
Signed by: nki
GPG key ID: 55A032EB38B49ADB
9 changed files with 427 additions and 234 deletions

View file

@ -35,8 +35,9 @@ use crate::{
},
models::{Beatmap, Mode, Mods, Score, User},
mods::UnparsedMods,
request::{BeatmapRequestKind, UserID, SCORE_COUNT_LIMIT},
OsuClient as OsuHttpClient, UserHeader,
request::{BeatmapRequestKind, UserID},
scores::Scores,
OsuClient as OsuHttpClient, UserHeader, MAX_TOP_SCORES_INDEX,
};
mod announcer;
@ -304,7 +305,8 @@ pub(crate) async fn find_save_requirements(
] {
let scores = client
.user_best(UserID::ID(u.id), |f| f.mode(*mode))
.try_collect::<Vec<_>>()
.await?
.get_all()
.await?;
if let Some(v) = scores.into_iter().choose(&mut rand::thread_rng()) {
return Ok(Some((v, *mode)));
@ -351,10 +353,12 @@ pub(crate) async fn handle_save_respond(
) -> Result<()> {
let osu_client = &env.client;
async fn check(client: &OsuHttpClient, u: &User, mode: Mode, map_id: u64) -> Result<bool> {
client
.user_recent(UserID::ID(u.id), |f| f.mode(mode).limit(1))
.try_any(|s| future::ready(s.beatmap_id == map_id))
.await
Ok(client
.user_recent(UserID::ID(u.id), |f| f.mode(mode))
.await?
.get(0)
.await?
.is_some_and(|s| s.beatmap_id == map_id))
}
let msg_id = reply.get_message().await?.id;
let recv = InteractionCollector::create(&ctx, msg_id).await?;
@ -498,13 +502,17 @@ pub(crate) struct UserExtras {
impl UserExtras {
// Collect UserExtras from the given user.
pub async fn from_user(env: &OsuEnv, user: &User, mode: Mode) -> Result<Self> {
let scores = env
.client
.user_best(UserID::ID(user.id), |f| f.mode(mode))
.try_collect::<Vec<_>>()
.await
.pls_ok()
.unwrap_or_else(std::vec::Vec::new);
let scores = {
match env
.client
.user_best(UserID::ID(user.id), |f| f.mode(mode))
.await
.pls_ok()
{
Some(v) => v.get_all().await.pls_ok().unwrap_or_else(Vec::new),
None => Vec::new(),
}
};
let (length, age) = join!(
calculate_weighted_map_length(&scores, &env.beatmaps, mode),
@ -589,7 +597,7 @@ impl ListingArgs {
sender: serenity::all::UserId,
) -> Result<Self> {
let nth = index
.filter(|&v| 1 <= v && v <= SCORE_COUNT_LIMIT as u8)
.filter(|&v| 1 <= v && v <= MAX_TOP_SCORES_INDEX as u8)
.map(|v| v - 1)
.map(Nth::Nth)
.unwrap_or_default();
@ -632,7 +640,7 @@ async fn user_header_or_default_id(
Some(UsernameArg::Raw(r)) => {
let user = env
.client
.user(&UserID::Username(r), |f| f)
.user(&UserID::Username(Arc::new(r)), |f| f)
.await?
.ok_or(Error::msg("User not found"))?;
(user.preferred_mode, user.into())
@ -678,30 +686,40 @@ pub async fn recent(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::Table).await?;
let osu_client = &env.client;
let plays = osu_client.user_recent(UserID::ID(user.id), |f| f.mode(mode));
let mut plays = osu_client
.user_recent(UserID::ID(user.id), |f| f.mode(mode))
.await?;
match nth {
Nth::All => {
let header = format!("Here are the recent plays by {}!", user.mention());
let reply = msg.reply(ctx, &header).await?;
style
.display_scores(
plays.try_collect::<Vec<_>>(),
ctx,
reply.guild_id,
(reply, ctx).with_header(header),
)
.display_scores(plays, ctx, reply.guild_id, (reply, ctx).with_header(header))
.await?;
}
Nth::Nth(nth) => {
let plays = std::pin::pin!(plays.into_stream());
let (play, rest) = plays.skip(nth as usize).into_future().await;
let play = play.ok_or(Error::msg("No such play"))??;
let attempts = rest
.try_take_while(|p| {
future::ok(p.beatmap_id == play.beatmap_id && p.mods == play.mods)
})
.count()
.await;
let play = plays
.get(nth as usize)
.await?
.ok_or(Error::msg("No such play"))?
.clone();
let attempts = {
let mut count = 0usize;
while plays
.get(nth as usize + count + 1)
.await
.ok()
.flatten()
.is_some_and(|p| {
p.beatmap_id == play.beatmap_id
&& p.mode == play.mode
&& p.mods == play.mods
})
{
count += 1;
}
count
};
let beatmap = env.beatmaps.get_beatmap(play.beatmap_id, mode).await?;
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
let beatmap_mode = BeatmapWithMode(beatmap, Some(mode));
@ -751,26 +769,22 @@ pub async fn pins(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
let osu_client = &env.client;
let plays = osu_client.user_pins(UserID::ID(user.id), |f| f.mode(mode));
let mut plays = osu_client
.user_pins(UserID::ID(user.id), |f| f.mode(mode))
.await?;
match nth {
Nth::All => {
let header = format!("Here are the pinned plays by `{}`!", user.username);
let reply = msg.reply(ctx, &header).await?;
style
.display_scores(
plays.try_collect::<Vec<_>>(),
ctx,
reply.guild_id,
(reply, ctx).with_header(header),
)
.display_scores(plays, ctx, reply.guild_id, (reply, ctx).with_header(header))
.await?;
}
Nth::Nth(nth) => {
let play = std::pin::pin!(plays.into_stream())
.skip(nth as usize)
.next()
.await
.ok_or(Error::msg("No such play"))??;
let play = plays
.get(nth as usize)
.await?
.ok_or(Error::msg("No such play"))?;
let beatmap = env.beatmaps.get_beatmap(play.beatmap_id, mode).await?;
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
let beatmap_mode = BeatmapWithMode(beatmap, Some(mode));
@ -1016,12 +1030,7 @@ pub async fn check(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
);
let reply = msg.reply(&ctx, &header).await?;
style
.display_scores(
future::ok(scores),
ctx,
msg.guild_id,
(reply, ctx).with_header(header),
)
.display_scores(scores, ctx, msg.guild_id, (reply, ctx).with_header(header))
.await?;
Ok(())
@ -1045,6 +1054,7 @@ pub(crate) async fn do_check(
let mods = mods.clone().and_then(|t| t.to_mods(m).ok());
osu_client
.scores(b.beatmap_id, |f| f.user(UserID::ID(user.id)).mode(m))
.and_then(|v| v.get_all())
.map_ok(move |mut v| {
v.retain(|s| mods.as_ref().is_none_or(|m| s.mods.contains(&m)));
v
@ -1088,15 +1098,16 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
} = ListingArgs::parse(&env, msg, &mut args, ScoreListStyle::default()).await?;
let osu_client = &env.client;
let plays = osu_client.user_best(UserID::ID(user.id), |f| f.mode(mode));
let mut plays = osu_client
.user_best(UserID::ID(user.id), |f| f.mode(mode))
.await?;
match nth {
Nth::Nth(nth) => {
let play = std::pin::pin!(plays.into_stream())
.skip(nth as usize)
.next()
.await
.ok_or(Error::msg("No such play"))??;
let play = plays
.get(nth as usize)
.await?
.ok_or(Error::msg("No such play"))?;
let beatmap = env.beatmaps.get_beatmap(play.beatmap_id, mode).await?;
let content = env.oppai.get_beatmap(beatmap.beatmap_id).await?;
@ -1126,12 +1137,7 @@ pub async fn top(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
let header = format!("Here are the top plays by {}!", user.mention());
let reply = msg.reply(&ctx, &header).await?;
style
.display_scores(
plays.try_collect::<Vec<_>>(),
ctx,
msg.guild_id,
(reply, ctx).with_header(header),
)
.display_scores(plays, ctx, msg.guild_id, (reply, ctx).with_header(header))
.await?;
}
}