From e3a5299b1acc6bba39f5b9de2726159add8ecb89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 19:23:43 +0900 Subject: [PATCH 01/27] Expose the loading player in `PlayerLoader` --- osu.Game/Screens/Play/PlayerLoader.cs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 41eb822e39..384561d616 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Play private bool readyForPush => !playerConsumed // don't push unless the player is completely loaded - && player?.LoadState == LoadState.Ready + && CurrentPlayer?.LoadState == LoadState.Ready // don't push if the user is hovering one of the panes, unless they are idle. && (IsHovered || idleTracker.IsIdle.Value) // don't push if the user is dragging a slider or otherwise. @@ -100,10 +100,14 @@ namespace osu.Game.Screens.Play private readonly Func createPlayer; - private Player player; + /// + /// The instance being loaded by this screen. + /// + [CanBeNull] + public Player CurrentPlayer { get; private set; } /// - /// Whether the curent player instance has been consumed via . + /// Whether the current player instance has been consumed via . /// private bool playerConsumed; @@ -237,12 +241,12 @@ namespace osu.Game.Screens.Play { base.OnResuming(last); - var lastScore = player.Score; + var lastScore = CurrentPlayer.Score; AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo; // prepare for a retry. - player = null; + CurrentPlayer = null; playerConsumed = false; cancelLoad(); @@ -346,7 +350,7 @@ namespace osu.Game.Screens.Play Debug.Assert(!playerConsumed); playerConsumed = true; - return player; + return CurrentPlayer; } private void prepareNewPlayer() @@ -354,11 +358,11 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - player = createPlayer(); - player.RestartCount = restartCount++; - player.RestartRequested = restartRequested; + CurrentPlayer = createPlayer(); + CurrentPlayer.RestartCount = restartCount++; + CurrentPlayer.RestartRequested = restartRequested; - LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false); + LoadTask = LoadComponentAsync(CurrentPlayer, _ => MetadataInfo.Loading = false); } private void restartRequested() @@ -472,7 +476,7 @@ namespace osu.Game.Screens.Play if (isDisposing) { // if the player never got pushed, we should explicitly dispose it. - DisposalTask = LoadTask?.ContinueWith(_ => player?.Dispose()); + DisposalTask = LoadTask?.ContinueWith(_ => CurrentPlayer?.Dispose()); } } From 5164b4d6403bcb83fedbd8f83a35999eb1eeb58f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 19:36:33 +0900 Subject: [PATCH 02/27] Use `nullable` in `PlayerLoader` --- osu.Game/Screens/Play/PlayerLoader.cs | 54 ++++++++++++++------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 384561d616..ba720af2a1 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -1,10 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Diagnostics; using System.Threading.Tasks; -using JetBrains.Annotations; using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -48,31 +49,31 @@ namespace osu.Game.Screens.Play public override bool HandlePositionalInput => true; // We show the previous screen status - protected override UserActivity InitialActivity => null; + protected override UserActivity? InitialActivity => null; protected override bool PlayResumeSound => false; - protected BeatmapMetadataDisplay MetadataInfo { get; private set; } + protected BeatmapMetadataDisplay MetadataInfo { get; private set; } = null!; /// /// A fill flow containing the player settings groups, exposed for the ability to hide it from inheritors of the player loader. /// - protected FillFlowContainer PlayerSettings { get; private set; } + protected FillFlowContainer PlayerSettings { get; private set; } = null!; - protected VisualSettings VisualSettings { get; private set; } + protected VisualSettings VisualSettings { get; private set; } = null!; - protected AudioSettings AudioSettings { get; private set; } + protected AudioSettings AudioSettings { get; private set; } = null!; - protected Task LoadTask { get; private set; } + protected Task? LoadTask { get; private set; } - protected Task DisposalTask { get; private set; } + protected Task? DisposalTask { get; private set; } private bool backgroundBrightnessReduction; private readonly BindableDouble volumeAdjustment = new BindableDouble(1); - private AudioFilter lowPassFilter; - private AudioFilter highPassFilter; + private AudioFilter lowPassFilter = null!; + private AudioFilter highPassFilter = null!; protected bool BackgroundBrightnessReduction { @@ -94,47 +95,45 @@ namespace osu.Game.Screens.Play // don't push if the user is hovering one of the panes, unless they are idle. && (IsHovered || idleTracker.IsIdle.Value) // don't push if the user is dragging a slider or otherwise. - && inputManager?.DraggedDrawable == null + && inputManager.DraggedDrawable == null // don't push if a focused overlay is visible, like settings. - && inputManager?.FocusedDrawable == null; + && inputManager.FocusedDrawable == null; private readonly Func createPlayer; /// /// The instance being loaded by this screen. /// - [CanBeNull] - public Player CurrentPlayer { get; private set; } + public Player? CurrentPlayer { get; private set; } /// /// Whether the current player instance has been consumed via . /// private bool playerConsumed; - private LogoTrackingContainer content; + private LogoTrackingContainer content = null!; private bool hideOverlays; - private InputManager inputManager; + private InputManager inputManager = null!; - private IdleTracker idleTracker; + private IdleTracker idleTracker = null!; - private ScheduledDelegate scheduledPushPlayer; + private ScheduledDelegate? scheduledPushPlayer; - [CanBeNull] - private EpilepsyWarning epilepsyWarning; + private EpilepsyWarning? epilepsyWarning; [Resolved(CanBeNull = true)] - private NotificationOverlay notificationOverlay { get; set; } + private NotificationOverlay? notificationOverlay { get; set; } [Resolved(CanBeNull = true)] - private VolumeOverlay volumeOverlay { get; set; } + private VolumeOverlay? volumeOverlay { get; set; } [Resolved] - private AudioManager audioManager { get; set; } + private AudioManager audioManager { get; set; } = null!; [Resolved(CanBeNull = true)] - private BatteryInfo batteryInfo { get; set; } + private BatteryInfo? batteryInfo { get; set; } public PlayerLoader(Func createPlayer) { @@ -241,6 +240,8 @@ namespace osu.Game.Screens.Play { base.OnResuming(last); + Debug.Assert(CurrentPlayer != null); + var lastScore = CurrentPlayer.Score; AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo; @@ -348,6 +349,7 @@ namespace osu.Game.Screens.Play private Player consumePlayer() { Debug.Assert(!playerConsumed); + Debug.Assert(CurrentPlayer != null); playerConsumed = true; return CurrentPlayer; @@ -484,7 +486,7 @@ namespace osu.Game.Screens.Play #region Mute warning - private Bindable muteWarningShownOnce; + private Bindable muteWarningShownOnce = null!; private int restartCount; @@ -539,7 +541,7 @@ namespace osu.Game.Screens.Play #region Low battery warning - private Bindable batteryWarningShownOnce; + private Bindable batteryWarningShownOnce = null!; private void showBatteryWarningIfNeeded() { From e3a8bb2d1c36c4995177a2826126e47dc2a0c6eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 19:23:49 +0900 Subject: [PATCH 03/27] Add test coverage of `SpectatorPlayer` failing to seek on inopportune frame arrival time --- .../Visual/Gameplay/TestSceneSpectator.cs | 54 ++++++++++++++++++- .../Visual/Spectator/TestSpectatorClient.cs | 5 +- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index d614815316..8b420cebc8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -70,6 +70,56 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [Test] + public void TestSeekToGameplayStartFramesArriveAfterPlayerLoad() + { + const double gameplay_start = 10000; + + loadSpectatingScreen(); + + start(); + + waitForPlayer(); + + sendFrames(startTime: gameplay_start); + + AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start); + } + + /// + /// Tests the same as but with the frames arriving just as is transitioning into existence. + /// + [Test] + public void TestSeekToGameplayStartFramesArriveAsPlayerLoaded() + { + const double gameplay_start = 10000; + + loadSpectatingScreen(); + + start(); + + AddUntilStep("wait for player loader", () => (Stack.CurrentScreen as PlayerLoader)?.IsLoaded == true); + + AddUntilStep("queue send frames on player load", () => + { + var loadingPlayer = (Stack.CurrentScreen as PlayerLoader)?.CurrentPlayer; + + if (loadingPlayer == null) + return false; + + loadingPlayer.OnLoadComplete += _ => + { + spectatorClient.SendFramesFromUser(streamingUser.Id, 10, gameplay_start); + }; + return true; + }); + + waitForPlayer(); + + AddUntilStep("state is playing", () => spectatorClient.WatchedUserStates[streamingUser.Id].State == SpectatedUserState.Playing); + AddAssert("time is greater than seek target", () => currentFrameStableTime > gameplay_start); + } + [Test] public void TestFrameStarvationAndResume() { @@ -319,9 +369,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void checkPaused(bool state) => AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType().First().IsPaused.Value == state); - private void sendFrames(int count = 10) + private void sendFrames(int count = 10, double startTime = 0) { - AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count)); + AddStep("send frames", () => spectatorClient.SendFramesFromUser(streamingUser.Id, count, startTime)); } private void loadSpectatingScreen() diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index f5da95bd7b..ac7cb43e02 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -88,7 +88,8 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to send frames for. /// The total number of frames to send. - public void SendFramesFromUser(int userId, int count) + /// The time to start gameplay frames from. + public void SendFramesFromUser(int userId, int count, double startTime = 0) { var frames = new List(); @@ -102,7 +103,7 @@ namespace osu.Game.Tests.Visual.Spectator flush(); var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1; - frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + frames.Add(new LegacyReplayFrame(currentFrameIndex * 100 + startTime, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); } flush(); From a4a0241800934b62349ac11b913e5e0a1c94c929 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 20:54:42 +0900 Subject: [PATCH 04/27] Use a more explicit flow to set and reset `GameplayClockContainer` start time --- .../Screens/Play/GameplayClockContainer.cs | 20 ++++++-- .../Play/MasterGameplayClockContainer.cs | 49 ++++++++++--------- osu.Game/Screens/Play/Player.cs | 10 ++-- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 +- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0fd524f976..b60a3e306e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -41,6 +41,15 @@ namespace osu.Game.Screens.Play /// public event Action OnSeek; + /// + /// The time from which gameplay should start. Will be seeked to on calling . + /// + /// + /// If not set, a value of zero will be used. + /// Importantly, the value will be inferred from the current ruleset in unless specified. + /// + protected double? GameplayStartTime { get; private set; } + /// /// Creates a new . /// @@ -106,15 +115,20 @@ namespace osu.Game.Screens.Play /// /// Resets this and the source to an initial state ready for gameplay. /// - public virtual void Reset() + /// Whether to start the clock immediately. + /// A time to use for future calls as the definite start of gameplay. + public void Reset(bool startClock = false, double? gameplayStartTime = null) { + if (gameplayStartTime != null) + GameplayStartTime = gameplayStartTime; + ensureSourceClockSet(); - Seek(0); + Seek(GameplayStartTime ?? 0); // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); - if (!IsPaused.Value) + if (!IsPaused.Value && startClock) Start(); } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index af58e9d910..ce7b6ef7f0 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -58,7 +58,6 @@ namespace osu.Game.Screens.Play private HardwareCorrectionOffsetClock platformOffsetClock; private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; - private double startOffset; private IDisposable beatmapOffsetSubscription; @@ -90,26 +89,33 @@ namespace osu.Game.Screens.Play settings => settings.Offset, val => userBeatmapOffsetClock.Offset = val); - // sane default provided by ruleset. - startOffset = gameplayStartTime; - - if (!startAtGameplayStart) + if (GameplayStartTime == null) { - startOffset = Math.Min(0, startOffset); + // sane default provided by ruleset. + double offset = gameplayStartTime; - // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. - // this is commonly used to display an intro before the audio track start. - double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; - if (firstStoryboardEvent != null) - startOffset = Math.Min(startOffset, firstStoryboardEvent.Value); + if (!startAtGameplayStart) + { + offset = Math.Min(0, offset); - // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. - // this is not available as an option in the live editor but can still be applied via .osu editing. - if (beatmap.BeatmapInfo.AudioLeadIn > 0) - startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. + // this is commonly used to display an intro before the audio track start. + double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; + if (firstStoryboardEvent != null) + offset = Math.Min(offset, firstStoryboardEvent.Value); + + // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. + // this is not available as an option in the live editor but can still be applied via .osu editing. + if (beatmap.BeatmapInfo.AudioLeadIn > 0) + offset = Math.Min(offset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + } + + // Reset may have been called externally before LoadComplete. + // If it was and the clock is in a playing state, we want to ensure that it isn't stopped here. + bool isStarted = !IsPaused.Value; + + Reset(startClock: isStarted, gameplayStartTime: offset); } - - Seek(startOffset); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) @@ -164,12 +170,6 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - public override void Reset() - { - base.Reset(); - Seek(startOffset); - } - protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. @@ -231,6 +231,7 @@ namespace osu.Game.Screens.Play } private class HardwareCorrectionOffsetClock : FramedOffsetClock + { private readonly BindableDouble pauseRateAdjust; @@ -276,9 +277,9 @@ namespace osu.Game.Screens.Play } private class MasterGameplayClock : GameplayClock + { public readonly List> MutableNonGameplayAdjustments = new List>(); - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; public MasterGameplayClock(FramedOffsetClock underlyingClock) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cb8f4b6020..6ec21ef924 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -607,13 +607,13 @@ namespace osu.Game.Screens.Play private ScheduledDelegate frameStablePlaybackResetDelegate; /// - /// Seeks to a specific time in gameplay, bypassing frame stability. + /// Specify and seek to a custom start time from which gameplay should be observed. /// /// - /// Intermediate hitobject judgements may not be applied or reverted correctly during this seek. + /// This performance a non-frame-stable seek. Intermediate hitobject judgements may not be applied or reverted correctly during this seek. /// /// The destination time to seek to. - internal void NonFrameStableSeek(double time) + protected void SetGameplayStartTime(double time) { if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed) frameStablePlaybackResetDelegate.RunTask(); @@ -621,7 +621,7 @@ namespace osu.Game.Screens.Play bool wasFrameStable = DrawableRuleset.FrameStablePlayback; DrawableRuleset.FrameStablePlayback = false; - Seek(time); + GameplayClockContainer.Reset(gameplayStartTime: time); // Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek. frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable); @@ -981,7 +981,7 @@ namespace osu.Game.Screens.Play if (GameplayClockContainer.GameplayClock.IsRunning) throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"); - GameplayClockContainer.Reset(); + GameplayClockContainer.Reset(true); } public override void OnSuspending(IScreen next) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index c415041081..c0682952c3 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play } if (isFirstBundle && score.Replay.Frames.Count > 0) - NonFrameStableSeek(score.Replay.Frames[0].Time); + SetGameplayStartTime(score.Replay.Frames[0].Time); } protected override Score CreateScore(IBeatmap beatmap) => score; From e3ab5de8cd944a35228d47bba2623fbd5ee712ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Mar 2022 23:39:45 +0900 Subject: [PATCH 05/27] Tidy up constructor logic overlap with `gameplayStartTime` --- .../TestSceneMultiSpectatorScreen.cs | 2 +- .../Screens/Play/GameplayClockContainer.cs | 8 +-- .../Play/MasterGameplayClockContainer.cs | 69 ++++++++++--------- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index e5e3fecd06..ed8b0bdd96 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -473,7 +473,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) - => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, gameplayStartTime.HasValue); + => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0); } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index b60a3e306e..66f5cb0bd4 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -42,13 +42,13 @@ namespace osu.Game.Screens.Play public event Action OnSeek; /// - /// The time from which gameplay should start. Will be seeked to on calling . + /// The time from which the clock should start. Will be seeked to on calling . /// /// /// If not set, a value of zero will be used. /// Importantly, the value will be inferred from the current ruleset in unless specified. /// - protected double? GameplayStartTime { get; private set; } + protected double? StartTime { get; set; } /// /// Creates a new . @@ -120,10 +120,10 @@ namespace osu.Game.Screens.Play public void Reset(bool startClock = false, double? gameplayStartTime = null) { if (gameplayStartTime != null) - GameplayStartTime = gameplayStartTime; + StartTime = gameplayStartTime; ensureSourceClockSet(); - Seek(GameplayStartTime ?? 0); + Seek(StartTime ?? 0); // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index ce7b6ef7f0..58e8b5f1ad 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -49,9 +49,6 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); private readonly WorkingBeatmap beatmap; - private readonly double gameplayStartTime; - private readonly bool startAtGameplayStart; - private readonly double firstHitObjectTime; private HardwareCorrectionOffsetClock userGlobalOffsetClock; private HardwareCorrectionOffsetClock userBeatmapOffsetClock; @@ -61,20 +58,29 @@ namespace osu.Game.Screens.Play private IDisposable beatmapOffsetSubscription; + private readonly double latestGameplayStartTime; + [Resolved] private RealmAccess realm { get; set; } [Resolved] private OsuConfigManager config { get; set; } - public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + /// + /// Create a new master gameplay clock container. + /// + /// The beatmap to be used for time and metadata references. + /// The latest time which should be used when introducing gameplay. Will be used when skipping forward. + /// Whether to start from the provided latest start time rather than zero. + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double latestGameplayStartTime, bool startFromLatestStartTime = false) : base(beatmap.Track) { this.beatmap = beatmap; - this.gameplayStartTime = gameplayStartTime; - this.startAtGameplayStart = startAtGameplayStart; - firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; + this.latestGameplayStartTime = latestGameplayStartTime; + + if (startFromLatestStartTime) + StartTime = latestGameplayStartTime; } protected override void LoadComplete() @@ -89,33 +95,34 @@ namespace osu.Game.Screens.Play settings => settings.Offset, val => userBeatmapOffsetClock.Offset = val); - if (GameplayStartTime == null) - { - // sane default provided by ruleset. - double offset = gameplayStartTime; + // Reset may have been called externally before LoadComplete. + // If it was, and the clock is in a playing state, we want to ensure that it isn't stopped here. + bool isStarted = !IsPaused.Value; - if (!startAtGameplayStart) - { - offset = Math.Min(0, offset); + // If a custom start time was not specified, calculate the best value to use. + double gameplayStartTime = StartTime ?? findBeatmapStartTime(); - // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. - // this is commonly used to display an intro before the audio track start. - double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; - if (firstStoryboardEvent != null) - offset = Math.Min(offset, firstStoryboardEvent.Value); + Reset(startClock: isStarted, gameplayStartTime: gameplayStartTime); + } - // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. - // this is not available as an option in the live editor but can still be applied via .osu editing. - if (beatmap.BeatmapInfo.AudioLeadIn > 0) - offset = Math.Min(offset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); - } + private double findBeatmapStartTime() + { + // start with the originally provided latest time as a sane default. + double time = latestGameplayStartTime; - // Reset may have been called externally before LoadComplete. - // If it was and the clock is in a playing state, we want to ensure that it isn't stopped here. - bool isStarted = !IsPaused.Value; + // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. + // this is commonly used to display an intro before the audio track start. + double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; + if (firstStoryboardEvent != null) + time = Math.Min(time, firstStoryboardEvent.Value); - Reset(startClock: isStarted, gameplayStartTime: offset); - } + // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. + // this is not available as an option in the live editor but can still be applied via .osu editing. + double firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; + if (beatmap.BeatmapInfo.AudioLeadIn > 0) + time = Math.Min(time, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + + return time; } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) @@ -158,10 +165,10 @@ namespace osu.Game.Screens.Play /// public void Skip() { - if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + if (GameplayClock.CurrentTime > latestGameplayStartTime - MINIMUM_SKIP_TIME) return; - double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + double skipTarget = latestGameplayStartTime - MINIMUM_SKIP_TIME; if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) // double skip exception for storyboards with very long intros From c6be26eb018a4f26701f0b2bcbe58c742fa4a2e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 01:15:17 +0900 Subject: [PATCH 06/27] Rename start time calculation method and add more commenting to explain purpose better --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 58e8b5f1ad..529503e020 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -100,15 +100,18 @@ namespace osu.Game.Screens.Play bool isStarted = !IsPaused.Value; // If a custom start time was not specified, calculate the best value to use. - double gameplayStartTime = StartTime ?? findBeatmapStartTime(); + double gameplayStartTime = StartTime ?? findEarliestStartTime(); Reset(startClock: isStarted, gameplayStartTime: gameplayStartTime); } - private double findBeatmapStartTime() + private double findEarliestStartTime() { - // start with the originally provided latest time as a sane default. - double time = latestGameplayStartTime; + // here we are trying to find the time to start playback from the "zero" point. + // generally this is either zero, or some point earlier than zero in the case of storyboards, lead-ins etc. + + // start with the originally provided latest time (if before zero). + double time = Math.Min(0, latestGameplayStartTime); // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. // this is commonly used to display an intro before the audio track start. From b5ff9ed13a0d6e936d949606af3ca44fe5a00df0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 14:27:58 +0900 Subject: [PATCH 07/27] Add test coverage of multiplayer being paused when users are still loading --- .../Multiplayer/TestSceneMultiplayer.cs | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index e38da96bd5..2a3dbdaf95 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -494,17 +494,20 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); - AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddAssert("Mods match current item", + () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() }); - AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddAssert("Mods don't match current item", + () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); AddStep("start match externally", () => multiplayerClient.StartMatch()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); - AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); + AddAssert("Mods match current item", + () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); } [Test] @@ -664,6 +667,41 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen); } + [Test] + public void TestGameplayDoesntStartWithNonLoadedUser() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + } + } + }); + + pressReadyButton(); + + AddStep("join other user and ready", () => + { + multiplayerClient.AddUser(new APIUser { Id = 1234 }); + multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready); + }); + + AddStep("start match", () => + { + multiplayerClient.StartMatch(); + }); + + AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player); + + AddWaitStep("wait some", 20); + + AddAssert("ensure gameplay hasn't started", () => this.ChildrenOfType().SingleOrDefault()?.IsRunning == false); + } + [Test] public void TestRoomSettingsReQueriedWhenJoiningRoom() { From 59aef88504a8408eb49b61d7362941ef177d7520 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 15:07:57 +0900 Subject: [PATCH 08/27] Simplify clock reset/start flow in `MultiSpectatorScreen` --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 6747b8fc66..523301f4fd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -164,7 +164,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate base.LoadComplete(); masterClockContainer.Reset(); - masterClockContainer.Stop(); syncManager.ReadyToStart += onReadyToStart; syncManager.MasterState.BindValueChanged(onMasterStateChanged, true); @@ -198,8 +197,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate .DefaultIfEmpty(0) .Min(); - masterClockContainer.Seek(startTime); - masterClockContainer.Start(); + masterClockContainer.Reset(true, startTime); // 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; From f09a9467226f335c95c823bb03b09fc99f13de4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 15:08:25 +0900 Subject: [PATCH 09/27] Start `GameplayClockContainer` paused for better state control --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 5 +---- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 2 -- osu.Game/Screens/Play/GameplayClockContainer.cs | 6 +++--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 77b402ad3c..0ed432bbea 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -85,10 +85,7 @@ namespace osu.Game.Tests.Gameplay Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); - if (whileStopped) - gameplayClockContainer.Stop(); - - gameplayClockContainer.Reset(); + gameplayClockContainer.Reset(startClock: !whileStopped); }); AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 615bd41f3f..ececa1e497 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -55,8 +55,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public SpectatorGameplayClockContainer([NotNull] IClock sourceClock) : base(sourceClock) { - // the container should initially be in a stopped state until the catch-up clock is started by the sync manager. - Stop(); } protected override void Update() diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 66f5cb0bd4..226f5709c7 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play /// /// Whether gameplay is paused. /// - public readonly BindableBool IsPaused = new BindableBool(); + public readonly BindableBool IsPaused = new BindableBool(true); /// /// The adjustable source clock used for gameplay. Should be used for seeks and clock control. @@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play /// /// Resets this and the source to an initial state ready for gameplay. /// - /// Whether to start the clock immediately. + /// Whether to start the clock immediately, if not already started. /// A time to use for future calls as the definite start of gameplay. public void Reset(bool startClock = false, double? gameplayStartTime = null) { @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); - if (!IsPaused.Value && startClock) + if (!IsPaused.Value || startClock) Start(); } From 0988c2b0fabd104792f7ebd7a4b3be49b1a26fb9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 15:28:48 +0900 Subject: [PATCH 10/27] Move `DrawableRuleset` binding to `LoadComplete` to avoid exceptions on `InputManager` access --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 29559f5036..be1105e7ff 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -133,6 +133,11 @@ namespace osu.Game.Rulesets.UI p.NewResult += (_, r) => NewResult?.Invoke(r); p.RevertResult += (_, r) => RevertResult?.Invoke(r); })); + } + + protected override void LoadComplete() + { + base.LoadComplete(); IsPaused.ValueChanged += paused => { From 611562c6502aaedeeba6f1c8ff883d51fb539518 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 16:21:14 +0900 Subject: [PATCH 11/27] Add more comments around catch up logic --- .../OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs | 5 ++++- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs index 1a5231e602..de23b4fef7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs @@ -17,8 +17,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Bindable WaitingOnFrames { get; } /// - /// Whether this clock is resynchronising to the master clock. + /// Whether this clock is behind the master clock and running at a higher rate to catch up to it. /// + /// + /// Of note, this will be false if this clock is *ahead* of the master clock. + /// bool IsCatchingUp { get; set; } /// diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index ececa1e497..29afaf00d8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -59,6 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override void Update() { + // The SourceClock here is always a CatchUpSpectatorPlayerClock. // 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(); From 4bc7c69bf70e7aa1bf91deec0e587523da0ea368 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 16:27:14 +0900 Subject: [PATCH 12/27] Fix test regression due to missing argument --- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index ed8b0bdd96..653a35417e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -473,7 +473,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) - => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0); + => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, true); } } } From 7cb7b03cee678db55530d8134d39295089a6a216 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Mar 2022 18:16:45 +0900 Subject: [PATCH 13/27] Fix `TestSceneMasterGameplayClockContainer` not clearing previous test clocks --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 0ed432bbea..b2dd6cbd97 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); + Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); }); AddStep("start clock", () => gameplayClockContainer.Start()); @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); + Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); }); AddStep("start clock", () => gameplayClockContainer.Start()); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Gameplay working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); working.LoadTrack(); - Add(gameplayClockContainer = new MasterGameplayClockContainer(working, 0)); + Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); gameplayClockContainer.Reset(startClock: !whileStopped); }); From 8ca9cbc866e089338b09585890c97fc5dfce8da4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Mar 2022 14:43:33 +0900 Subject: [PATCH 14/27] Set a more correct initial value for `pauseFreqAdjust` As the `GameplayClock` now starts paused, this value needs to match to ensure things work correctly. For a better explanation of how we got here, see discussion at https://github.com/ppy/osu/pull/17302#discussion_r830017735. --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 529503e020..9bcbbf789b 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play private double totalAppliedOffset => userBeatmapOffsetClock.RateAdjustedOffset + userGlobalOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset; - private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(); // Important that this starts at zero, matching the paused state of the clock. private readonly WorkingBeatmap beatmap; From 16dc2f6ef50bccada9e9cba2f3e934324204140c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Mar 2022 14:51:21 +0900 Subject: [PATCH 15/27] Adjust `TestSceneFailAnimation` to account for initial frequency change --- osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 744227c55e..83d7d769df 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -56,10 +56,11 @@ namespace osu.Game.Tests.Visual.Gameplay private double lastFrequency = double.MaxValue; - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); + // This must be done in UpdateAfterChildren to allow the gameplay clock to have updated before checking values. double freq = Beatmap.Value.Track.AggregateFrequency.Value; FrequencyIncreased |= freq > lastFrequency; From 1049e349e39a8944c223b71ad8d0a621a3965781 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Mar 2022 14:55:01 +0900 Subject: [PATCH 16/27] Add test coverage of user offset being set before construction of `MasterGameplayClockContainer` --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index b2dd6cbd97..75be7b0362 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -73,11 +73,15 @@ namespace osu.Game.Tests.Gameplay public void TestSeekPerformsInGameplayTime( [Values(1.0, 0.5, 2.0)] double clockRate, [Values(0.0, 200.0, -200.0)] double userOffset, - [Values(false, true)] bool whileStopped) + [Values(false, true)] bool whileStopped, + [Values(false, true)] bool setAudioOffsetBeforeConstruction) { ClockBackedTestWorkingBeatmap working = null; GameplayClockContainer gameplayClockContainer = null; + if (setAudioOffsetBeforeConstruction) + AddStep($"preset audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); + AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); @@ -89,7 +93,9 @@ namespace osu.Game.Tests.Gameplay }); AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate))); - AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); + + if (!setAudioOffsetBeforeConstruction) + AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset)); AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500)); AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f)); From c2b2d443ed070da9b8fafa5b871e235f40ca13f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 15:31:12 +0900 Subject: [PATCH 17/27] Add more comprehensive assert output to try and discern CI issues --- .../Visual/Gameplay/TestSceneLeadIn.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 5a1fc1b1e5..58d2e4a1da 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -36,10 +36,10 @@ namespace osu.Game.Tests.Visual.Gameplay BeatmapInfo = { AudioLeadIn = leadIn } }); - AddAssert($"first frame is {expectedStartTime}", () => + AddStep($"check first frame time", () => { - Debug.Assert(player.FirstFrameClockTime != null); - return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); + Assert.That(player.FirstFrameClockTime, Is.Not.Null); + Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms)); }); } @@ -59,10 +59,10 @@ namespace osu.Game.Tests.Visual.Gameplay loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); - AddAssert($"first frame is {expectedStartTime}", () => + AddStep($"check first frame time", () => { - Debug.Assert(player.FirstFrameClockTime != null); - return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); + Assert.That(player.FirstFrameClockTime, Is.Not.Null); + Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms)); }); } @@ -97,10 +97,10 @@ namespace osu.Game.Tests.Visual.Gameplay loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); - AddAssert($"first frame is {expectedStartTime}", () => + AddStep($"check first frame time", () => { - Debug.Assert(player.FirstFrameClockTime != null); - return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); + Assert.That(player.FirstFrameClockTime, Is.Not.Null); + Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms)); }); } From 065bb60324c6f62393aee92799b4a6d916a9fa77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 16:05:11 +0900 Subject: [PATCH 18/27] Remove unused using statements --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 58d2e4a1da..5ec9c6c474 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu; From 808f0ecb74d2f2c65fdc9558d1eb07a616b6ac18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Apr 2022 14:10:50 +0900 Subject: [PATCH 19/27] Ensure running state is updated before performing `Seek` in `GameplayClockContainer.Reset` --- osu.Game/Screens/Play/GameplayClockContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 226f5709c7..f2df00d691 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -122,14 +122,14 @@ namespace osu.Game.Screens.Play if (gameplayStartTime != null) StartTime = gameplayStartTime; - ensureSourceClockSet(); - Seek(StartTime ?? 0); - // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); if (!IsPaused.Value || startClock) Start(); + + ensureSourceClockSet(); + Seek(StartTime ?? 0); } /// From cb6e557212a5156ba2f59dd542943c878414b40f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Apr 2022 14:11:23 +0900 Subject: [PATCH 20/27] Fix `MasterGameplayClockContainer` having incorrect rate-based offsets immediately after `LoadComplete` I've attempted to explain why this is required using inline comments. There's also further conversation at https://github.com/ppy/osu/pull/17302#issuecomment-1091850927. --- .../Play/MasterGameplayClockContainer.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 9bcbbf789b..54901746fc 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -130,17 +130,32 @@ namespace osu.Game.Screens.Play protected override void OnIsPausedChanged(ValueChangedEvent isPaused) { - // The source is stopped by a frequency fade first. - if (isPaused.NewValue) + if (IsLoaded) { - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => + // During normal operation, the source is stopped after performing a frequency ramp. + if (isPaused.NewValue) { - if (IsPaused.Value == isPaused.NewValue) - AdjustableSource.Stop(); - }); + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => + { + if (IsPaused.Value == isPaused.NewValue) + AdjustableSource.Stop(); + }); + } + else + this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } else - this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); + { + if (isPaused.NewValue) + AdjustableSource.Stop(); + + // If not yet loaded, we still want to ensure relevant state is correct, as it is used for offset calculations. + pauseFreqAdjust.Value = isPaused.NewValue ? 0 : 1; + + // We must also process underlying gameplay clocks to update rate-adjusted offsets with the new frequency adjustment. + // Without doing this, an initial seek may be performed with the wrong offset. + GameplayClock.UnderlyingClock.ProcessFrame(); + } } public override void Start() From 01cec7d3fb0e11f8e8f0b55a4ad509b6f6865a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Apr 2022 19:37:01 +0900 Subject: [PATCH 21/27] Remove unnecessary string literals --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 5ec9c6c474..b90bd93002 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay BeatmapInfo = { AudioLeadIn = leadIn } }); - AddStep($"check first frame time", () => + AddStep("check first frame time", () => { Assert.That(player.FirstFrameClockTime, Is.Not.Null); Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms)); @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); - AddStep($"check first frame time", () => + AddStep("check first frame time", () => { Assert.That(player.FirstFrameClockTime, Is.Not.Null); Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms)); @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); - AddStep($"check first frame time", () => + AddStep("check first frame time", () => { Assert.That(player.FirstFrameClockTime, Is.Not.Null); Assert.That(player.FirstFrameClockTime.Value, Is.EqualTo(expectedStartTime).Within(lenience_ms)); From 282fccb4c8842ff30cde671989a620303f4dc0f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Apr 2022 12:24:47 +0900 Subject: [PATCH 22/27] Fix typo in xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6ec21ef924..be19565657 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -610,7 +610,7 @@ namespace osu.Game.Screens.Play /// Specify and seek to a custom start time from which gameplay should be observed. /// /// - /// This performance a non-frame-stable seek. Intermediate hitobject judgements may not be applied or reverted correctly during this seek. + /// This performs a non-frame-stable seek. Intermediate hitobject judgements may not be applied or reverted correctly during this seek. /// /// The destination time to seek to. protected void SetGameplayStartTime(double time) From d4286255a094ac759e40e34c9be3aaa9caaf85b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Apr 2022 13:49:58 +0900 Subject: [PATCH 23/27] Expose and set `GameplayStartTime` directly, rather than via `Reset` parameter --- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 3 ++- osu.Game/Screens/Play/GameplayClockContainer.cs | 8 ++------ osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 3 ++- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 523301f4fd..2d03276fe5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -197,7 +197,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate .DefaultIfEmpty(0) .Min(); - masterClockContainer.Reset(true, startTime); + 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; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f2df00d691..721abc66f8 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play /// If not set, a value of zero will be used. /// Importantly, the value will be inferred from the current ruleset in unless specified. /// - protected double? StartTime { get; set; } + public double? StartTime { get; set; } /// /// Creates a new . @@ -116,12 +116,8 @@ namespace osu.Game.Screens.Play /// Resets this and the source to an initial state ready for gameplay. /// /// Whether to start the clock immediately, if not already started. - /// A time to use for future calls as the definite start of gameplay. - public void Reset(bool startClock = false, double? gameplayStartTime = null) + public void Reset(bool startClock = false) { - if (gameplayStartTime != null) - StartTime = gameplayStartTime; - // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 54901746fc..88dc6db0f7 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -100,9 +100,9 @@ namespace osu.Game.Screens.Play bool isStarted = !IsPaused.Value; // If a custom start time was not specified, calculate the best value to use. - double gameplayStartTime = StartTime ?? findEarliestStartTime(); + StartTime ??= findEarliestStartTime(); - Reset(startClock: isStarted, gameplayStartTime: gameplayStartTime); + Reset(startClock: isStarted); } private double findEarliestStartTime() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index be19565657..f99b07c313 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -621,7 +621,8 @@ namespace osu.Game.Screens.Play bool wasFrameStable = DrawableRuleset.FrameStablePlayback; DrawableRuleset.FrameStablePlayback = false; - GameplayClockContainer.Reset(gameplayStartTime: time); + GameplayClockContainer.StartTime = time; + GameplayClockContainer.Reset(); // Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek. frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable); From a59c6013c790f305d8567d98ec4e3a846c29fdeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Apr 2022 14:03:52 +0900 Subject: [PATCH 24/27] Rename `latestGameplayStartTime` to `skipTargetTime` --- .../Play/MasterGameplayClockContainer.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 88dc6db0f7..61f31db435 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Play private IDisposable beatmapOffsetSubscription; - private readonly double latestGameplayStartTime; + private readonly double skipTargetTime; [Resolved] private RealmAccess realm { get; set; } @@ -70,17 +70,17 @@ namespace osu.Game.Screens.Play /// Create a new master gameplay clock container. /// /// The beatmap to be used for time and metadata references. - /// The latest time which should be used when introducing gameplay. Will be used when skipping forward. - /// Whether to start from the provided latest start time rather than zero. - public MasterGameplayClockContainer(WorkingBeatmap beatmap, double latestGameplayStartTime, bool startFromLatestStartTime = false) + /// The latest time which should be used when introducing gameplay. Will be used when skipping forward. + /// Whether to start from the provided latest start time rather than zero. + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime, bool startFromSkipTarget = false) : base(beatmap.Track) { this.beatmap = beatmap; - this.latestGameplayStartTime = latestGameplayStartTime; + this.skipTargetTime = skipTargetTime; - if (startFromLatestStartTime) - StartTime = latestGameplayStartTime; + if (startFromSkipTarget) + StartTime = skipTargetTime; } protected override void LoadComplete() @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Play // generally this is either zero, or some point earlier than zero in the case of storyboards, lead-ins etc. // start with the originally provided latest time (if before zero). - double time = Math.Min(0, latestGameplayStartTime); + double time = Math.Min(0, skipTargetTime); // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. // this is commonly used to display an intro before the audio track start. @@ -183,10 +183,10 @@ namespace osu.Game.Screens.Play /// public void Skip() { - if (GameplayClock.CurrentTime > latestGameplayStartTime - MINIMUM_SKIP_TIME) + if (GameplayClock.CurrentTime > skipTargetTime - MINIMUM_SKIP_TIME) return; - double skipTarget = latestGameplayStartTime - MINIMUM_SKIP_TIME; + double skipTarget = skipTargetTime - MINIMUM_SKIP_TIME; if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) // double skip exception for storyboards with very long intros From fbf0e5a45cf4f4f073126a717b35c6edf258c412 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Apr 2022 14:09:49 +0900 Subject: [PATCH 25/27] Remove `startFromSkipTarget` parameter and update usages that required said behaviour --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 7 +++++-- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 8 ++++---- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 2 +- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 7 +------ 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 76ec35d87d..e0a497cf24 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestSampleHasLifetimeEndWithInitialClockTime() { - GameplayClockContainer gameplayContainer = null; + MasterGameplayClockContainer gameplayContainer = null; DrawableStoryboardSample sample = null; AddStep("create container", () => @@ -96,8 +96,11 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true) + const double start_time = 1000; + + Add(gameplayContainer = new MasterGameplayClockContainer(working, start_time) { + StartTime = start_time, IsPaused = { Value = true }, Child = new FrameStabilityContainer { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 653a35417e..703b526e8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -464,16 +464,16 @@ namespace osu.Game.Tests.Visual.Multiplayer private class TestMultiSpectatorScreen : MultiSpectatorScreen { - private readonly double? gameplayStartTime; + private readonly double? startTime; - public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? gameplayStartTime = null) + public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? startTime = null) : base(room, users) { - this.gameplayStartTime = gameplayStartTime; + this.startTime = startTime; } protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) - => new MasterGameplayClockContainer(beatmap, gameplayStartTime ?? 0, true); + => new MasterGameplayClockContainer(beatmap, 0) { StartTime = startTime ?? 0 }; } } } diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index f49603c754..d9e19df350 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.GameplayTest } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new MasterGameplayClockContainer(beatmap, editorState.Time, true); + => new MasterGameplayClockContainer(beatmap, gameplayStart) { StartTime = editorState.Time }; protected override void LoadComplete() { diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 61f31db435..c5a5c14070 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -71,16 +71,11 @@ namespace osu.Game.Screens.Play /// /// The beatmap to be used for time and metadata references. /// The latest time which should be used when introducing gameplay. Will be used when skipping forward. - /// Whether to start from the provided latest start time rather than zero. - public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime, bool startFromSkipTarget = false) + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime) : base(beatmap.Track) { this.beatmap = beatmap; - this.skipTargetTime = skipTargetTime; - - if (startFromSkipTarget) - StartTime = skipTargetTime; } protected override void LoadComplete() From c8db7cf2a89dec23eec10b0ec217ef28802dd47a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Apr 2022 18:09:23 +0900 Subject: [PATCH 26/27] Reset audio offset before each test run in `TestSceneMasterGameplayClockContainer` --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 75be7b0362..5c04ac88a7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -26,6 +26,12 @@ namespace osu.Game.Tests.Gameplay Dependencies.Cache(localConfig = new OsuConfigManager(LocalStorage)); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("reset audio offset", () => localConfig.SetValue(OsuSetting.AudioOffset, 0.0)); + } + [Test] public void TestStartThenElapsedTime() { From 2abdbe53e7e90a7dfb86541ee475a93fe4ec6665 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 14 Apr 2022 18:55:12 +0900 Subject: [PATCH 27/27] Cleanup whitespace --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index c5a5c14070..ea43fb1546 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -251,7 +251,6 @@ namespace osu.Game.Screens.Play } private class HardwareCorrectionOffsetClock : FramedOffsetClock - { private readonly BindableDouble pauseRateAdjust; @@ -297,7 +296,6 @@ namespace osu.Game.Screens.Play } private class MasterGameplayClock : GameplayClock - { public readonly List> MutableNonGameplayAdjustments = new List>(); public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments;