Skip to content

Commit

Permalink
Merge pull request #19936 from peppy/catch-up-woes-2
Browse files Browse the repository at this point in the history
Move `MasterClockState` handling in to `SpectatorSyncManager`
  • Loading branch information
peppy authored Aug 24, 2022
2 parents f5a71ec + 7f92466 commit b5c244e
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 96 deletions.
6 changes: 3 additions & 3 deletions osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ public void TestInSyncPlayerClockDoesNotStartIfWaitingOnFrames()
}

private void setWaiting(Func<SpectatorPlayerClock> playerClock, bool waiting)
=> AddStep($"set player clock {clocksById[playerClock()]} waiting = {waiting}", () => playerClock().WaitingOnFrames.Value = waiting);
=> AddStep($"set player clock {clocksById[playerClock()]} waiting = {waiting}", () => playerClock().WaitingOnFrames = waiting);

private void setAllWaiting(bool waiting) => AddStep($"set all player clocks waiting = {waiting}", () =>
{
player1.WaitingOnFrames.Value = waiting;
player2.WaitingOnFrames.Value = waiting;
player1.WaitingOnFrames = waiting;
player2.WaitingOnFrames = waiting;
});

private void setMasterTime(double time)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
Expand All @@ -14,7 +13,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
/// </summary>
public class MultiSpectatorPlayer : SpectatorPlayer
{
private readonly Bindable<bool> waitingOnFrames = new Bindable<bool>(true);
private readonly SpectatorPlayerClock spectatorPlayerClock;

/// <summary>
Expand All @@ -31,8 +29,6 @@ public MultiSpectatorPlayer(Score score, SpectatorPlayerClock spectatorPlayerClo
[BackgroundDependencyLoader]
private void load()
{
spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames);

HUDOverlay.PlayerSettingsOverlay.Expire();
HUDOverlay.HoldToQuit.Expire();
}
Expand All @@ -53,7 +49,7 @@ protected override void UpdateAfterChildren()
base.UpdateAfterChildren();

// This is required because the frame stable clock is set to WaitingOnFrames = false for one frame.
waitingOnFrames.Value = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || Score.Replay.Frames.Count == 0;
spectatorPlayerClock.WaitingOnFrames = DrawableRuleset.FrameStableClock.WaitingOnFrames.Value || Score.Replay.Frames.Count == 0;
}

protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
Expand Down Expand Up @@ -52,7 +49,6 @@ public class MultiSpectatorScreen : SpectatorScreen
private PlayerGrid grid = null!;
private MultiSpectatorLeaderboard leaderboard = null!;
private PlayerArea? currentAudioSource;
private bool canStartMasterClock;

private readonly Room room;
private readonly MultiplayerRoomUser[] users;
Expand All @@ -77,50 +73,54 @@ private void load()
FillFlowContainer leaderboardFlow;
Container scoreDisplayContainer;

masterClockContainer = CreateMasterGameplayClockContainer(Beatmap.Value);

InternalChildren = new[]
InternalChildren = new Drawable[]
{
(Drawable)(syncManager = new SpectatorSyncManager(masterClockContainer)),
masterClockContainer.WithChild(new GridContainer
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0)
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
Child = new GridContainer
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
scoreDisplayContainer = new Container
new Drawable[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
scoreDisplayContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
},
},
new Drawable[]
{
new GridContainer
new Drawable[]
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
new GridContainer
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
leaderboardFlow = new FillFlowContainer
new Drawable[]
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5)
},
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
leaderboardFlow = new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(5)
},
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
}
}
}
}
}
}
})
},
syncManager = new SpectatorSyncManager(masterClockContainer)
{
ReadyToStart = performInitialSeek,
}
};

for (int i = 0; i < Users.Count; i++)
Expand Down Expand Up @@ -157,9 +157,6 @@ protected override void LoadComplete()
base.LoadComplete();

masterClockContainer.Reset();

syncManager.ReadyToStart += onReadyToStart;
syncManager.MasterState.BindValueChanged(onMasterStateChanged, true);
}

protected override void Update()
Expand All @@ -178,9 +175,9 @@ protected override void Update()
}

private bool isCandidateAudioSource(SpectatorPlayerClock? clock)
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value;
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames;

private void onReadyToStart()
private void performInitialSeek()
{
// Seek the master clock to the gameplay time.
// This is chosen as the first available frame in the players' replays, which matches the seek by each individual SpectatorPlayer.
Expand All @@ -192,27 +189,6 @@ private void onReadyToStart()

masterClockContainer.StartTime = startTime;
masterClockContainer.Reset(true);

// Although the clock has been started, this flag is set to allow for later synchronisation state changes to also be able to start it.
canStartMasterClock = true;
}

