1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Change terminology from "slave" to "player clock"

This commit is contained in:
smoogipoo 2021-04-26 17:19:44 +09:00
parent 737a15c2d4
commit 5b4cb71cc7
8 changed files with 118 additions and 118 deletions

View File

@ -17,31 +17,31 @@ namespace osu.Game.Tests.OnlinePlay
private TestManualClock master;
private CatchUpSyncManager syncManager;
private TestSlaveClock slave1;
private TestSlaveClock slave2;
private TestSpectatorPlayerClock player1;
private TestSpectatorPlayerClock player2;
[SetUp]
public void Setup()
{
syncManager = new CatchUpSyncManager(master = new TestManualClock());
syncManager.AddSlave(slave1 = new TestSlaveClock(1));
syncManager.AddSlave(slave2 = new TestSlaveClock(2));
syncManager.AddPlayerClock(player1 = new TestSpectatorPlayerClock(1));
syncManager.AddPlayerClock(player2 = new TestSpectatorPlayerClock(2));
Schedule(() => Child = syncManager);
}
[Test]
public void TestMasterClockStartsWhenAllSlavesHaveFrames()
public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames()
{
setWaiting(() => slave1, false);
setWaiting(() => player1, false);
assertMasterState(false);
assertSlaveState(() => slave1, false);
assertSlaveState(() => slave2, false);
assertPlayerClockState(() => player1, false);
assertPlayerClockState(() => player2, false);
setWaiting(() => slave2, false);
setWaiting(() => player2, false);
assertMasterState(true);
assertSlaveState(() => slave1, true);
assertSlaveState(() => slave2, true);
assertPlayerClockState(() => player1, true);
assertPlayerClockState(() => player2, true);
}
[Test]
@ -54,115 +54,115 @@ namespace osu.Game.Tests.OnlinePlay
[Test]
public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime()
{
setWaiting(() => slave1, false);
setWaiting(() => player1, false);
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
assertMasterState(true);
}
[Test]
public void TestSlaveDoesNotCatchUpWhenSlightlyOutOfSync()
public void TestPlayerClockDoesNotCatchUpWhenSlightlyOutOfSync()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.SYNC_TARGET + 1);
assertCatchingUp(() => slave1, false);
assertCatchingUp(() => player1, false);
}
[Test]
public void TestSlaveStartsCatchingUpWhenTooFarBehind()
public void TestPlayerClockStartsCatchingUpWhenTooFarBehind()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
assertCatchingUp(() => slave1, true);
assertCatchingUp(() => slave2, true);
assertCatchingUp(() => player1, true);
assertCatchingUp(() => player2, true);
}
[Test]
public void TestSlaveKeepsCatchingUpWhenSlightlyOutOfSync()
public void TestPlayerClockKeepsCatchingUpWhenSlightlyOutOfSync()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 1);
setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET + 1);
assertCatchingUp(() => slave1, true);
setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET + 1);
assertCatchingUp(() => player1, true);
}
[Test]
public void TestSlaveStopsCatchingUpWhenInSync()
public void TestPlayerClockStopsCatchingUpWhenInSync()
{
setAllWaiting(false);
setMasterTime(CatchUpSyncManager.MAX_SYNC_OFFSET + 2);
setSlaveTime(() => slave1, CatchUpSyncManager.SYNC_TARGET);
assertCatchingUp(() => slave1, false);
assertCatchingUp(() => slave2, true);
setPlayerClockTime(() => player1, CatchUpSyncManager.SYNC_TARGET);
assertCatchingUp(() => player1, false);
assertCatchingUp(() => player2, true);
}
[Test]
public void TestSlaveDoesNotStopWhenSlightlyAhead()
public void TestPlayerClockDoesNotStopWhenSlightlyAhead()
{
setAllWaiting(false);
setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET);
assertCatchingUp(() => slave1, false);
assertSlaveState(() => slave1, true);
setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET);
assertCatchingUp(() => player1, false);
assertPlayerClockState(() => player1, true);
}
[Test]
public void TestSlaveStopsWhenTooFarAheadAndStartsWhenBackInSync()
public void TestPlayerClockStopsWhenTooFarAheadAndStartsWhenBackInSync()
{
setAllWaiting(false);
setSlaveTime(() => slave1, -CatchUpSyncManager.SYNC_TARGET - 1);
setPlayerClockTime(() => player1, -CatchUpSyncManager.SYNC_TARGET - 1);
// This is a silent catchup, where IsCatchingUp = false but IsRunning = false also.
assertCatchingUp(() => slave1, false);
assertSlaveState(() => slave1, false);
assertCatchingUp(() => player1, false);
assertPlayerClockState(() => player1, false);
setMasterTime(1);
assertCatchingUp(() => slave1, false);
assertSlaveState(() => slave1, true);
assertCatchingUp(() => player1, false);
assertPlayerClockState(() => player1, true);
}
[Test]
public void TestInSyncSlaveDoesNotStartIfWaitingOnFrames()
public void TestInSyncPlayerClockDoesNotStartIfWaitingOnFrames()
{
setAllWaiting(false);
assertSlaveState(() => slave1, true);
setWaiting(() => slave1, true);
assertSlaveState(() => slave1, false);
assertPlayerClockState(() => player1, true);
setWaiting(() => player1, true);
assertPlayerClockState(() => player1, false);
}
private void setWaiting(Func<TestSlaveClock> slave, bool waiting)
=> AddStep($"set slave {slave().Id} waiting = {waiting}", () => slave().WaitingOnFrames.Value = waiting);
private void setWaiting(Func<TestSpectatorPlayerClock> playerClock, bool waiting)
=> AddStep($"set player clock {playerClock().Id} waiting = {waiting}", () => playerClock().WaitingOnFrames.Value = waiting);
private void setAllWaiting(bool waiting) => AddStep($"set all slaves waiting = {waiting}", () =>
private void setAllWaiting(bool waiting) => AddStep($"set all player clocks waiting = {waiting}", () =>
{
slave1.WaitingOnFrames.Value = waiting;
slave2.WaitingOnFrames.Value = waiting;
player1.WaitingOnFrames.Value = waiting;
player2.WaitingOnFrames.Value = waiting;
});
private void setMasterTime(double time)
=> AddStep($"set master = {time}", () => master.Seek(time));
/// <summary>
/// slave.Time = master.Time - offsetFromMaster
/// clock.Time = master.Time - offsetFromMaster
/// </summary>
private void setSlaveTime(Func<TestSlaveClock> slave, double offsetFromMaster)
=> AddStep($"set slave {slave().Id} = master - {offsetFromMaster}", () => slave().Seek(master.CurrentTime - offsetFromMaster));
private void setPlayerClockTime(Func<TestSpectatorPlayerClock> playerClock, double offsetFromMaster)
=> AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster));
private void assertMasterState(bool running)
=> AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running);
private void assertCatchingUp(Func<TestSlaveClock> slave, bool catchingUp) =>
AddAssert($"slave {slave().Id} {(catchingUp ? "is" : "is not")} catching up", () => slave().IsCatchingUp == catchingUp);
private void assertCatchingUp(Func<TestSpectatorPlayerClock> playerClock, bool catchingUp) =>
AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp);
private void assertSlaveState(Func<TestSlaveClock> slave, bool running)
=> AddAssert($"slave {slave().Id} {(running ? "is" : "is not")} running", () => slave().IsRunning == running);
private void assertPlayerClockState(Func<TestSpectatorPlayerClock> playerClock, bool running)
=> AddAssert($"player clock {playerClock().Id} {(running ? "is" : "is not")} running", () => playerClock().IsRunning == running);
private class TestSlaveClock : TestManualClock, ISlaveClock
private class TestSpectatorPlayerClock : TestManualClock, ISpectatorPlayerClock
{
public Bindable<bool> WaitingOnFrames { get; } = new Bindable<bool>(true);
@ -170,7 +170,7 @@ namespace osu.Game.Tests.OnlinePlay
public readonly int Id;
public TestSlaveClock(int id)
public TestSpectatorPlayerClock(int id)
{
Id = id;

View File

@ -19,24 +19,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
private readonly Bindable<bool> waitingOnFrames = new Bindable<bool>(true);
private readonly Score score;
private readonly ISlaveClock slaveClock;
private readonly ISpectatorPlayerClock spectatorPlayerClock;
/// <summary>
/// Creates a new <see cref="MultiSpectatorPlayer"/>.
/// </summary>
/// <param name="score">The score containing the player's replay.</param>
/// <param name="slaveClock">The clock controlling the gameplay running state.</param>
public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISlaveClock slaveClock)
/// <param name="spectatorPlayerClock">The clock controlling the gameplay running state.</param>
public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock)
: base(score)
{
this.score = score;
this.slaveClock = slaveClock;
this.spectatorPlayerClock = spectatorPlayerClock;
}
[BackgroundDependencyLoader]
private void load()
{
slaveClock.WaitingOnFrames.BindTo(waitingOnFrames);
spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames);
}
protected override void UpdateAfterChildren()
@ -48,18 +48,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
=> new SlaveGameplayClockContainer(slaveClock);
=> new SpectatorGameplayClockContainer(spectatorPlayerClock);
private class SlaveGameplayClockContainer : GameplayClockContainer
private class SpectatorGameplayClockContainer : GameplayClockContainer
{
public SlaveGameplayClockContainer([NotNull] IClock sourceClock)
public SpectatorGameplayClockContainer([NotNull] IClock sourceClock)
: base(sourceClock)
{
}
protected override void Update()
{
// The slave clock's running state is controlled externally, but the local pausing state needs to be updated to stop gameplay.
// The player clock's running state is controlled externally, but the local pausing state needs to be updated to stop gameplay.
if (SourceClock.IsRunning)
Start();
else

View File

@ -79,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
};
for (int i = 0; i < UserIds.Length; i++)
grid.Add(instances[i] = new PlayerArea(UserIds[i], new CatchUpSlaveClock(masterClockContainer.GameplayClock)));
grid.Add(instances[i] = new PlayerArea(UserIds[i], new CatchUpSpectatorPlayerClock(masterClockContainer.GameplayClock)));
// Todo: This is not quite correct - it should be per-user to adjust for other mod combinations.
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
@ -104,7 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
if (!isCandidateAudioSource(currentAudioSource?.GameplayClock))
{
currentAudioSource = instances.Where(i => isCandidateAudioSource(i.GameplayClock))
.OrderBy(i => Math.Abs(i.GameplayClock.CurrentTime - syncManager.Master.CurrentTime))
.OrderBy(i => Math.Abs(i.GameplayClock.CurrentTime - syncManager.MasterClock.CurrentTime))
.FirstOrDefault();
foreach (var instance in instances)
@ -112,7 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
}
}
private bool isCandidateAudioSource([CanBeNull] ISlaveClock clock)
private bool isCandidateAudioSource([CanBeNull] ISpectatorPlayerClock clock)
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value;
protected override void OnUserStateChanged(int userId, SpectatorState spectatorState)
@ -124,7 +124,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
var instance = instances[getIndexForUser(userId)];
instance.LoadScore(gameplayState.Score);
syncManager.AddSlave(instance.GameplayClock);
syncManager.AddPlayerClock(instance.GameplayClock);
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
}

