Skip to content

Fix multiplayer spectator potentially taking too long to start #24451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 16, 2023

Conversation

peppy
Copy link
Member

@peppy peppy commented Aug 2, 2023

When watching from the middle of gameplay, due to a series of failures, SpectatorClock would not get seeked to the current time, causing all clients to look like they were out of sync.

This is a hotfix for the issue. A better fix will require framework changes or considerable restructuring.

I'd recommend testing this works in practice and agreeing that while it is a hack, it's likely not going to cause issues and is something we want to see fixed sooner rather than later.

Closes #22091.

When watching from the middle of gameplay, due to a series of failures,
`SpectatorClock` would not get seeked to the current time, causing all
clients to look like they were out of sync.

This is a hotfix for the issue. A better fix will require framework
changes or considerable restructuring.

I'd recommend testing this works in practice and agreeing that while it
is a hack, it's likely not going to cause issues and is something we
want to see fixed sooner rather than later.
//
// This breaks in multiplayer spectator.
// I hope to remove this once we knock some sense into clocks in general.
(SourceClock as IAdjustableClock)?.Seek(StartTime);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here's a "fun" question. This is preceded by a call to Seek(), which is:

/// <summary>
/// Seek to a specific time in gameplay.
/// </summary>
/// <param name="time">The destination time to seek to.</param>
public virtual void Seek(double time)
{
Logger.Log($"{nameof(GameplayClockContainer)} seeking to {time}");
GameplayClock.Seek(time);
OnSeek?.Invoke();
}

Which in turn calls

public bool Seek(double position)
{
bool success = decoupledClock.Seek(position - TotalAppliedOffset);
finalClockSource.ProcessFrame();
return success;
}

Uh oh. Now there's offsets and decoupled interpolations and so on. The offsets worry me especially. It appears that in standard gameplay this currently seems to be partially saved by (a) the fact that the clock initially stops, which gets rid of the interpolation, and (b) the fact that offsets are applied in LoadComplete() and so they're zero at the point where Reset() is currently called, which is:

protected override void LoadComplete()
{
base.LoadComplete();
if (applyOffsets)
{
Debug.Assert(userBeatmapOffsetClock != null);
Debug.Assert(userGlobalOffsetClock != null);
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true);
beatmapOffsetSubscription = realm.SubscribeToPropertyChanged(
r => r.Find<BeatmapInfo>(beatmap.Value.BeatmapInfo.ID)?.UserSettings,
settings => settings.Offset,
val =>
{
userBeatmapOffsetClock.Offset = val;
});
}
}

but in editor gameplay test I can get this log to trigger:

diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 226108209a..70d4e17110 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -167,6 +167,9 @@ public void Reset(double? time = null, bool startClock = false)
             // I hope to remove this once we knock some sense into clocks in general.
             (SourceClock as IAdjustableClock)?.Seek(StartTime);
 
+            if (SourceClock.CurrentTime != CurrentTime)
+                Logger.Log($"UH OH WE DID A DESYNC. gcc is {CurrentTime}, source is {SourceClock.CurrentTime}");
+
             if (!wasPaused || startClock)
                 Start();
         }

Am I thinking correctly that this is a potential desync? And if so, then maybe just for sheer safety this should be

diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 226108209a..35e1e0b792 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -165,7 +165,7 @@ public void Reset(double? time = null, bool startClock = false)
             //
             // This breaks in multiplayer spectator.
             // I hope to remove this once we knock some sense into clocks in general.
-            (SourceClock as IAdjustableClock)?.Seek(StartTime);
+            (SourceClock as IAdjustableClock)?.Seek(CurrentTime);
 
             if (!wasPaused || startClock)
                 Start();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense. I've updated the workaround and comment to hopefully explain this requirement.

@bdach bdach enabled auto-merge August 16, 2023 08:34
@bdach bdach disabled auto-merge August 16, 2023 09:49
@bdach bdach merged commit 90ab3fc into ppy:master Aug 16, 2023
@peppy peppy deleted the multi-spectator-fix-startup-delay branch August 17, 2023 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multiplayer spectator sometimes takes ages to start
2 participants