From 20823abb309ac0fd908bdd03b532abfbdbd22afd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 13 Apr 2021 22:10:35 +0900 Subject: [PATCH] Make resyncing a bit more resilient --- .../TestSceneMultiplayerSpectator.cs | 33 ++++++++++++++++- .../Multiplayer/Spectate/PlayerInstance.cs | 35 +++++++++++++++---- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs index 897219dbad..1090f1d10e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectator.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . 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.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; @@ -153,6 +156,27 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(55, false); } + [Test] + public void TestPlayerStartsCatchingUpOnlyAfterExceedingMaxOffset() + { + start(new[] { 55, 56 }); + loadSpectateScreen(); + + sendFrames(55, 1000); + sendFrames(56, 1000); + + Bindable slowDownAdjustment; + + AddStep("slow down player 2", () => + { + slowDownAdjustment = new Bindable(0.99); + getInstance(56).Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, slowDownAdjustment); + }); + + AddUntilStep("exceeded min offset but not catching up", () => getGameplayOffset(55, 56) > PlayerInstance.MAX_OFFSET && !getInstance(56).IsCatchingUp); + AddUntilStep("catching up or caught up", () => getInstance(56).IsCatchingUp || Math.Abs(getGameplayOffset(55, 56)) < PlayerInstance.SYNC_TARGET * 2); + } + [Test] public void TestPlayersCatchUpAfterFallingBehind() { @@ -174,7 +198,9 @@ namespace osu.Game.Tests.Visual.Multiplayer checkPausedInstant(56, false); // Player 2 should catch up to player 1 after unpausing. - AddUntilStep("player 1 time == player 2 time", () => Precision.AlmostEquals(getGameplayTime(55), getGameplayTime(56), 16)); + AddUntilStep("player 2 not catching up", () => !getInstance(56).IsCatchingUp); + AddAssert("player 1 time == player 2 time", () => Math.Abs(getGameplayOffset(55, 56)) <= 2 * PlayerInstance.SYNC_TARGET); + AddWaitStep("wait a bit", 5); } private void loadSpectateScreen() @@ -236,6 +262,11 @@ namespace osu.Game.Tests.Visual.Multiplayer private void checkPausedInstant(int userId, bool state) => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().IsPaused.Value == state); + /// + /// Returns time(user1) - time(user2). + /// + private double getGameplayOffset(int user1, int user2) => getGameplayTime(user1) - getGameplayTime(user2); + private double getGameplayTime(int userId) => getPlayer(userId).ChildrenOfType().Single().GameplayClock.CurrentTime; private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs index 32cc3b48d6..a4a3a0c133 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerInstance.cs @@ -6,6 +6,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -15,8 +16,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class PlayerInstance : CompositeDrawable { + /// + /// The rate at which a user catches up after becoming desynchronised. + /// private const double catchup_rate = 2; - private const double max_sync_offset = 50; + + /// + /// The offset from the expected time at which to START synchronisation. + /// + public const double MAX_OFFSET = 50; + + /// + /// The maximum offset from the expected time at which to STOP synchronisation. + /// + public const double SYNC_TARGET = 16; public bool PlayerLoaded => stack?.CurrentScreen is Player; @@ -26,6 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public readonly Score Score; + public bool IsCatchingUp { get; private set; } + private OsuScreenStack stack; private MultiplayerSpectatorPlayer player; @@ -63,7 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate); private double targetTrackTime; - private bool isCatchingUp; private void updateCatchup() { @@ -77,18 +91,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate return; double currentTime = Beatmap.Track.CurrentTime; - bool catchupRequired = targetTrackTime > currentTime + max_sync_offset; + double timeBehind = targetTrackTime - currentTime; - // Skip catchup if nothing needs to be done. - if (catchupRequired == isCatchingUp) + double offsetForCatchup = IsCatchingUp ? SYNC_TARGET : MAX_OFFSET; + bool catchupRequired = timeBehind > offsetForCatchup; + + // Skip catchup if no work needs to be done. + if (catchupRequired == IsCatchingUp) return; if (catchupRequired) + { Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + Logger.Log($"{User.Id} catchup started (behind: {timeBehind})"); + } else + { Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment); + Logger.Log($"{User.Id} catchup finished (behind: {timeBehind})"); + } - isCatchingUp = catchupRequired; + IsCatchingUp = catchupRequired; } public double GetCurrentGameplayTime()