View File

@ -35,10 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public readonly int UserId;
/// <summary>
/// The <see cref="ISlaveClock"/> used to control the gameplay running state of a loaded <see cref="Player"/>.
/// The <see cref="ISpectatorPlayerClock"/> used to control the gameplay running state of a loaded <see cref="Player"/>.
/// </summary>
[NotNull]
public readonly ISlaveClock GameplayClock;
public readonly ISpectatorPlayerClock GameplayClock;
/// <summary>
/// The currently-loaded score.
@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private readonly AudioContainer audioContainer;
private OsuScreenStack stack;
public PlayerArea(int userId, [NotNull] ISlaveClock gameplayClock)
public PlayerArea(int userId, [NotNull] ISpectatorPlayerClock gameplayClock)
{
UserId = userId;
GameplayClock = gameplayClock;

View File

@ -8,9 +8,9 @@ using osu.Framework.Timing;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
{
/// <summary>
/// A <see cref="ISlaveClock"/> which catches up using rate adjustment.
/// A <see cref="ISpectatorPlayerClock"/> which catches up using rate adjustment.
/// </summary>
public class CatchUpSlaveClock : ISlaveClock
public class CatchUpSpectatorPlayerClock : ISpectatorPlayerClock
{
/// <summary>
/// The catch up rate.
@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
private readonly IFrameBasedClock masterClock;
public CatchUpSlaveClock(IFrameBasedClock masterClock)
public CatchUpSpectatorPlayerClock(IFrameBasedClock masterClock)
{
this.masterClock = masterClock;
}

View File

@ -9,46 +9,46 @@ using osu.Framework.Timing;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
{
/// <summary>
/// A <see cref="ISyncManager"/> which synchronises de-synced slave clocks through catchup.
/// A <see cref="ISyncManager"/> which synchronises de-synced player clocks through catchup.
/// </summary>
public class CatchUpSyncManager : Component, ISyncManager
{
/// <summary>
/// The offset from the master clock to which slaves should be synchronised to.
/// The offset from the master clock to which player clocks should be synchronised to.
/// </summary>
public const double SYNC_TARGET = 16;
/// <summary>
/// The offset from the master clock at which slaves begin resynchronising.
/// The offset from the master clock at which player clocks begin resynchronising.
/// </summary>
public const double MAX_SYNC_OFFSET = 50;
/// <summary>
/// The maximum delay to start gameplay, if any (but not all) slaves are ready.
/// The maximum delay to start gameplay, if any (but not all) player clocks are ready.
/// </summary>
public const double MAXIMUM_START_DELAY = 15000;
/// <summary>
/// The master clock which is used to control the timing of all slave clocks.
/// The master clock which is used to control the timing of all player clocks clocks.
/// </summary>
public IAdjustableClock Master { get; }
public IAdjustableClock MasterClock { get; }
/// <summary>
/// The slave clocks.
/// The player clocks.
/// </summary>
private readonly List<ISlaveClock> slaves = new List<ISlaveClock>();
private readonly List<ISpectatorPlayerClock> playerClocks = new List<ISpectatorPlayerClock>();
private bool hasStarted;
private double? firstStartAttemptTime;
public CatchUpSyncManager(IAdjustableClock master)
{
Master = master;
MasterClock = master;
}
public void AddSlave(ISlaveClock clock) => slaves.Add(clock);
public void AddPlayerClock(ISpectatorPlayerClock clock) => playerClocks.Add(clock);
public void RemoveSlave(ISlaveClock clock) => slaves.Remove(clock);
public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock);
protected override void Update()
{
@ -56,9 +56,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
if (!attemptStart())
{
// Ensure all slaves are stopped until the start succeeds.
foreach (var slave in slaves)
slave.Stop();
// Ensure all player clocks are stopped until the start succeeds.
foreach (var clock in playerClocks)
clock.Stop();
return;
}
@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
}
/// <summary>
/// Attempts to start playback. Awaits for all slaves to have available frames for up to <see cref="MAXIMUM_START_DELAY"/> milliseconds.
/// Attempts to start playback. Waits for all player clocks to have available frames for up to <see cref="MAXIMUM_START_DELAY"/> milliseconds.
/// </summary>
/// <returns>Whether playback was started and syncing should occur.</returns>
private bool attemptStart()
@ -75,14 +75,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
if (hasStarted)
return true;
if (slaves.Count == 0)
if (playerClocks.Count == 0)
return false;
firstStartAttemptTime ??= Time.Current;
int readyCount = slaves.Count(s => !s.WaitingOnFrames.Value);
int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value);
if (readyCount == slaves.Count)
if (readyCount == playerClocks.Count)
return hasStarted = true;
if (readyCount > 0 && (Time.Current - firstStartAttemptTime) > MAXIMUM_START_DELAY)
@ -92,38 +92,38 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
}
/// <summary>
/// Updates the catchup states of all slave clocks.
/// Updates the catchup states of all player clocks clocks.
/// </summary>
private void updateCatchup()
{
for (int i = 0; i < slaves.Count; i++)
for (int i = 0; i < playerClocks.Count; i++)
{
var slave = slaves[i];
double timeDelta = Master.CurrentTime - slave.CurrentTime;
var clock = playerClocks[i];
double timeDelta = MasterClock.CurrentTime - clock.CurrentTime;
// Check that the slave isn't too far ahead.
// This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the slave.
// Check that the player clock isn't too far ahead.
// This is a quiet case in which the catchup is done by the master clock, so IsCatchingUp is not set on the player clock.
if (timeDelta < -SYNC_TARGET)
{
slave.Stop();
clock.Stop();
continue;
}
// Make sure the slave is running if it can.
if (!slave.WaitingOnFrames.Value)
slave.Start();
// Make sure the player clock is running if it can.
if (!clock.WaitingOnFrames.Value)
clock.Start();
if (slave.IsCatchingUp)
if (clock.IsCatchingUp)
{
// Stop the slave from catching up if it's within the sync target.
// Stop the player clock from catching up if it's within the sync target.
if (timeDelta <= SYNC_TARGET)
slave.IsCatchingUp = false;
clock.IsCatchingUp = false;
}
else
{
// Make the slave start catching up if it's exceeded the maximum allowable sync offset.
// Make the player clock start catching up if it's exceeded the maximum allowable sync offset.
if (timeDelta > MAX_SYNC_OFFSET)
slave.IsCatchingUp = true;
clock.IsCatchingUp = true;
}
}
}
@ -133,14 +133,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
/// </summary>
private void updateMasterClock()
{
bool anyInSync = slaves.Any(s => !s.IsCatchingUp);
bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp);
if (Master.IsRunning != anyInSync)
if (MasterClock.IsRunning != anyInSync)
{
if (anyInSync)
Master.Start();
MasterClock.Start();
else
Master.Stop();
MasterClock.Stop();
}
}
}

