mirror of
https://github.com/ppy/osu
synced 2025-01-19 20:40:52 +00:00
Merge pull request #11199 from smoogipoo/refactor-player-score-creation
Asyncify player score creation and submission
This commit is contained in:
commit
8ac76bd524
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
||||
completionProgressDelegate = Schedule(GotoRanking);
|
||||
}
|
||||
|
||||
protected virtual ScoreInfo CreateScore()
|
||||
{
|
||||
var score = new ScoreInfo
|
||||
scoreSubmissionTask ??= Task.Run(async () =>
|
||||
{
|
||||
Beatmap = Beatmap.Value.BeatmapInfo,
|
||||
Ruleset = rulesetInfo,
|
||||
Mods = Mods.Value.ToArray(),
|
||||
};
|
||||
var score = CreateScore();
|
||||
|
||||
if (DrawableRuleset.ReplayScore != null)
|
||||
score.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
|
||||
else
|
||||
score.User = api.LocalUser.Value;
|
||||
try
|
||||
{
|
||||
await SubmitScore(score);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Score submission failed!");
|
||||
}
|
||||
|
||||
ScoreProcessor.PopulateScore(score);
|
||||
try
|
||||
{
|
||||
await ImportScore(score);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Score import failed!");
|
||||
}
|
||||
|
||||
return score;
|
||||
return score.ScoreInfo;
|
||||
});
|
||||
|
||||
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
||||
scheduleCompletion();
|
||||
}
|
||||
|
||||
private void scheduleCompletion() => completionProgressDelegate = Schedule(() =>
|
||||
{
|
||||
if (!scoreSubmissionTask.IsCompleted)
|
||||
{
|
||||
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,39 +769,74 @@ 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;
|
||||
}
|
||||
|
||||
LegacyByteArrayReader replayReader = null;
|
||||
|
||||
var score = new Score { ScoreInfo = CreateScore() };
|
||||
|
||||
if (recordingScore?.Replay.Frames.Count > 0)
|
||||
else
|
||||
{
|
||||
score.Replay = recordingScore.Replay;
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
|
||||
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
|
||||
}
|
||||
score.ScoreInfo.User = api.LocalUser.Value;
|
||||
score.Replay = new Replay { Frames = recordingScore?.Replay.Frames.ToList() ?? new List<ReplayFrame>() };
|
||||
}
|
||||
|
||||
scoreManager.Import(score.ScoreInfo, replayReader)
|
||||
.ContinueWith(imported => Schedule(() =>
|
||||
{
|
||||
// screen may be in the exiting transition phase.
|
||||
if (this.IsCurrentScreen())
|
||||
this.Push(CreateResults(imported.Result));
|
||||
}));
|
||||
ScoreProcessor.PopulateScore(score.ScoreInfo);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
float fadeOutDuration = instant ? 0 : 250;
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user