mirror of
https://github.com/ppy/osu.git
synced 2025-02-21 03:02:54 +08: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.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
cancel();
|
cancel();
|
||||||
complete();
|
complete();
|
||||||
|
|
||||||
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).ResultsCreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -84,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
// wait to ensure there was no attempt of pushing the results screen.
|
// wait to ensure there was no attempt of pushing the results screen.
|
||||||
AddWaitStep("wait", resultsDisplayWaitCount);
|
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)
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
@ -110,16 +112,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public class FakeRankingPushPlayer : TestPlayer
|
public class FakeRankingPushPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
public bool GotoRankingInvoked;
|
public bool ResultsCreated { get; private set; }
|
||||||
|
|
||||||
public FakeRankingPushPlayer()
|
public FakeRankingPushPlayer()
|
||||||
: base(true, true)
|
: 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.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -95,19 +96,36 @@ namespace osu.Game.Screens.Multi.Play
|
|||||||
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true);
|
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScoreInfo CreateScore()
|
protected override Score CreateScore()
|
||||||
{
|
{
|
||||||
var score = base.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);
|
Debug.Assert(token != null);
|
||||||
|
|
||||||
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score);
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
request.Success += s => score.OnlineScoreID = s.ID;
|
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score.ScoreInfo);
|
||||||
request.Failure += e => Logger.Error(e, "Failed to submit score");
|
|
||||||
api.Queue(request);
|
|
||||||
|
|
||||||
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)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -22,8 +23,10 @@ using osu.Game.Graphics.Containers;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -501,6 +504,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate completionProgressDelegate;
|
private ScheduledDelegate completionProgressDelegate;
|
||||||
|
private Task<ScoreInfo> scoreSubmissionTask;
|
||||||
|
|
||||||
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
||||||
{
|
{
|
||||||
@ -527,33 +531,50 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
if (!showResults) return;
|
if (!showResults) return;
|
||||||
|
|
||||||
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
scoreSubmissionTask ??= Task.Run(async () =>
|
||||||
completionProgressDelegate = Schedule(GotoRanking);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual ScoreInfo CreateScore()
|
|
||||||
{
|
|
||||||
var score = new ScoreInfo
|
|
||||||
{
|
{
|
||||||
Beatmap = Beatmap.Value.BeatmapInfo,
|
var score = CreateScore();
|
||||||
Ruleset = rulesetInfo,
|
|
||||||
Mods = Mods.Value.ToArray(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (DrawableRuleset.ReplayScore != null)
|
try
|
||||||
score.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
|
{
|
||||||
else
|
await SubmitScore(score);
|
||||||
score.User = api.LocalUser.Value;
|
}
|
||||||
|
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 override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
|
||||||
|
|
||||||
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true);
|
|
||||||
|
|
||||||
#region Fail Logic
|
#region Fail Logic
|
||||||
|
|
||||||
protected FailOverlay FailOverlay { get; private set; }
|
protected FailOverlay FailOverlay { get; private set; }
|
||||||
@ -748,39 +769,74 @@ namespace osu.Game.Screens.Play
|
|||||||
return base.OnExiting(next);
|
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 (DrawableRuleset.ReplayScore != null)
|
||||||
{
|
{
|
||||||
// if a replay is present, we likely don't want to import into the local database.
|
score.ScoreInfo.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
|
||||||
this.Push(CreateResults(CreateScore()));
|
score.Replay = DrawableRuleset.ReplayScore.Replay;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
LegacyByteArrayReader replayReader = null;
|
|
||||||
|
|
||||||
var score = new Score { ScoreInfo = CreateScore() };
|
|
||||||
|
|
||||||
if (recordingScore?.Replay.Frames.Count > 0)
|
|
||||||
{
|
{
|
||||||
score.Replay = recordingScore.Replay;
|
score.ScoreInfo.User = api.LocalUser.Value;
|
||||||
|
score.Replay = new Replay { Frames = recordingScore?.Replay.Frames.ToList() ?? new List<ReplayFrame>() };
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
|
|
||||||
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scoreManager.Import(score.ScoreInfo, replayReader)
|
ScoreProcessor.PopulateScore(score.ScoreInfo);
|
||||||
.ContinueWith(imported => Schedule(() =>
|
|
||||||
{
|
return score;
|
||||||
// screen may be in the exiting transition phase.
|
|
||||||
if (this.IsCurrentScreen())
|
|
||||||
this.Push(CreateResults(imported.Result));
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
private void fadeOut(bool instant = false)
|
||||||
{
|
{
|
||||||
float fadeOutDuration = instant ? 0 : 250;
|
float fadeOutDuration = instant ? 0 : 250;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -26,18 +27,21 @@ namespace osu.Game.Screens.Play
|
|||||||
DrawableRuleset?.SetReplayScore(Score);
|
DrawableRuleset?.SetReplayScore(Score);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
|
protected override Score CreateScore()
|
||||||
|
|
||||||
protected override ScoreInfo CreateScore()
|
|
||||||
{
|
{
|
||||||
var baseScore = base.CreateScore();
|
var baseScore = base.CreateScore();
|
||||||
|
|
||||||
// Since the replay score doesn't contain statistics, we'll pass them through here.
|
// 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)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
|
Loading…
Reference in New Issue
Block a user