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.
|
|
|
|
|
2021-06-11 15:25:45 +08:00
|
|
|
using System;
|
2021-04-15 15:33:59 +08:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
2021-06-11 18:15:53 +08:00
|
|
|
using osu.Framework.Bindables;
|
2021-04-15 15:33:59 +08:00
|
|
|
using osu.Framework.Graphics;
|
2022-08-22 18:14:06 +08:00
|
|
|
using osu.Game.Screens.Play;
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-05-03 12:38:53 +08:00
|
|
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
2021-04-15 18:32:55 +08:00
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// A <see cref="ISyncManager"/> which synchronises de-synced player clocks through catchup.
|
2021-04-15 18:32:55 +08:00
|
|
|
/// </summary>
|
2021-04-16 21:47:52 +08:00
|
|
|
public class CatchUpSyncManager : Component, ISyncManager
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
|
|
|
/// <summary>
|
2021-05-03 13:37:11 +08:00
|
|
|
/// The offset from the master clock to which player clocks should remain within to be considered in-sync.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
|
|
|
public const double SYNC_TARGET = 16;
|
|
|
|
|
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// The offset from the master clock at which player clocks begin resynchronising.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
|
|
|
public const double MAX_SYNC_OFFSET = 50;
|
|
|
|
|
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// The maximum delay to start gameplay, if any (but not all) player clocks are ready.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
|
|
|
public const double MAXIMUM_START_DELAY = 15000;
|
|
|
|
|
2022-08-22 20:52:43 +08:00
|
|
|
public event Action? ReadyToStart;
|
2021-06-11 18:15:53 +08:00
|
|
|
|
2021-04-15 15:33:59 +08:00
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// The master clock which is used to control the timing of all player clocks clocks.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
2022-08-22 18:14:06 +08:00
|
|
|
public GameplayClockContainer MasterClock { get; }
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-06-11 18:15:53 +08:00
|
|
|
public IBindable<MasterClockState> MasterState => masterState;
|
2021-06-11 15:25:45 +08:00
|
|
|
|
2021-04-15 15:33:59 +08:00
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// The player clocks.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
private readonly List<ISpectatorPlayerClock> playerClocks = new List<ISpectatorPlayerClock>();
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-06-11 18:15:53 +08:00
|
|
|
private readonly Bindable<MasterClockState> masterState = new Bindable<MasterClockState>();
|
|
|
|
|
2021-04-15 15:33:59 +08:00
|
|
|
private bool hasStarted;
|
|
|
|
private double? firstStartAttemptTime;
|
|
|
|
|
2022-08-22 18:14:06 +08:00
|
|
|
public CatchUpSyncManager(GameplayClockContainer master)
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
2021-04-26 16:19:44 +08:00
|
|
|
MasterClock = master;
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
|
2022-08-23 12:52:43 +08:00
|
|
|
public ISpectatorPlayerClock CreateManagedClock()
|
2021-06-29 16:29:25 +08:00
|
|
|
{
|
2022-08-22 20:52:43 +08:00
|
|
|
var clock = new CatchUpSpectatorPlayerClock(MasterClock);
|
2021-06-29 16:29:25 +08:00
|
|
|
playerClocks.Add(clock);
|
2022-08-22 18:14:06 +08:00
|
|
|
return clock;
|
2021-06-29 16:29:25 +08:00
|
|
|
}
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2022-08-23 12:52:43 +08:00
|
|
|
public void RemoveManagedClock(ISpectatorPlayerClock clock)
|
2021-08-25 16:29:48 +08:00
|
|
|
{
|
|
|
|
playerClocks.Remove(clock);
|
|
|
|
clock.Stop();
|
|
|
|
}
|
2021-04-15 15:33:59 +08:00
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
{
|
|
|
|
base.Update();
|
|
|
|
|
|
|
|
if (!attemptStart())
|
|
|
|
{
|
2021-04-26 16:19:44 +08:00
|
|
|
// Ensure all player clocks are stopped until the start succeeds.
|
|
|
|
foreach (var clock in playerClocks)
|
|
|
|
clock.Stop();
|
2021-04-15 15:33:59 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-11 18:15:53 +08:00
|
|
|
updatePlayerCatchup();
|
|
|
|
updateMasterState();
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// Attempts to start playback. Waits for all player clocks to have available frames for up to <see cref="MAXIMUM_START_DELAY"/> milliseconds.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
|
|
|
/// <returns>Whether playback was started and syncing should occur.</returns>
|
|
|
|
private bool attemptStart()
|
|
|
|
{
|
|
|
|
if (hasStarted)
|
|
|
|
return true;
|
|
|
|
|
2021-04-26 16:19:44 +08:00
|
|
|
if (playerClocks.Count == 0)
|
2021-04-15 15:33:59 +08:00
|
|
|
return false;
|
|
|
|
|
2021-04-26 16:19:44 +08:00
|
|
|
int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value);
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-04-26 16:19:44 +08:00
|
|
|
if (readyCount == playerClocks.Count)
|
2021-06-11 15:25:45 +08:00
|
|
|
return performStart();
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-04-26 16:35:13 +08:00
|
|
|
if (readyCount > 0)
|
|
|
|
{
|
|
|
|
firstStartAttemptTime ??= Time.Current;
|
|
|
|
|
|
|
|
if (Time.Current - firstStartAttemptTime > MAXIMUM_START_DELAY)
|
2021-06-11 15:25:45 +08:00
|
|
|
return performStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool performStart()
|
|
|
|
{
|
|
|
|
ReadyToStart?.Invoke();
|
|
|
|
return hasStarted = true;
|
2021-04-26 16:35:13 +08:00
|
|
|
}
|
2021-04-15 15:33:59 +08:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2021-04-26 16:19:44 +08:00
|
|
|
/// Updates the catchup states of all player clocks clocks.
|
2021-04-15 15:33:59 +08:00
|
|
|
/// </summary>
|
2021-06-11 18:15:53 +08:00
|
|
|
private void updatePlayerCatchup()
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
2021-04-26 16:19:44 +08:00
|
|
|
for (int i = 0; i < playerClocks.Count; i++)
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
2021-04-26 16:19:44 +08:00
|
|
|
var clock = playerClocks[i];
|
2021-05-03 13:37:11 +08:00
|
|
|
|
|
|
|
// How far this player's clock is out of sync, compared to the master clock.
|
|
|
|
// A negative value means the player is running fast (ahead); a positive value means the player is running behind (catching up).
|
2021-04-26 16:19:44 +08:00
|
|
|
double timeDelta = MasterClock.CurrentTime - clock.CurrentTime;
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-04-26 16:19:44 +08:00
|
|
|
// 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.
|
2021-04-15 15:33:59 +08:00
|
|
|
if (timeDelta < -SYNC_TARGET)
|
|
|
|
{
|
2021-07-05 16:45:08 +08:00
|
|
|
// Importantly, set the clock to a non-catchup state. if this isn't done, updateMasterState may incorrectly pause the master clock
|
|
|
|
// when it is required to be running (ie. if all players are ahead of the master).
|
|
|
|
clock.IsCatchingUp = false;
|
2021-04-26 16:19:44 +08:00
|
|
|
clock.Stop();
|
2021-04-15 15:33:59 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-04-26 16:19:44 +08:00
|
|
|
// Make sure the player clock is running if it can.
|
|
|
|
if (!clock.WaitingOnFrames.Value)
|
|
|
|
clock.Start();
|
2022-05-13 05:07:11 +08:00
|
|
|
else
|
|
|
|
clock.Stop();
|
2021-04-15 15:33:59 +08:00
|
|
|
|
2021-04-26 16:19:44 +08:00
|
|
|
if (clock.IsCatchingUp)
|
2021-04-15 15:33:59 +08:00
|
|
|
{
|
2021-04-26 16:19:44 +08:00
|
|
|
// Stop the player clock from catching up if it's within the sync target.
|
2021-04-15 15:33:59 +08:00
|
|
|
if (timeDelta <= SYNC_TARGET)
|
2021-04-26 16:19:44 +08:00
|
|
|
clock.IsCatchingUp = false;
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-04-26 16:19:44 +08:00
|
|
|
// Make the player clock start catching up if it's exceeded the maximum allowable sync offset.
|
2021-04-15 15:33:59 +08:00
|
|
|
if (timeDelta > MAX_SYNC_OFFSET)
|
2021-04-26 16:19:44 +08:00
|
|
|
clock.IsCatchingUp = true;
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-11 18:15:53 +08:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Updates the state of the master clock.
|
|
|
|
/// </summary>
|
|
|
|
private void updateMasterState()
|
|
|
|
{
|
|
|
|
bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp);
|
|
|
|
masterState.Value = anyInSync ? MasterClockState.Synchronised : MasterClockState.TooFarAhead;
|
|
|
|
}
|
2021-04-15 15:33:59 +08:00
|
|
|
}
|
|
|
|
}
|