2021-04-15 15:33:59 +08:00
|
|
|
// 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 NUnit.Framework;
|
|
|
|
using osu.Framework.Bindables;
|
|
|
|
using osu.Framework.Testing;
|
|
|
|
using osu.Framework.Timing;
|
2021-04-15 18:32:55 +08:00
|
|
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync;
|
2021-04-15 15:33:59 +08:00
|
|
|
using osu.Game.Tests.Visual;
|
|
|
|
|
|
|
|
namespace osu.Game.Tests.OnlinePlay
|
|
|
|
{
|
|
|
|
[HeadlessTest]
|
2021-04-16 21:47:52 +08:00
|
|
|
public class TestCaseCatchUpSyncManager : OsuTestScene
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
|
|
|
private TestManualClock master;
|
2021-04-16 21:47:52 +08:00
|
|
|
private CatchUpSyncManager syncManager;
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
private TestSlaveClock slave1;
|
|
|
|
private TestSlaveClock slave2;
|
2021-04-15 15:33:59 +08:00
|
|
|
|
|
|
|
[SetUp]
|
|
|
|
public void Setup()
|
|
|
|
{
|
2021-04-16 21:47:52 +08:00
|
|
|
syncManager = new CatchUpSyncManager(master = new TestManualClock());
|
|
|
|
syncManager.AddSlave(slave1 = new TestSlaveClock(1));
|
|
|
|
syncManager.AddSlave(slave2 = new TestSlaveClock(2));
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-04-15 18:32:55 +08:00
|
|
|
Schedule(() => Child = syncManager);
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestMasterClockStartsWhenAllSlavesHaveFrames()
|
|
|
|
{
|
|
|
|
setWaiting(() => slave1, false);
|
|
|
|
assertMasterState(false);
|
|
|
|
assertSlaveState(() => slave1, false);
|
|
|
|
assertSlaveState(() => slave2, false);
|
|
|
|
|
|
|
|
setWaiting(() => slave2, false);
|
|
|
|
assertMasterState(true);
|
|
|
|
assertSlaveState(() => slave1, true);
|
|
|
|
assertSlaveState(() => slave2, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime()
|
|
|
|
{
|
2021-04-16 21:47:52 +08:00
|
|
|
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
|
2021-04-15 15:33:59 +08:00
|
|
|
assertMasterState(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime()
|
|
|
|
{
|
|
|
|
setWaiting(() => slave1, false);
|
2021-04-16 21:47:52 +08:00
|
|
|
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
|
2021-04-15 15:33:59 +08:00
|
|
|
assertMasterState(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestSlaveDoesNotCatchUpWhenSlightlyOutOfSync()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
setMasterTime(CatchUpSyncManager.SYNC_TARGET + 1);
|
2021-04-15 15:33:59 +08:00
|
|
|
assertCatchingUp(() => slave1, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestSlaveStartsCatchingUpWhenTooFarBehind()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
|
2021-04-15 15:33:59 +08:00
|
|
|
assertCatchingUp(() => slave1, true);
|
|
|
|
assertCatchingUp(() => slave2, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestSlaveKeepsCatchingUpWhenSlightlyOutOfSync()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
|
|
|
|
setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET + 1);
|
2021-04-15 15:33:59 +08:00
|
|
|
assertCatchingUp(() => slave1, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestSlaveStopsCatchingUpWhenInSync()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 2);
|
|
|
|
setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET);
|
2021-04-15 15:33:59 +08:00
|
|
|
assertCatchingUp(() => slave1, false);
|
|
|
|
assertCatchingUp(() => slave2, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestSlaveDoesNotStopWhenSlightlyAhead()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET);
|
2021-04-15 15:33:59 +08:00
|
|
|
assertCatchingUp(() => slave1, false);
|
|
|
|
assertSlaveState(() => slave1, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestSlaveStopsWhenTooFarAheadAndStartsWhenBackInSync()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET - 1);
|
2021-04-15 15:33:59 +08:00
|
|
|
|
|
|
|
// This is a silent catchup, where IsCatchingUp = false but IsRunning = false also.
|
|
|
|
assertCatchingUp(() => slave1, false);
|
|
|
|
assertSlaveState(() => slave1, false);
|
|
|
|
|
|
|
|
setMasterTime(1);
|
|
|
|
assertCatchingUp(() => slave1, false);
|
|
|
|
assertSlaveState(() => slave1, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
public void TestInSyncSlaveDoesNotStartIfWaitingOnFrames()
|
|
|
|
{
|
|
|
|
setAllWaiting(false);
|
|
|
|
|
|
|
|
assertSlaveState(() => slave1, true);
|
|
|
|
setWaiting(() => slave1, true);
|
|
|
|
assertSlaveState(() => slave1, false);
|
|
|
|
}
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
private void setWaiting(Func<TestSlaveClock> slave, bool waiting)
|
2021-04-15 15:33:59 +08:00
|
|
|
=> AddStep($"set slave {slave().Id} waiting = {waiting}", () => slave().WaitingOnFrames.Value = waiting);
|
|
|
|
|
|
|
|
private void setAllWaiting(bool waiting) => AddStep($"set all slaves waiting = {waiting}", () =>
|
|
|
|
{
|
|
|
|
slave1.WaitingOnFrames.Value = waiting;
|
|
|
|
slave2.WaitingOnFrames.Value = waiting;
|
|
|
|
});
|
|
|
|
|
|
|
|
private void setMasterTime(double time)
|
|
|
|
=> AddStep($"set master = {time}", () => master.Seek(time));
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// slave.Time = master.Time - offsetFromMaster
|
|
|
|
/// </summary>
|
2021-04-16 21:47:52 +08:00
|
|
|
private void setSlaveTime(Func<TestSlaveClock> slave, double offsetFromMaster)
|
2021-04-15 15:33:59 +08:00
|
|
|
=> AddStep($"set slave {slave().Id} = master - {offsetFromMaster}", () => slave().Seek(master.CurrentTime - offsetFromMaster));
|
|
|
|
|
|
|
|
private void assertMasterState(bool running)
|
|
|
|
=> AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
private void assertCatchingUp(Func<TestSlaveClock> slave, bool catchingUp) =>
|
2021-04-15 15:33:59 +08:00
|
|
|
AddAssert($"slave {slave().Id} {(catchingUp ? "is" : "is not")} catching up", () => slave().IsCatchingUp == catchingUp);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
private void assertSlaveState(Func<TestSlaveClock> slave, bool running)
|
2021-04-15 15:33:59 +08:00
|
|
|
=> AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running);
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
private class TestSlaveClock : TestManualClock, ISlaveClock
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
|
|
|
public readonly Bindable<bool> WaitingOnFrames = new Bindable<bool>(true);
|
2021-04-16 21:47:52 +08:00
|
|
|
IBindable<bool> ISlaveClock.WaitingOnFrames => WaitingOnFrames;
|
2021-04-15 15:33:59 +08:00
|
|
|
|
|
|
|
public bool IsCatchingUp { get; set; }
|
|
|
|
|
|
|
|
public readonly int Id;
|
|
|
|
|
2021-04-16 21:47:52 +08:00
|
|
|
public TestSlaveClock(int id)
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
|
|
|
Id = id;
|
|
|
|
|
|
|
|
WaitingOnFrames.BindValueChanged(waiting =>
|
|
|
|
{
|
|
|
|
if (waiting.NewValue)
|
|
|
|
Stop();
|
|
|
|
else
|
|
|
|
Start();
|
|
|
|
});
|
|
|
|
}
|
2021-04-15 18:32:55 +08:00
|
|
|
|
|
|
|
public void ProcessFrame()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
public double ElapsedFrameTime => 0;
|
|
|
|
|
|
|
|
public double FramesPerSecond => 0;
|
|
|
|
|
|
|
|
public FrameTimeInfo TimeInfo => default;
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private class TestManualClock : ManualClock, IAdjustableClock
|
|
|
|
{
|
|
|
|
public void Start() => IsRunning = true;
|
|
|
|
|
|
|
|
public void Stop() => IsRunning = false;
|
|
|
|
|
|
|
|
public bool Seek(double position)
|
|
|
|
{
|
|
|
|
CurrentTime = position;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Reset()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
public void ResetSpeedAdjustments()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|