From c93ce73123c142499f554fa77b752ec668573876 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 19:58:24 +0900 Subject: [PATCH] Move catchup logic inside PlayerInstance, fixup some edge cases --- .../Spectate/MultiplayerSpectator.cs | 37 +++------ .../Spectate/MultiplayerSpectatorPlayer.cs | 2 + .../Multiplayer/Spectate/PlayerInstance.cs | 79 ++++++++++++++++++- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs index d415669cb1..19c0d4e742 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectator.cs @@ -2,16 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.Spectator; -using osu.Game.Screens.Play; using osu.Game.Screens.Spectate; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -19,7 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public class MultiplayerSpectator : SpectatorScreen { private const double min_duration_to_allow_playback = 50; - private const double max_sync_offset = 2; // Isolates beatmap/ruleset to this screen. public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -73,9 +69,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, UserIds) { Expanded = { Value = true } }, leaderboardContainer.Add); } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); updatePlayTime(); } @@ -85,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { if (gameplayStarted) { - ensurePlaying(instances.Select(i => i.Beatmap.Track.CurrentTime).Max()); + ensurePlaying(instances.Select(i => i.GetCurrentTrackTime()).Max()); return; } @@ -108,30 +104,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void ensureAllStopped() { foreach (var inst in instances) - inst.ChildrenOfType().SingleOrDefault()?.Stop(); + inst?.PauseGameplay(); } - private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(2.0); - - private void ensurePlaying(double targetTime) + private void ensurePlaying(double targetTrackTime) { foreach (var inst in instances) { + Debug.Assert(inst != null); + double lastFrameTime = inst.Score.Replay.Frames.Select(f => f.Time).Last(); - double currentTime = inst.Beatmap.Track.CurrentTime; + double currentTime = inst.GetCurrentGameplayTime(); - // If we have enough frames to play back, start playback. - if (Precision.DefinitelyBigger(lastFrameTime, currentTime, min_duration_to_allow_playback)) - { - inst.ChildrenOfType().Single().Start(); + bool canContinuePlayback = Precision.DefinitelyBigger(lastFrameTime, currentTime, min_duration_to_allow_playback); + if (!canContinuePlayback) + continue; - if (targetTime < lastFrameTime && targetTime > currentTime + max_sync_offset) - inst.Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - else - inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); - } - else - inst.Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + inst.ContinueGameplay(targetTrackTime); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs index bad093f666..1634438850 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorPlayer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + public MultiplayerSpectatorPlayer(Score score) : base(score) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 5689a3222a..acac753fe2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -15,10 +17,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class PlayerInstance : CompositeDrawable { + private const double catchup_rate = 2; + private const double max_sync_offset = catchup_rate * 2; // Double the catchup rate to prevent ringing. + public bool PlayerLoaded => stack?.CurrentScreen is Player; public User User => Score.ScoreInfo.User; - public ScoreProcessor ScoreProcessor => player?.ScoreProcessor; public WorkingBeatmap Beatmap { get; private set; } public Ruleset Ruleset { get; private set; } @@ -54,5 +58,78 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiplayerSpectatorPlayerLoader(Score, () => player = new MultiplayerSpectatorPlayer(Score))); } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + updateCatchup(); + } + + private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate); + private double targetTrackTime; + private bool isCatchingUp; + + private void updateCatchup() + { + if (player?.IsLoaded != true) + return; + + if (Score.Replay.Frames.Count == 0) + return; + + if (player.GameplayClockContainer.IsPaused.Value) + return; + + double currentTime = Beatmap.Track.CurrentTime; + bool catchupRequired = targetTrackTime > currentTime + max_sync_offset; + + // Skip catchup if nothing needs to be done. + if (catchupRequired == isCatchingUp) + return; + + if (catchupRequired) + { + Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + isCatchingUp = true; + } + else + { + Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + isCatchingUp = false; + } + } + + public double GetCurrentGameplayTime() + { + if (player?.IsLoaded != true) + return 0; + + return player.GameplayClockContainer.GameplayClock.CurrentTime; + } + + public double GetCurrentTrackTime() + { + if (player?.IsLoaded != true) + return 0; + + return Beatmap.Track.CurrentTime; + } + + public void ContinueGameplay(double targetTrackTime) + { + if (player?.IsLoaded != true) + return; + + player.GameplayClockContainer.Start(); + this.targetTrackTime = targetTrackTime; + } + + public void PauseGameplay() + { + if (player?.IsLoaded != true) + return; + + player.GameplayClockContainer.Stop(); + } } }