1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 19:12:54 +08:00

Make SoloSpectator use the new SpectatorScreen class

This commit is contained in:
smoogipoo 2021-04-01 22:09:51 +09:00
parent 9e95441aa6
commit 9bc2a486e0

View File

@ -1,13 +1,9 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -24,73 +20,54 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.Spectator;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.Settings;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.Spectate;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Play
{
[Cached(typeof(IPreviewTrackOwner))]
public class SoloSpectator : OsuScreen, IPreviewTrackOwner
public class SoloSpectator : SpectatorScreen, IPreviewTrackOwner
{
[NotNull]
private readonly User targetUser;
[Resolved]
private Bindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private Bindable<RulesetInfo> ruleset { get; set; }
private Ruleset rulesetInstance;
[Resolved]
private Bindable<IReadOnlyList<Mod>> mods { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private SpectatorStreamingClient spectatorStreaming { get; set; }
[Resolved]
private BeatmapManager beatmaps { get; set; }
private PreviewTrackManager previewTrackManager { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
private Score score;
private readonly object scoreLock = new object();
private BeatmapManager beatmaps { get; set; }
private Container beatmapPanelContainer;
private SpectatorState state;
private IBindable<WeakReference<BeatmapSetInfo>> managerUpdated;
private TriangleButton watchButton;
private SettingsCheckbox automaticDownload;
private BeatmapSetInfo onlineBeatmap;
/// <summary>
/// Becomes true if a new state is waiting to be loaded (while this screen was not active).
/// The player's immediate online gameplay state.
/// This doesn't reflect the gameplay state being watched by the user if <see cref="pendingGameplayState"/> is non-null.
/// </summary>
private bool newStatePending;
private GameplayState immediateGameplayState;
/// <summary>
/// The gameplay state that is pending to be watched, upon this screen becoming current.
/// </summary>
private GameplayState pendingGameplayState;
private GetBeatmapSetRequest onlineBeatmapRequest;
public SoloSpectator([NotNull] User targetUser)
: base(targetUser.Id)
{
this.targetUser = targetUser ?? throw new ArgumentNullException(nameof(targetUser));
this.targetUser = targetUser;
}
[BackgroundDependencyLoader]
@ -173,7 +150,7 @@ namespace osu.Game.Screens.Play
Width = 250,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = attemptStart,
Action = () => attemptStart(immediateGameplayState),
Enabled = { Value = false }
}
}
@ -185,169 +162,81 @@ namespace osu.Game.Screens.Play
protected override void LoadComplete()
{
base.LoadComplete();
spectatorStreaming.OnUserBeganPlaying += userBeganPlaying;
spectatorStreaming.OnUserFinishedPlaying += userFinishedPlaying;
spectatorStreaming.OnNewFrames += userSentFrames;
spectatorStreaming.WatchUser(targetUser.Id);
managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
managerUpdated.BindValueChanged(beatmapUpdated);
automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload());
}
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> beatmap)
protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) => Schedule(() =>
{
if (beatmap.NewValue.TryGetTarget(out var beatmapSet) && beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID))
Schedule(attemptStart);
}
clearDisplay();
showBeatmapPanel(spectatorState);
});
private void userSentFrames(int userId, FrameDataBundle data)
protected override void StartGameplay(int userId, GameplayState gameplayState) => Schedule(() =>
{
// this is not scheduled as it handles propagation of frames even when in a child screen (at which point we are not alive).
// probably not the safest way to handle this.
if (userId != targetUser.Id)
return;
lock (scoreLock)
{
// this should never happen as the server sends the user's state on watching,
// but is here as a safety measure.
if (score == null)
return;
// rulesetInstance should be guaranteed to be in sync with the score via scoreLock.
Debug.Assert(rulesetInstance != null && rulesetInstance.RulesetInfo.Equals(score.ScoreInfo.Ruleset));
foreach (var frame in data.Frames)
{
IConvertibleReplayFrame convertibleFrame = rulesetInstance.CreateConvertibleReplayFrame();
convertibleFrame.FromLegacy(frame, beatmap.Value.Beatmap);
var convertedFrame = (ReplayFrame)convertibleFrame;
convertedFrame.Time = frame.Time;
score.Replay.Frames.Add(convertedFrame);
}
}
}
private void userBeganPlaying(int userId, SpectatorState state)
{
if (userId != targetUser.Id)
return;
this.state = state;
pendingGameplayState = null;
immediateGameplayState = gameplayState;
if (this.IsCurrentScreen())
Schedule(attemptStart);
attemptStart(gameplayState);
else
newStatePending = true;
}
pendingGameplayState = gameplayState;
watchButton.Enabled.Value = true;
});
protected override void EndGameplay(int userId) => Schedule(() =>
{
pendingGameplayState = null;
immediateGameplayState = null;
Schedule(clearDisplay);
watchButton.Enabled.Value = false;
});
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
if (newStatePending)
if (pendingGameplayState != null)
{
attemptStart();
newStatePending = false;
attemptStart(pendingGameplayState);
pendingGameplayState = null;
}
}
private void userFinishedPlaying(int userId, SpectatorState state)
{
if (userId != targetUser.Id)
return;
lock (scoreLock)
{
if (score != null)
{
score.Replay.HasReceivedAllFrames = true;
score = null;
}
}
Schedule(clearDisplay);
}
private void clearDisplay()
{
watchButton.Enabled.Value = false;
onlineBeatmapRequest?.Cancel();
beatmapPanelContainer.Clear();
previewTrackManager.StopAnyPlaying(this);
}
private void attemptStart()
private void attemptStart(GameplayState gameplayState)
{
clearDisplay();
showBeatmapPanel(state);
var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == state.RulesetID)?.CreateInstance();
// ruleset not available
if (resolvedRuleset == null)
if (gameplayState == null)
return;
if (state.BeatmapID == null)
return;
Beatmap.Value = gameplayState.Beatmap;
Ruleset.Value = gameplayState.Ruleset.RulesetInfo;
var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == state.BeatmapID);
if (resolvedBeatmap == null)
{
return;
}
lock (scoreLock)
{
score = new Score
{
ScoreInfo = new ScoreInfo
{
Beatmap = resolvedBeatmap,
User = targetUser,
Mods = state.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(),
Ruleset = resolvedRuleset.RulesetInfo,
},
Replay = new Replay { HasReceivedAllFrames = false },
};
ruleset.Value = resolvedRuleset.RulesetInfo;
rulesetInstance = resolvedRuleset;
beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap);
watchButton.Enabled.Value = true;
this.Push(new SpectatorPlayerLoader(score));
}
this.Push(new SpectatorPlayerLoader(gameplayState.Score));
}
private void showBeatmapPanel(SpectatorState state)
{
if (state?.BeatmapID == null)
{
onlineBeatmap = null;
return;
}
Debug.Assert(state.BeatmapID != null);
var req = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId);
req.Success += res => Schedule(() =>
onlineBeatmapRequest = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId);
onlineBeatmapRequest.Success += res => Schedule(() =>
{
if (state != this.state)
return;
onlineBeatmap = res.ToBeatmapSet(rulesets);
beatmapPanelContainer.Child = new GridBeatmapPanel(onlineBeatmap);
checkForAutomaticDownload();
});
api.Queue(req);
api.Queue(onlineBeatmapRequest);
}
private void checkForAutomaticDownload()
@ -369,21 +258,5 @@ namespace osu.Game.Screens.Play
previewTrackManager.StopAnyPlaying(this);
return base.OnExiting(next);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (spectatorStreaming != null)
{
spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying;
spectatorStreaming.OnUserFinishedPlaying -= userFinishedPlaying;
spectatorStreaming.OnNewFrames -= userSentFrames;
spectatorStreaming.StopWatchingUser(targetUser.Id);
}
managerUpdated?.UnbindAll();
}
}
}