View File

@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
/// <summary>
/// A clock which is used by <see cref="MultiSpectatorPlayer"/>s and managed by an <see cref="ISyncManager"/>.
/// </summary>
public interface ISlaveClock : IFrameBasedClock, IAdjustableClock
public interface ISpectatorPlayerClock : IFrameBasedClock, IAdjustableClock
{
/// <summary>
/// Whether this clock is waiting on frames to continue playback.

View File

@ -6,25 +6,25 @@ using osu.Framework.Timing;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync
{
/// <summary>
/// Manages the synchronisation between one or more <see cref="ISlaveClock"/>s in relation to a master clock.
/// Manages the synchronisation between one or more <see cref="ISpectatorPlayerClock"/>s in relation to a master clock.
/// </summary>
public interface ISyncManager
{
/// <summary>
/// The master clock which slaves should synchronise to.
/// The master clock which player clocks should synchronise to.
/// </summary>
IAdjustableClock Master { get; }
IAdjustableClock MasterClock { get; }
/// <summary>
/// Adds an <see cref="ISlaveClock"/> to manage.
/// Adds an <see cref="ISpectatorPlayerClock"/> to manage.
/// </summary>
/// <param name="clock">The <see cref="ISlaveClock"/> to add.</param>
void AddSlave(ISlaveClock clock);
/// <param name="clock">The <see cref="ISpectatorPlayerClock"/> to add.</param>
void AddPlayerClock(ISpectatorPlayerClock clock);
/// <summary>
/// Removes an <see cref="ISlaveClock"/>, stopping it from being managed by this <see cref="ISyncManager"/>.
/// Removes an <see cref="ISpectatorPlayerClock"/>, stopping it from being managed by this <see cref="ISyncManager"/>.
/// </summary>
/// <param name="clock">The <see cref="ISlaveClock"/> to remove.</param>
void RemoveSlave(ISlaveClock clock);
/// <param name="clock">The <see cref="ISpectatorPlayerClock"/> to remove.</param>
void RemovePlayerClock(ISpectatorPlayerClock clock);
}
}