From 600651795b88563e77e5e8b3120aa71367329aee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 15:23:22 +0900 Subject: [PATCH 01/28] Change `FramedBeatmapClock` to always be decoupled --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 10 +++------- osu.Game/Screens/Edit/EditorClock.cs | 2 +- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 62484fa12bb5..1626fec9bde7 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -13,6 +13,8 @@ using osu.Game.Database; using osu.Game.Screens.Play; +#pragma warning disable CS0618 + namespace osu.Game.Beatmaps { /// @@ -66,19 +68,13 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab public bool IsRewinding { get; private set; } - public bool IsCoupled - { - get => decoupledClock.IsCoupled; - set => decoupledClock.IsCoupled = value; - } - public FramedBeatmapClock(bool applyOffsets = false) { this.applyOffsets = applyOffsets; // A decoupled clock is used to ensure precise time values even when the host audio subsystem is not reporting // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). - decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; + decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; if (applyOffsets) { diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index a05a87310123..108552b61c2a 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -54,7 +54,7 @@ public EditorClock(IBeatmap beatmap = null, BindableBeatDivisor beatDivisor = nu this.beatDivisor = beatDivisor ?? new BindableBeatDivisor(); - underlyingClock = new FramedBeatmapClock(applyOffsets: true) { IsCoupled = false }; + underlyingClock = new FramedBeatmapClock(applyOffsets: true); AddInternal(underlyingClock); } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2478af1dd4da..6e069297d413 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -68,7 +68,7 @@ public GameplayClockContainer(IClock sourceClock, bool applyOffsets = false) InternalChildren = new Drawable[] { - GameplayClock = new FramedBeatmapClock(applyOffsets) { IsCoupled = false }, + GameplayClock = new FramedBeatmapClock(applyOffsets), Content }; } From 3f27be1f330103ced3937de808107376b350bcd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 15:24:03 +0900 Subject: [PATCH 02/28] Replace most usages of `DecoupleableInterpolatingFramedClock` Except `FramedBeatmapClock`, which is the high-effort one. --- .../Visual/Gameplay/TestSceneStoryboard.cs | 4 +--- osu.Game/Screens/Menu/IntroTriangles.cs | 13 +++---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index a6663f308612..893b9f11f424 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -106,14 +106,12 @@ private void loadStoryboard(Storyboard toLoad) if (storyboard != null) storyboardContainer.Remove(storyboard, true); - var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; - storyboardContainer.Clock = decoupledClock; + storyboardContainer.Clock = new FramedClock(Beatmap.Value.Track); storyboard = toLoad.CreateDrawable(SelectedMods.Value); storyboard.Passing = false; storyboardContainer.Add(storyboard); - decoupledClock.ChangeSource(Beatmap.Value.Track); } private void loadStoryboard(string filename, Action? setUpStoryboard = null) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index a9c86b10c410..808680b9e559 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -36,7 +36,6 @@ public partial class IntroTriangles : IntroScreen private Sample welcome; - private DecoupleableInterpolatingFramedClock decoupledClock; private TrianglesIntroSequence intro; public IntroTriangles([CanBeNull] Func createNextScreen = null) @@ -59,18 +58,12 @@ protected override void LogoArriving(OsuLogo logo, bool resuming) { PrepareMenuLoad(); - decoupledClock = new DecoupleableInterpolatingFramedClock - { - IsCoupled = false - }; - - if (UsingThemedIntro) - decoupledClock.ChangeSource(Track); + var decouplingClock = new DecouplingClock(UsingThemedIntro ? Track : null); LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground()) { RelativeSizeAxes = Axes.Both, - Clock = decoupledClock, + Clock = new InterpolatingFramedClock(decouplingClock), LoadMenu = LoadMenu }, _ => { @@ -94,7 +87,7 @@ protected override void LogoArriving(OsuLogo logo, bool resuming) StartTrack(); // no-op for the case of themed intro, no harm in calling for both scenarios as a safety measure. - decoupledClock.Start(); + decouplingClock.Start(); }); } } From 117cd74af671877a05600e261a93323a39fcd7e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 15:32:29 +0900 Subject: [PATCH 03/28] Update usage of `DecoupleableInterpolatingFramedClock` in `FramedBeatmapClock` --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 33 +++++++++---------- .../Screens/Play/GameplayClockContainer.cs | 3 +- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 1626fec9bde7..acd10cccf341 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -13,8 +13,6 @@ using osu.Game.Database; using osu.Game.Screens.Play; -#pragma warning disable CS0618 - namespace osu.Game.Beatmaps { /// @@ -55,7 +53,7 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab private IDisposable? beatmapOffsetSubscription; - private readonly DecoupleableInterpolatingFramedClock decoupledClock; + private readonly DecouplingClock decoupledTrack; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -74,13 +72,13 @@ public FramedBeatmapClock(bool applyOffsets = false) // A decoupled clock is used to ensure precise time values even when the host audio subsystem is not reporting // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). - decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + decoupledTrack = new DecouplingClock(); if (applyOffsets) { // Audio timings in general with newer BASS versions don't match stable. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new OffsetCorrectionClock(decoupledClock, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; + platformOffsetClock = new OffsetCorrectionClock(decoupledTrack, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; // User global offset (set in settings) should also be applied. userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock, ExternalPauseFrequencyAdjust); @@ -90,7 +88,7 @@ public FramedBeatmapClock(bool applyOffsets = false) } else { - finalClockSource = decoupledClock; + finalClockSource = new InterpolatingFramedClock(decoupledTrack); } } @@ -120,13 +118,12 @@ protected override void Update() { base.Update(); - if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < decoupledClock.CurrentTime - 100) + // TODO: necessary? + if (Source.CurrentTime < decoupledTrack.CurrentTime - 100) { // InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime. // See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93 // This is not always the case here when doing large seeks. - // (Of note, this is not an issue if the source is adjustable, as the source is seeked to be in time by DecoupleableInterpolatingFramedClock). - // Rather than trying to get around this by fixing the framework clock stack, let's work around it for now. Seek(Source.CurrentTime); } else @@ -156,43 +153,43 @@ public double TotalAppliedOffset public void ChangeSource(IClock? source) { Track = source as Track ?? new TrackVirtual(60000); - decoupledClock.ChangeSource(source); + decoupledTrack.ChangeSource(Track); } - public IClock? Source => decoupledClock.Source; + public IClock Source => decoupledTrack.Source; public void Reset() { - decoupledClock.Reset(); + decoupledTrack.Reset(); finalClockSource.ProcessFrame(); } public void Start() { - decoupledClock.Start(); + decoupledTrack.Start(); finalClockSource.ProcessFrame(); } public void Stop() { - decoupledClock.Stop(); + decoupledTrack.Stop(); finalClockSource.ProcessFrame(); } public bool Seek(double position) { - bool success = decoupledClock.Seek(position - TotalAppliedOffset); + bool success = decoupledTrack.Seek(position - TotalAppliedOffset); finalClockSource.ProcessFrame(); return success; } - public void ResetSpeedAdjustments() => decoupledClock.ResetSpeedAdjustments(); + public void ResetSpeedAdjustments() => decoupledTrack.ResetSpeedAdjustments(); public double Rate { - get => decoupledClock.Rate; - set => decoupledClock.Rate = value; + get => decoupledTrack.Rate; + set => decoupledTrack.Rate = value; } #endregion diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6e069297d413..ba034b079f02 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -194,7 +194,8 @@ public void Reset(double? time = null, bool startClock = false) /// private void ensureSourceClockSet() { - if (GameplayClock.Source == null) + // TODO: does this need to exist? + if (GameplayClock.Source != SourceClock) ChangeSource(SourceClock); } From 04e6ec87158a0117f366b4ef8dc8289b18a6e285 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 17:02:31 +0900 Subject: [PATCH 04/28] Fix interpolation not being applied when `applyOffsets` is set --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index acd10cccf341..338a5d305668 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -74,11 +74,13 @@ public FramedBeatmapClock(bool applyOffsets = false) // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). decoupledTrack = new DecouplingClock(); + var interpolatedTrack = new InterpolatingFramedClock(decoupledTrack); + if (applyOffsets) { // Audio timings in general with newer BASS versions don't match stable. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new OffsetCorrectionClock(decoupledTrack, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; + platformOffsetClock = new OffsetCorrectionClock(interpolatedTrack, ExternalPauseFrequencyAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; // User global offset (set in settings) should also be applied. userGlobalOffsetClock = new OffsetCorrectionClock(platformOffsetClock, ExternalPauseFrequencyAdjust); @@ -88,7 +90,7 @@ public FramedBeatmapClock(bool applyOffsets = false) } else { - finalClockSource = new InterpolatingFramedClock(decoupledTrack); + finalClockSource = new InterpolatingFramedClock(interpolatedTrack); } } From 21a2e27e5fff1adfdb3ac8eae52804919ac987a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 18:39:55 +0900 Subject: [PATCH 05/28] Simplify some pieces of `FramedBeatmapClock` --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 338a5d305668..7a1252845351 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -28,16 +28,6 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab { private readonly bool applyOffsets; - /// - /// The length of the underlying beatmap track. Will default to 60 seconds if unavailable. - /// - public double TrackLength => Track.Length; - - /// - /// The underlying beatmap track, if available. - /// - public Track Track { get; private set; } = new TrackVirtual(60000); - /// /// The total frequency adjustment from pause transforms. Should eventually be handled in a better way. /// @@ -154,8 +144,7 @@ public double TotalAppliedOffset public void ChangeSource(IClock? source) { - Track = source as Track ?? new TrackVirtual(60000); - decoupledTrack.ChangeSource(Track); + decoupledTrack.ChangeSource(source as Track ?? new TrackVirtual(60000)); } public IClock Source => decoupledTrack.Source; From 5f634f2812b42c6e6d404b19c2dc015db80b3575 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 18:45:34 +0900 Subject: [PATCH 06/28] Remove unnecessary encapsulation workaround The new implementation of `DecouplingClock` will not mutate the underlying clock in any way (unless attempting to start it when approaching from a negative time value). This should be quite safe as a result. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 5 +---- osu.Game/OsuGameBase.cs | 11 +---------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 7a1252845351..cd349e6c2943 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -142,10 +142,7 @@ public double TotalAppliedOffset #region Delegation of IAdjustableClock / ISourceChangeableClock to decoupled clock. - public void ChangeSource(IClock? source) - { - decoupledTrack.ChangeSource(source as Track ?? new TrackVirtual(60000)); - } + public void ChangeSource(IClock? source) => decoupledTrack.ChangeSource(source); public IClock Source => decoupledTrack.Source; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 75b46a0a4ddc..c946362124d7 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -441,16 +441,7 @@ private void addFilesWarning() } } - private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) - { - // FramedBeatmapClock uses a decoupled clock internally which will mutate the source if it is an `IAdjustableClock`. - // We don't want this for now, as the intention of beatmapClock is to be a read-only source for beat sync components. - // - // Encapsulating in a FramedClock will avoid any mutations. - var framedClock = new FramedClock(beatmap.Track); - - beatmapClock.ChangeSource(framedClock); - } + private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) => beatmapClock.ChangeSource(beatmap.Track); protected virtual void InitialiseFonts() { From df08c4e1adf701068e9c00dd2e2fd40d248c9bb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 18:49:44 +0900 Subject: [PATCH 07/28] Disable decoupling for `OsuGameBase`'s beatmap implementation This avoids it ever mutating the underlying track (aka attempting to start it). Resolves the one caveat mentioned in aeef92fa710648d4a00edc523e13c17ac6104125. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 8 ++++---- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 2 +- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index cd349e6c2943..d738333a9870 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -56,14 +56,14 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab public bool IsRewinding { get; private set; } - public FramedBeatmapClock(bool applyOffsets = false) + public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling) { this.applyOffsets = applyOffsets; - // A decoupled clock is used to ensure precise time values even when the host audio subsystem is not reporting - // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). - decoupledTrack = new DecouplingClock(); + decoupledTrack = new DecouplingClock { AllowDecoupling = requireDecoupling }; + // An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting + // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). var interpolatedTrack = new InterpolatingFramedClock(decoupledTrack); if (applyOffsets) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c946362124d7..1f46eb0c0d74 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -215,7 +215,7 @@ public virtual string Version /// For now, this is used as a source specifically for beat synced components. /// Going forward, it could potentially be used as the single source-of-truth for beatmap timing. /// - private readonly FramedBeatmapClock beatmapClock = new FramedBeatmapClock(true); + private readonly FramedBeatmapClock beatmapClock = new FramedBeatmapClock(applyOffsets: true, requireDecoupling: false); protected override Container Content => content; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 108552b61c2a..d4de1bae5fc8 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -54,7 +54,7 @@ public EditorClock(IBeatmap beatmap = null, BindableBeatDivisor beatDivisor = nu this.beatDivisor = beatDivisor ?? new BindableBeatDivisor(); - underlyingClock = new FramedBeatmapClock(applyOffsets: true); + underlyingClock = new FramedBeatmapClock(applyOffsets: true, requireDecoupling: true); AddInternal(underlyingClock); } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ba034b079f02..2a27d3304708 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -68,7 +68,7 @@ public GameplayClockContainer(IClock sourceClock, bool applyOffsets = false) InternalChildren = new Drawable[] { - GameplayClock = new FramedBeatmapClock(applyOffsets), + GameplayClock = new FramedBeatmapClock(applyOffsets, requireDecoupling: true), Content }; } From a451ab75dd4a72dcce21428ffffb27f4cf26cdc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 19:10:14 +0900 Subject: [PATCH 08/28] Remove hopefully-unnecessary workaround It was causing issues with the new implementation. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index d738333a9870..c83874ded3f5 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; @@ -110,16 +109,7 @@ protected override void Update() { base.Update(); - // TODO: necessary? - if (Source.CurrentTime < decoupledTrack.CurrentTime - 100) - { - // InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime. - // See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93 - // This is not always the case here when doing large seeks. - Seek(Source.CurrentTime); - } - else - finalClockSource.ProcessFrame(); + finalClockSource.ProcessFrame(); if (Clock.ElapsedFrameTime != 0) IsRewinding = Clock.ElapsedFrameTime < 0; From a1e298930c6a0e697f9f3f2cf80be6d2c3b3704c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 19:39:15 +0900 Subject: [PATCH 09/28] Remove second hopefully-unnecessary workaround --- osu.Game/Screens/Play/GameplayClockContainer.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2a27d3304708..4c3aa301a624 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -160,21 +160,6 @@ public void Reset(double? time = null, bool startClock = false) Seek(StartTime); - // This is a workaround for the fact that DecoupleableInterpolatingFramedClock doesn't seek the source - // if the source is not IsRunning. (see https://github.com/ppy/osu-framework/blob/2102638056dfcf85d21b4d85266d53b5dd018767/osu.Framework/Timing/DecoupleableInterpolatingFramedClock.cs#L209-L210) - // I hope to remove this once we knock some sense into clocks in general. - // - // Without this seek, the multiplayer spectator start sequence breaks: - // - Individual clients' clocks are never updated to their expected time - // - The sync manager thinks they are running behind - // - Gameplay doesn't start when it should (until a timeout occurs because nothing is happening for 10+ seconds) - // - // In addition, we use `CurrentTime` for this seek instead of `StartTime` as the above seek may have applied inherent - // offsets which need to be accounted for (ie. FramedBeatmapClock.TotalAppliedOffset). - // - // See https://github.com/ppy/osu/pull/24451/files/87fee001c786b29db34063ef3350e9a9f024d3ab#diff-28ca02979641e2d98a15fe5d5e806f56acf60ac100258a059fa72503b6cc54e8. - (SourceClock as IAdjustableClock)?.Seek(CurrentTime); - if (!wasPaused || startClock) Start(); } From 0200b63fd385eb8ed42b480d86d09e13e5b152e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Sep 2023 19:53:55 +0900 Subject: [PATCH 10/28] Add note about beatmap offset not being reapplied correctly on `ChangeSource` --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index c83874ded3f5..a3e1b9ae24aa 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -95,6 +95,7 @@ protected override void LoadComplete() userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.BindValueChanged(offset => userGlobalOffsetClock.Offset = offset.NewValue, true); + // TODO: this doesn't update when using ChangeSource() to change beatmap. beatmapOffsetSubscription = realm.SubscribeToPropertyChanged( r => r.Find(beatmap.Value.BeatmapInfo.ID)?.UserSettings, settings => settings.Offset, From 59d6e6751255558aab0cbdba7de97105437dd8b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 04:10:57 +0900 Subject: [PATCH 11/28] Add missing `TestManualClock.Reset` implementation for safe measure --- osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 1d568a9dc2f8..63ebe5187fdd 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -188,6 +188,8 @@ public bool Seek(double position) public void Reset() { + IsRunning = false; + CurrentTime = 0; } public void ResetSpeedAdjustments() From 586311d508c28ffb486de78e5e490639714b746a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 13:16:51 +0900 Subject: [PATCH 12/28] Fix souce clock not always being transferred to `FramedBeatmapClock` in time --- .../Spectate/MultiSpectatorPlayer.cs | 2 +- .../Screens/Play/GameplayClockContainer.cs | 29 ++----------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 930bea4497e5..18a890c2b8fe 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -49,7 +49,7 @@ private void load(CancellationToken cancellationToken) protected override void Update() { // The player clock's running state is controlled externally, but the local pausing state needs to be updated to start/stop gameplay. - if (GameplayClockContainer.SourceClock.IsRunning) + if (GameplayClockContainer.IsRunning) GameplayClockContainer.Start(); else GameplayClockContainer.Stop(); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 4c3aa301a624..38e8fe0dc7e4 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -23,11 +23,6 @@ public partial class GameplayClockContainer : Container, IAdjustableClock, IGame public bool IsRewinding => GameplayClock.IsRewinding; - /// - /// The source clock. Should generally not be used for any timekeeping purposes. - /// - public IClock SourceClock { get; private set; } - /// /// Invoked when a seek has been performed via /// @@ -62,8 +57,6 @@ public partial class GameplayClockContainer : Container, IAdjustableClock, IGame /// Whether to apply platform, user and beatmap offsets to the mix. public GameplayClockContainer(IClock sourceClock, bool applyOffsets = false) { - SourceClock = sourceClock; - RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] @@ -71,6 +64,8 @@ public GameplayClockContainer(IClock sourceClock, bool applyOffsets = false) GameplayClock = new FramedBeatmapClock(applyOffsets, requireDecoupling: true), Content }; + + GameplayClock.ChangeSource(sourceClock); } /// @@ -83,8 +78,6 @@ public void Start() isPaused.Value = false; - ensureSourceClockSet(); - PrepareStart(); // The case which caused this to be added is FrameStabilityContainer, which manages its own current and elapsed time. @@ -153,8 +146,6 @@ public void Reset(double? time = null, bool startClock = false) Stop(); - ensureSourceClockSet(); - if (time != null) StartTime = time.Value; @@ -168,21 +159,7 @@ public void Reset(double? time = null, bool startClock = false) /// Changes the source clock. /// /// The new source. - protected void ChangeSource(IClock sourceClock) => GameplayClock.ChangeSource(SourceClock = sourceClock); - - /// - /// Ensures that the is set to , if it hasn't been given a source yet. - /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, - /// but not the actual source clock. - /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, - /// but it is not yet set on the adjustable source there. - /// - private void ensureSourceClockSet() - { - // TODO: does this need to exist? - if (GameplayClock.Source != SourceClock) - ChangeSource(SourceClock); - } + protected void ChangeSource(IClock sourceClock) => GameplayClock.ChangeSource(sourceClock); #region IAdjustableClock From bf08fbe1962b613597fad898a5c75ee0baa9530f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 13:29:46 +0900 Subject: [PATCH 13/28] Set source directly in `FramedBeatmapClock` ctor This isn't required, but avoids creating a temporary `StopwatchClock` and generally just makes debug easier with less state changes. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 4 ++-- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index a3e1b9ae24aa..495a30b87cf8 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -55,11 +55,11 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab public bool IsRewinding { get; private set; } - public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling) + public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling, IClock? source = null) { this.applyOffsets = applyOffsets; - decoupledTrack = new DecouplingClock { AllowDecoupling = requireDecoupling }; + decoupledTrack = new DecouplingClock(source) { AllowDecoupling = requireDecoupling }; // An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 38e8fe0dc7e4..ace4778ac7ec 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -61,11 +61,9 @@ public GameplayClockContainer(IClock sourceClock, bool applyOffsets = false) InternalChildren = new Drawable[] { - GameplayClock = new FramedBeatmapClock(applyOffsets, requireDecoupling: true), + GameplayClock = new FramedBeatmapClock(applyOffsets, requireDecoupling: true, sourceClock), Content }; - - GameplayClock.ChangeSource(sourceClock); } /// From 3cb928fe6f4d09e43bd0336d22e53d90dde4c4cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 14:06:05 +0900 Subject: [PATCH 14/28] Add note about test not calling `ProcessFrame` --- osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index 63ebe5187fdd..d601f187d861 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -15,6 +15,9 @@ namespace osu.Game.Tests.OnlinePlay { + // NOTE: This test scene never calls ProcessFrame on clocks. + // The current tests are fine without this as they are testing very static scenarios, but it's worth knowing + // if adding further tests to this class. [HeadlessTest] public partial class TestSceneCatchUpSyncManager : OsuTestScene { From 6629a47ed38daff0356122f61d8676037e7e4319 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 14:10:26 +0900 Subject: [PATCH 15/28] Fix `FramedBeatmapClock`'s source not being processed --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 495a30b87cf8..5fdabaab463f 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -110,6 +110,9 @@ protected override void Update() { base.Update(); + if (decoupledTrack.Source is IFrameBasedClock framedClock) + framedClock.ProcessFrame(); + finalClockSource.ProcessFrame(); if (Clock.ElapsedFrameTime != 0) From 8367bb6bee4ea92dc97771eb343f056cdd60d45d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 14:26:33 +0900 Subject: [PATCH 16/28] Don't apply decoupling to `SpectatorPlayerClock`s See inline comment for reasoning. It's a bit complicated. --- .../TestSceneDrumSampleTriggerSource.cs | 2 +- osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs | 2 +- osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 2 +- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs | 5 ++++- osu.Game/Screens/Play/GameplayClockContainer.cs | 5 +++-- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 2 +- 9 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs index ced2e4b98cf1..6c925f566bf2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs @@ -38,7 +38,7 @@ public partial class TestSceneDrumSampleTriggerSource : OsuTestScene [SetUp] public void SetUp() => Schedule(() => { - gameplayClock = new GameplayClockContainer(manualClock) + gameplayClock = new GameplayClockContainer(manualClock, false, false) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index d67a3cb82485..4b3163ad8994 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -25,7 +25,7 @@ public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRat private partial class TestGameplayClockContainer : GameplayClockContainer { public TestGameplayClockContainer(IFrameBasedClock underlyingClock) - : base(underlyingClock) + : base(underlyingClock, false, false) { AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); } diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs index d601f187d861..7b0b21189927 100644 --- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs +++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs @@ -31,7 +31,7 @@ public partial class TestSceneCatchUpSyncManager : OsuTestScene [SetUp] public void Setup() { - syncManager = new SpectatorSyncManager(master = new GameplayClockContainer(new TestManualClock())); + syncManager = new SpectatorSyncManager(master = new GameplayClockContainer(new TestManualClock(), false, false)); player1 = syncManager.CreateManagedClock(); player2 = syncManager.CreateManagedClock(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index aa893429261b..22600172ddb5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -41,7 +41,7 @@ public partial class TestSceneHUDOverlay : OsuManualInputManagerTestScene private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached(typeof(IGameplayClock))] - private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock()); + private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock(), false, false); // best way to check without exposing. private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 81ce088b9d58..f1e9c831a6e1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -32,7 +32,7 @@ public partial class TestSceneSkinEditorMultipleSkins : SkinnableTestScene private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached(typeof(IGameplayClock))] - private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock()); + private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock(), false, false); [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index a8580ebf770c..782cbb0a852a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -39,7 +39,7 @@ public partial class TestSceneSkinnableHUDOverlay : SkinnableTestScene private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached(typeof(IGameplayClock))] - private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock()); + private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock(), false, false); private IEnumerable hudOverlays => CreatedDrawables.OfType(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 18a890c2b8fe..8526e11e125a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -67,7 +67,10 @@ protected override void UpdateAfterChildren() protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) { - var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); + // Importantly, we don't want to apply decoupling because SpectatorPlayerClock updates its IsRunning directly. + // If we applied decoupling, this state change wouldn't actually cause the clock to stop. + // TODO: Can we just use Start/Stop rather than this workaround, now that DecouplingClock is more sane? + var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock, applyOffsets: false, requireDecoupling: false); clockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); return gameplayClockContainer; } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ace4778ac7ec..e6a38a994652 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -55,13 +55,14 @@ public partial class GameplayClockContainer : Container, IAdjustableClock, IGame /// /// The source used for timing. /// Whether to apply platform, user and beatmap offsets to the mix. - public GameplayClockContainer(IClock sourceClock, bool applyOffsets = false) + /// Whether decoupling logic should be applied on the source clock. + public GameplayClockContainer(IClock sourceClock, bool applyOffsets, bool requireDecoupling) { RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { - GameplayClock = new FramedBeatmapClock(applyOffsets, requireDecoupling: true, sourceClock), + GameplayClock = new FramedBeatmapClock(applyOffsets, requireDecoupling, sourceClock), Content }; } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 489a4ef8b3b5..9ecabd9c6b97 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -65,7 +65,7 @@ public partial class MasterGameplayClockContainer : GameplayClockContainer, IBea /// 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. public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime) - : base(beatmap.Track, true) + : base(beatmap.Track, applyOffsets: true, requireDecoupling: true) { this.beatmap = beatmap; this.skipTargetTime = skipTargetTime; From a3e4d1993331bd9dc27d482c13def9c6b7019126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 14:32:31 +0900 Subject: [PATCH 17/28] Update tests which were not using an `IAdjustableClock` as `GameplayClockContainer` source --- osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index 4b3163ad8994..65113add2dc5 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -16,7 +17,7 @@ public partial class GameplayClockContainerTest [TestCase(1)] public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRate) { - var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); + var framedClock = new TrackVirtual(60000) { Frequency = { Value = underlyingClockRate } }; var gameplayClock = new TestGameplayClockContainer(framedClock); Assert.That(gameplayClock.GetTrueGameplayRate(), Is.EqualTo(2)); @@ -24,7 +25,7 @@ public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRat private partial class TestGameplayClockContainer : GameplayClockContainer { - public TestGameplayClockContainer(IFrameBasedClock underlyingClock) + public TestGameplayClockContainer(IClock underlyingClock) : base(underlyingClock, false, false) { AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); From c40bd741383107ec806680f4346468e224ea3dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 14:40:10 +0900 Subject: [PATCH 18/28] Update usages of `GameplayClockContainer` not using an adjustable source --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 4 ++-- .../Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs | 4 ++-- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 22600172ddb5..f8226eb21de7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -6,11 +6,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; @@ -41,7 +41,7 @@ public partial class TestSceneHUDOverlay : OsuManualInputManagerTestScene private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached(typeof(IGameplayClock))] - private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock(), false, false); + private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false); // best way to check without exposing. private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index f1e9c831a6e1..656873e9edea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -4,10 +4,10 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -32,7 +32,7 @@ public partial class TestSceneSkinEditorMultipleSkins : SkinnableTestScene private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached(typeof(IGameplayClock))] - private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock(), false, false); + private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false); [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 782cbb0a852a..4cb0d5c0ff32 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -8,11 +8,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; @@ -39,7 +39,7 @@ public partial class TestSceneSkinnableHUDOverlay : SkinnableTestScene private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached(typeof(IGameplayClock))] - private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock(), false, false); + private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false); private IEnumerable hudOverlays => CreatedDrawables.OfType(); From faa0481fc6291226cf0c836f2f33c7f0f14831a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 16:11:02 +0900 Subject: [PATCH 19/28] Fix editor operating directly on track rather than decoupled clock --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1cdca5754df5..75d9820680d5 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -859,7 +859,7 @@ protected void SetPreviewPointToCurrentTime() private void resetTrack(bool seekToStart = false) { - Beatmap.Value.Track.Stop(); + clock.Stop(); if (seekToStart) { From 251a85db435b4eb7958f4fb7dc444639b580b977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 16:18:43 +0900 Subject: [PATCH 20/28] Fix `StopUsingBeatmapClock` not transferring time and running state --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 9ecabd9c6b97..70d9ecd3e7e3 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -187,7 +187,13 @@ public void Skip() public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - ChangeSource(new TrackVirtual(beatmap.Track.Length)); + + var virtualTrack = new TrackVirtual(beatmap.Track.Length); + virtualTrack.Seek(CurrentTime); + if (IsRunning) + virtualTrack.Start(); + ChangeSource(virtualTrack); + addSourceClockAdjustments(); } From d019cb5167acdd6e77190a11993c4e3ddf0a1b02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Sep 2023 22:02:18 +0900 Subject: [PATCH 21/28] Update in line with framed clock changes --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 7 ++----- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 5fdabaab463f..316d1f8a3400 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -42,7 +42,7 @@ public partial class FramedBeatmapClock : Component, IFrameBasedClock, IAdjustab private IDisposable? beatmapOffsetSubscription; - private readonly DecouplingClock decoupledTrack; + private readonly DecouplingFramedClock decoupledTrack; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -59,7 +59,7 @@ public FramedBeatmapClock(bool applyOffsets, bool requireDecoupling, IClock? sou { this.applyOffsets = applyOffsets; - decoupledTrack = new DecouplingClock(source) { AllowDecoupling = requireDecoupling }; + decoupledTrack = new DecouplingFramedClock(source) { AllowDecoupling = requireDecoupling }; // An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting // high precision times (on windows there's generally only 5-10ms reporting intervals, as an example). @@ -110,9 +110,6 @@ protected override void Update() { base.Update(); - if (decoupledTrack.Source is IFrameBasedClock framedClock) - framedClock.ProcessFrame(); - finalClockSource.ProcessFrame(); if (Clock.ElapsedFrameTime != 0) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 808680b9e559..aab3afcd248f 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -58,7 +58,7 @@ protected override void LogoArriving(OsuLogo logo, bool resuming) { PrepareMenuLoad(); - var decouplingClock = new DecouplingClock(UsingThemedIntro ? Track : null); + var decouplingClock = new DecouplingFramedClock(UsingThemedIntro ? Track : null); LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground()) { From a186d97edf53f2152bd52b852421a789c58cfabf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Oct 2023 16:57:54 +0900 Subject: [PATCH 22/28] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 20b0f220a3c6..85d5e66d44a3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - +