Merge pull request #11199 from smoogipoo/refactor-player-score-creation

Asyncify player score creation and submission
This commit is contained in:
Dean Herbert 2020-12-20 17:30:11 +09:00 committed by GitHub
commit 8ac76bd524
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 142 additions and 60 deletions

View File

@ -10,6 +10,8 @@ using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Storyboards;
using osuTK;
@ -50,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
cancel();
complete();
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).ResultsCreated);
}
/// <summary>
@ -84,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
// wait to ensure there was no attempt of pushing the results screen.
AddWaitStep("wait", resultsDisplayWaitCount);
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).ResultsCreated);
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
@ -110,16 +112,18 @@ namespace osu.Game.Tests.Visual.Gameplay
public class FakeRankingPushPlayer : TestPlayer
{
public bool GotoRankingInvoked;
public bool ResultsCreated { get; private set; }
public FakeRankingPushPlayer()
: base(true, true)
{
}
protected override void GotoRanking()
protected override ResultsScreen CreateResults(ScoreInfo score)
{
GotoRankingInvoked = true;
var results = base.CreateResults(score);
ResultsCreated = true;
return results;
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Logging;
@ -95,19 +96,36 @@ namespace osu.Game.Screens.Multi.Play
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true);
}
protected override ScoreInfo CreateScore()
protected override Score CreateScore()
{
var score = base.CreateScore();
score.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore());
score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore());
return score;
}
protected override async Task SubmitScore(Score score)
{
await base.SubmitScore(score);
Debug.Assert(token != null);
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score);
request.Success += s => score.OnlineScoreID = s.ID;
request.Failure += e => Logger.Error(e, "Failed to submit score");
api.Queue(request);
var tcs = new TaskCompletionSource<bool>();
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score.ScoreInfo);
return score;
request.Success += s =>
{
score.ScoreInfo.OnlineScoreID = s.ID;
tcs.SetResult(true);
};
request.Failure += e =>
{
Logger.Error(e, "Failed to submit score");
tcs.SetResult(false);
};
api.Queue(request);
await tcs.Task;
}
protected override void Dispose(bool isDisposing)

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -22,8 +23,10 @@ using osu.Game.Graphics.Containers;
using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
@ -501,6 +504,7 @@ namespace osu.Game.Screens.Play
}
private ScheduledDelegate completionProgressDelegate;
private Task<ScoreInfo> scoreSubmissionTask;
private void updateCompletionState(ValueChangedEvent<bool> completionState)
{
@ -527,33 +531,50 @@ namespace osu.Game.Screens.Play
if (!showResults) return;
scoreSubmissionTask ??= Task.Run(async () =>
{
var score = CreateScore();
try
{
await SubmitScore(score);
}
catch (Exception ex)
{
Logger.Error(ex, "Score submission failed!");
}
try
{
await ImportScore(score);
}
catch (Exception ex)
{
Logger.Error(ex, "Score import failed!");
}
return score.ScoreInfo;
});
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
completionProgressDelegate = Schedule(GotoRanking);
scheduleCompletion();
}
protected virtual ScoreInfo CreateScore()
private void scheduleCompletion() => completionProgressDelegate = Schedule(() =>
{
var score = new ScoreInfo
if (!scoreSubmissionTask.IsCompleted)
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = rulesetInfo,
Mods = Mods.Value.ToArray(),
};
if (DrawableRuleset.ReplayScore != null)
score.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
else
score.User = api.LocalUser.Value;
ScoreProcessor.PopulateScore(score);
return score;
scheduleCompletion();
return;
}
// screen may be in the exiting transition phase.
if (this.IsCurrentScreen())
this.Push(CreateResults(scoreSubmissionTask.Result));
});
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true);
#region Fail Logic
protected FailOverlay FailOverlay { get; private set; }
@ -748,38 +769,73 @@ namespace osu.Game.Screens.Play
return base.OnExiting(next);
}
protected virtual void GotoRanking()
/// <summary>
/// Creates the player's <see cref="Score"/>.
/// </summary>
/// <returns>The <see cref="Score"/>.</returns>
protected virtual Score CreateScore()
{
var score = new Score
{
ScoreInfo = new ScoreInfo
{
Beatmap = Beatmap.Value.BeatmapInfo,
Ruleset = rulesetInfo,
Mods = Mods.Value.ToArray(),
}
};
if (DrawableRuleset.ReplayScore != null)
{
// if a replay is present, we likely don't want to import into the local database.
this.Push(CreateResults(CreateScore()));
return;
score.ScoreInfo.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
score.Replay = DrawableRuleset.ReplayScore.Replay;
}
else
{
score.ScoreInfo.User = api.LocalUser.Value;
score.Replay = new Replay { Frames = recordingScore?.Replay.Frames.ToList() ?? new List<ReplayFrame>() };
}
LegacyByteArrayReader replayReader = null;
ScoreProcessor.PopulateScore(score.ScoreInfo);
var score = new Score { ScoreInfo = CreateScore() };
return score;
}
if (recordingScore?.Replay.Frames.Count > 0)
/// <summary>
/// Imports the player's <see cref="Score"/> to the local database.
/// </summary>
/// <param name="score">The <see cref="Score"/> to import.</param>
/// <returns>The imported score.</returns>
protected virtual Task ImportScore(Score score)
{
score.Replay = recordingScore.Replay;
// Replays are already populated and present in the game's database, so should not be re-imported.
if (DrawableRuleset.ReplayScore != null)
return Task.CompletedTask;
LegacyByteArrayReader replayReader;
using (var stream = new MemoryStream())
{
new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
}
return scoreManager.Import(score.ScoreInfo, replayReader);
}
scoreManager.Import(score.ScoreInfo, replayReader)
.ContinueWith(imported => Schedule(() =>
{
// screen may be in the exiting transition phase.
if (this.IsCurrentScreen())
this.Push(CreateResults(imported.Result));
}));
}
/// <summary>
/// Submits the player's <see cref="Score"/>.
/// </summary>
/// <param name="score">The <see cref="Score"/> to submit.</param>
/// <returns>The submitted score.</returns>
protected virtual Task SubmitScore(Score score) => Task.CompletedTask;
/// <summary>
/// Creates the <see cref="ResultsScreen"/> for a <see cref="ScoreInfo"/>.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to be displayed in the results screen.</param>
/// <returns>The <see cref="ResultsScreen"/>.</returns>
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true);
private void fadeOut(bool instant = false)
{

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Threading.Tasks;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Scoring;
@ -26,18 +27,21 @@ namespace osu.Game.Screens.Play
DrawableRuleset?.SetReplayScore(Score);
}
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
protected override ScoreInfo CreateScore()
protected override Score CreateScore()
{
var baseScore = base.CreateScore();
// Since the replay score doesn't contain statistics, we'll pass them through here.
Score.ScoreInfo.HitEvents = baseScore.HitEvents;
Score.ScoreInfo.HitEvents = baseScore.ScoreInfo.HitEvents;
return Score.ScoreInfo;
return Score;
}
// Don't re-import replay scores as they're already present in the database.
protected override Task ImportScore(Score score) => Task.CompletedTask;
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
public bool OnPressed(GlobalAction action)
{
switch (action)