mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 00:23:59 +09:00
Make resyncing a bit more resilient
This commit is contained in:
@ -1,12 +1,15 @@
|
|||||||
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -153,6 +156,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
checkPausedInstant(55, false);
|
checkPausedInstant(55, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlayerStartsCatchingUpOnlyAfterExceedingMaxOffset()
|
||||||
|
{
|
||||||
|
start(new[] { 55, 56 });
|
||||||
|
loadSpectateScreen();
|
||||||
|
|
||||||
|
sendFrames(55, 1000);
|
||||||
|
sendFrames(56, 1000);
|
||||||
|
|
||||||
|
Bindable<double> slowDownAdjustment;
|
||||||
|
|
||||||
|
AddStep("slow down player 2", () =>
|
||||||
|
{
|
||||||
|
slowDownAdjustment = new Bindable<double>(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]
|
[Test]
|
||||||
public void TestPlayersCatchUpAfterFallingBehind()
|
public void TestPlayersCatchUpAfterFallingBehind()
|
||||||
{
|
{
|
||||||
@ -174,7 +198,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
checkPausedInstant(56, false);
|
checkPausedInstant(56, false);
|
||||||
|
|
||||||
// Player 2 should catch up to player 1 after unpausing.
|
// 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()
|
private void loadSpectateScreen()
|
||||||
@ -236,6 +262,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
private void checkPausedInstant(int userId, bool state) =>
|
private void checkPausedInstant(int userId, bool state) =>
|
||||||
AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().IsPaused.Value == state);
|
AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().First().IsPaused.Value == state);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns time(user1) - time(user2).
|
||||||
|
/// </summary>
|
||||||
|
private double getGameplayOffset(int user1, int user2) => getGameplayTime(user1) - getGameplayTime(user2);
|
||||||
|
|
||||||
private double getGameplayTime(int userId) => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().Single().GameplayClock.CurrentTime;
|
private double getGameplayTime(int userId) => getPlayer(userId).ChildrenOfType<GameplayClockContainer>().Single().GameplayClock.CurrentTime;
|
||||||
|
|
||||||
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single();
|
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType<Player>().Single();
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -15,8 +16,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
{
|
{
|
||||||
public class PlayerInstance : CompositeDrawable
|
public class PlayerInstance : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The rate at which a user catches up after becoming desynchronised.
|
||||||
|
/// </summary>
|
||||||
private const double catchup_rate = 2;
|
private const double catchup_rate = 2;
|
||||||
private const double max_sync_offset = 50;
|
|
||||||
|
/// <summary>
|
||||||
|
/// The offset from the expected time at which to START synchronisation.
|
||||||
|
/// </summary>
|
||||||
|
public const double MAX_OFFSET = 50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum offset from the expected time at which to STOP synchronisation.
|
||||||
|
/// </summary>
|
||||||
|
public const double SYNC_TARGET = 16;
|
||||||
|
|
||||||
public bool PlayerLoaded => stack?.CurrentScreen is Player;
|
public bool PlayerLoaded => stack?.CurrentScreen is Player;
|
||||||
|
|
||||||
@ -26,6 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
|
|
||||||
public readonly Score Score;
|
public readonly Score Score;
|
||||||
|
|
||||||
|
public bool IsCatchingUp { get; private set; }
|
||||||
|
|
||||||
private OsuScreenStack stack;
|
private OsuScreenStack stack;
|
||||||
private MultiplayerSpectatorPlayer player;
|
private MultiplayerSpectatorPlayer player;
|
||||||
|
|
||||||
@ -63,7 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
|
|
||||||
private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate);
|
private readonly BindableDouble catchupFrequencyAdjustment = new BindableDouble(catchup_rate);
|
||||||
private double targetTrackTime;
|
private double targetTrackTime;
|
||||||
private bool isCatchingUp;
|
|
||||||
|
|
||||||
private void updateCatchup()
|
private void updateCatchup()
|
||||||
{
|
{
|
||||||
@ -77,18 +91,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
double currentTime = Beatmap.Track.CurrentTime;
|
double currentTime = Beatmap.Track.CurrentTime;
|
||||||
bool catchupRequired = targetTrackTime > currentTime + max_sync_offset;
|
double timeBehind = targetTrackTime - currentTime;
|
||||||
|
|
||||||
// Skip catchup if nothing needs to be done.
|
double offsetForCatchup = IsCatchingUp ? SYNC_TARGET : MAX_OFFSET;
|
||||||
if (catchupRequired == isCatchingUp)
|
bool catchupRequired = timeBehind > offsetForCatchup;
|
||||||
|
|
||||||
|
// Skip catchup if no work needs to be done.
|
||||||
|
if (catchupRequired == IsCatchingUp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (catchupRequired)
|
if (catchupRequired)
|
||||||
|
{
|
||||||
Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment);
|
Beatmap.Track.AddAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment);
|
||||||
|
Logger.Log($"{User.Id} catchup started (behind: {timeBehind})");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment);
|
Beatmap.Track.RemoveAdjustment(AdjustableProperty.Frequency, catchupFrequencyAdjustment);
|
||||||
|
Logger.Log($"{User.Id} catchup finished (behind: {timeBehind})");
|
||||||
|
}
|
||||||
|
|
||||||
isCatchingUp = catchupRequired;
|
IsCatchingUp = catchupRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double GetCurrentGameplayTime()
|
public double GetCurrentGameplayTime()
|
||||||
|
Reference in New Issue
Block a user