private void onMasterStateChanged(ValueChangedEvent<MasterClockState> state)
{
Logger.Log($"{nameof(MultiSpectatorScreen)}'s master clock become {state.NewValue}");

switch (state.NewValue)
{
case MasterClockState.Synchronised:
if (canStartMasterClock)
masterClockContainer.Start();

break;

case MasterClockState.TooFarAhead:
masterClockContainer.Stop();
break;
}
}

protected override void OnNewPlayingUserState(int userId, SpectatorState spectatorState)
Expand Down Expand Up @@ -254,7 +230,5 @@ public override bool OnBackButton()

return base.OnBackButton();
}

protected virtual MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) => new MasterGameplayClockContainer(beatmap, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Screens.Play;

Expand All @@ -16,7 +15,7 @@ public class SpectatorPlayerClock : IFrameBasedClock, IAdjustableClock
/// <summary>
/// The catch up rate.
/// </summary>
public const double CATCHUP_RATE = 2;
private const double catchup_rate = 2;

private readonly GameplayClockContainer masterClock;

Expand All @@ -25,7 +24,7 @@ public class SpectatorPlayerClock : IFrameBasedClock, IAdjustableClock
/// <summary>
/// Whether this clock is waiting on frames to continue playback.
/// </summary>
public Bindable<bool> WaitingOnFrames { get; } = new Bindable<bool>(true);
public bool WaitingOnFrames { get; set; } = true;

/// <summary>
/// Whether this clock is behind the master clock and running at a higher rate to catch up to it.
Expand Down Expand Up @@ -68,23 +67,14 @@ public void ResetSpeedAdjustments()
{
}

public double Rate => IsCatchingUp ? CATCHUP_RATE : 1;

double IAdjustableClock.Rate
public double Rate
{
get => Rate;
set => throw new NotSupportedException();
get => IsCatchingUp ? catchup_rate : 1;
set => throw new NotImplementedException();
}

double IClock.Rate => Rate;

public void ProcessFrame()
{
ElapsedFrameTime = 0;
FramesPerSecond = 0;

masterClock.ProcessFrame();

if (IsRunning)
{
double elapsedSource = masterClock.ElapsedFrameTime;
Expand All @@ -94,6 +84,11 @@ public void ProcessFrame()
ElapsedFrameTime = elapsed;
FramesPerSecond = masterClock.FramesPerSecond;
}
else
{
ElapsedFrameTime = 0;
FramesPerSecond = 0;
}
}

public double ElapsedFrameTime { get; private set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Screens.Play;

namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
Expand Down Expand Up @@ -33,12 +33,7 @@ public class SpectatorSyncManager : Component
/// <summary>
/// An event which is invoked when gameplay is ready to start.
/// </summary>
public event Action? ReadyToStart;

/// <summary>
/// The catch-up state of the master clock.
/// </summary>
public IBindable<MasterClockState> MasterState => masterState;
public Action? ReadyToStart;

public double CurrentMasterTime => masterClock.CurrentTime;

Expand All @@ -52,9 +47,10 @@ public class SpectatorSyncManager : Component
/// </summary>
private readonly List<SpectatorPlayerClock> playerClocks = new List<SpectatorPlayerClock>();

private readonly Bindable<MasterClockState> masterState = new Bindable<MasterClockState>();
private MasterClockState masterState = MasterClockState.Synchronised;

private bool hasStarted;

private double? firstStartAttemptTime;

public SpectatorSyncManager(GameplayClockContainer master)
Expand Down Expand Up @@ -111,7 +107,7 @@ private bool attemptStart()
if (playerClocks.Count == 0)
return false;

int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value);
int readyCount = playerClocks.Count(s => !s.WaitingOnFrames);

if (readyCount == playerClocks.Count)
return performStart();
Expand Down Expand Up @@ -158,7 +154,7 @@ private void updatePlayerCatchup()
}

// Make sure the player clock is running if it can.
clock.IsRunning = !clock.WaitingOnFrames.Value;
clock.IsRunning = !clock.WaitingOnFrames;

if (clock.IsCatchingUp)
{
Expand All @@ -180,8 +176,26 @@ private void updatePlayerCatchup()
/// </summary>
private void updateMasterState()
{
bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp);
masterState.Value = anyInSync ? MasterClockState.Synchronised : MasterClockState.TooFarAhead;
MasterClockState newState = playerClocks.Any(s => !s.IsCatchingUp) ? MasterClockState.Synchronised : MasterClockState.TooFarAhead;

if (masterState == newState)
return;

masterState = newState;
Logger.Log($"{nameof(SpectatorSyncManager)}'s master clock become {masterState}");

switch (masterState)
{
case MasterClockState.Synchronised:
if (hasStarted)
masterClock.Start();

break;

case MasterClockState.TooFarAhead:
masterClock.Stop();
break;
}
}
}
}

0 comments on commit b5c244e

Please sign in to comment.