Skip to content

Commit

Permalink
Skip score uploads when nothing was hit in score
Browse files Browse the repository at this point in the history
The client will not submit a play if hit statistics indicate that they
never hit anything:

	https://github.com/ppy/osu/blob/a47ccb8edd2392258b6b7e176b222a9ecd511fc0/osu.Game/Screens/Play/SubmittingPlayer.cs#L281

This check was not exercised spectator server-side, which meant that
scores with nothing hit would get completed and enqueued for upload that
would never succeed (because the score would never be submitted anyway).
To save on some processing, just port the same check server-side to skip
upload if nothing was hit.

I'm hoping the effects of this is going to be the decrease of the score
timeouts monitoring on sentry & datadog closer to real levels (see
discussion in ppy#220).
  • Loading branch information
bdach committed Mar 14, 2024
1 parent cc1d36d commit e08c3b2
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 5 deletions.
49 changes: 47 additions & 2 deletions osu.Server.Spectator.Tests/SpectatorHubTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,13 @@ public async Task ReplayDataIsSaved(bool savingEnabled)

await hub.BeginPlaySession(1234, state);
await hub.SendFrameData(new FrameDataBundle(
new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()),
new FrameHeader(new ScoreInfo
{
Statistics =
{
[HitResult.Great] = 1
}
}, new ScoreProcessorStatistics()),
new[] { new LegacyReplayFrame(1234, 0, 0, ReplayButtonState.None) }));
await hub.EndPlaySession(state);

Expand All @@ -145,6 +151,39 @@ public async Task ReplayDataIsSaved(bool savingEnabled)
mockScoreStorage.Verify(s => s.WriteAsync(It.IsAny<Score>()), Times.Never);
}

[Fact]
public async Task ReplaysWithoutAnyHitsAreDiscarded()
{
AppSettings.SaveReplays = true;

Mock<IHubCallerClients<ISpectatorClient>> mockClients = new Mock<IHubCallerClients<ISpectatorClient>>();
Mock<ISpectatorClient> mockReceiver = new Mock<ISpectatorClient>();
mockClients.Setup(clients => clients.All).Returns(mockReceiver.Object);
mockClients.Setup(clients => clients.Group(SpectatorHub.GetGroupId(streamer_id))).Returns(mockReceiver.Object);

Mock<HubCallerContext> mockContext = new Mock<HubCallerContext>();

mockContext.Setup(context => context.UserIdentifier).Returns(streamer_id.ToString());
hub.Context = mockContext.Object;
hub.Clients = mockClients.Object;

mockDatabase.Setup(db => db.GetScoreFromToken(1234)).Returns(Task.FromResult<SoloScore?>(new SoloScore
{
id = 456,
passed = true
}));

await hub.BeginPlaySession(1234, state);
await hub.SendFrameData(new FrameDataBundle(
new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()),
new[] { new LegacyReplayFrame(1234, 0, 0, ReplayButtonState.None) }));
await hub.EndPlaySession(state);

await scoreUploader.Flush();

mockScoreStorage.Verify(s => s.WriteAsync(It.IsAny<Score>()), Times.Never);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
Expand Down Expand Up @@ -287,7 +326,13 @@ public async Task ScoresAreOnlySavedOnRankedBeatmaps(BeatmapOnlineStatus status,

await hub.BeginPlaySession(1234, state);
await hub.SendFrameData(new FrameDataBundle(
new FrameHeader(new ScoreInfo(), new ScoreProcessorStatistics()),
new FrameHeader(new ScoreInfo
{
Statistics =
{
[HitResult.Great] = 10
}
}, new ScoreProcessorStatistics()),
new[] { new LegacyReplayFrame(1234, 0, 0, ReplayButtonState.None) }));
await hub.EndPlaySession(state);

Expand Down
13 changes: 10 additions & 3 deletions osu.Server.Spectator/Hubs/Spectator/SpectatorHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.Caching.Distributed;
using osu.Game.Beatmaps;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Server.Spectator.Database;
using osu.Server.Spectator.Database.Models;
Expand Down Expand Up @@ -145,10 +146,16 @@ public async Task EndPlaySession(SpectatorState state)
if (status < min_beatmap_status_for_replays || status > max_beatmap_status_for_replays)
return;

score.ScoreInfo.Date = DateTimeOffset.UtcNow;
// if the user never hit anything, further processing that depends on the score existing can be waived because the client won't have submitted the score anyway.
// note that this isn't an early return as we still want to end the play session.
// see: https://github.com/ppy/osu/blob/a47ccb8edd2392258b6b7e176b222a9ecd511fc0/osu.Game/Screens/Play/SubmittingPlayer.cs#L281
if (score.ScoreInfo.Statistics.Any(s => s.Key.IsHit() && s.Value > 0))
{
score.ScoreInfo.Date = DateTimeOffset.UtcNow;

scoreUploader.Enqueue(scoreToken.Value, score);
await scoreProcessedSubscriber.RegisterForNotificationAsync(Context.ConnectionId, Context.GetUserId(), scoreToken.Value);
scoreUploader.Enqueue(scoreToken.Value, score);
await scoreProcessedSubscriber.RegisterForNotificationAsync(Context.ConnectionId, Context.GetUserId(), scoreToken.Value);
}
}
finally
{
Expand Down

0 comments on commit e08c3b2

Please sign in to comment.