From 73fb1851328c416c712937bb1be401ba958af47b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:00:50 +0900 Subject: [PATCH 01/22] Change the way ShouldProcessClock is specified in line with framework changes --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 1 + osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 37ca0c021b..3cbe37fed2 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { // as we are currently very dependent on having a running clock, let's make our own clock for the time being. Clock = new FramedClock(); + ShouldProcessClock = true; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index f465d0e202..5053d582e2 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -91,8 +91,6 @@ namespace osu.Game.Rulesets.UI #region Clock control - protected override bool ShouldProcessClock => false; // We handle processing the clock ourselves - private ManualClock clock; private IFrameBasedClock parentClock; From 9c0dfb7c8c3b4a5667e408904897f460a8d57093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:01:30 +0900 Subject: [PATCH 02/22] Avoid creating an extra framed clock in SkipButton --- osu.Game/Screens/Play/Player.cs | 5 ++++- osu.Game/Screens/Play/SkipButton.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4954618ef9..5ea070d4ff 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -185,9 +185,12 @@ namespace osu.Game.Screens.Play Clock = offsetClock, Child = RulesetContainer, }, - new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock) + new SkipButton(firstObjectTime) { + SeekableClock = decoupledClock, + FramedClock = offsetClock, + }, Anchor = Anchor.Centre, Origin = Anchor.Centre }, diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index f67a9b801e..463dcc1644 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -24,7 +24,9 @@ namespace osu.Game.Screens.Play public class SkipButton : OverlayContainer, IKeyBindingHandler { private readonly double startTime; - public IAdjustableClock AudioClock; + + public IAdjustableClock SeekableClock; + public IFrameBasedClock FramedClock; private Button button; private Box remainingTimeBox; @@ -60,8 +62,8 @@ namespace osu.Game.Screens.Play { var baseClock = Clock; - if (AudioClock != null) - Clock = new FramedClock(AudioClock) { ProcessSourceClockFrames = false }; + if (FramedClock != null) + Clock = FramedClock; Children = new Drawable[] { @@ -109,7 +111,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => AudioClock?.Seek(startTime - skip_required_cutoff - fade_time); + button.Action = () => SeekableClock?.Seek(startTime - skip_required_cutoff - fade_time); displayTime = Time.Current; From 0635ae2293180d986f435570bd41f672dc906c9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:01:55 +0900 Subject: [PATCH 03/22] Include missing offset --- 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 5ea070d4ff..a0310a93ed 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -198,7 +198,7 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Clock = decoupledClock, + Clock = offsetClock, Breaks = beatmap.Breaks } } From d4f1723ae63a6616cb3094e8ca9105e6af4ba2b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:02:48 +0900 Subject: [PATCH 04/22] Remove unnecessary secondary argument from HUDOverlay --- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index e68a17f014..231fcfb3e2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, DecoupleableInterpolatingFramedClock decoupledClock, WorkingBeatmap working, IAdjustableClock adjustableSourceClock) + public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, IAdjustableClock seekableClock, WorkingBeatmap working) { RelativeSizeAxes = Axes.Both; @@ -66,13 +66,13 @@ namespace osu.Game.Screens.Play BindRulesetContainer(rulesetContainer); Progress.Objects = rulesetContainer.Objects; - Progress.AudioClock = decoupledClock; + Progress.AudioClock = seekableClock; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded; - Progress.OnSeek = pos => decoupledClock.Seek(pos); + Progress.OnSeek = pos => seekableClock.Seek(pos); ModDisplay.Current.BindTo(working.Mods); - PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock; + PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = seekableClock; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a0310a93ed..a4fdc8a053 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -185,12 +185,13 @@ namespace osu.Game.Screens.Play Clock = offsetClock, Child = RulesetContainer, }, - hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock) new SkipButton(firstObjectTime) { SeekableClock = decoupledClock, FramedClock = offsetClock, }, + hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working) + { Anchor = Anchor.Centre, Origin = Anchor.Centre }, From fee258f2f210c3a31381c4cba89af480d5484cff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:02:57 +0900 Subject: [PATCH 05/22] Rework PauseContainer to better pause --- osu.Game/Screens/Play/PauseContainer.cs | 54 ++++++++++++------------- osu.Game/Screens/Play/Player.cs | 24 +++++------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 669bcd600c..b2f2fe2d4c 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -44,14 +44,21 @@ namespace osu.Game.Screens.Play public Action OnResume; public Action OnPause; - public IAdjustableClock AudioClock; - public FramedClock FramedClock; + public readonly IAdjustableClock SeekableClock; + public readonly FramedClock FramedClock; - public PauseContainer() + public PauseContainer(FramedClock framedClock, IAdjustableClock seekableClock) { + FramedClock = framedClock; + SeekableClock = seekableClock; + RelativeSizeAxes = Axes.Both; - AddInternal(content = new Container { RelativeSizeAxes = Axes.Both }); + AddInternal(content = new Container + { + Clock = FramedClock, + RelativeSizeAxes = Axes.Both + }); AddInternal(pauseOverlay = new PauseOverlay { @@ -65,47 +72,37 @@ namespace osu.Game.Screens.Play }); } - public void Pause(bool force = false) + public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. { if (!CanPause && !force) return; if (IsPaused) return; - // stop the decoupled clock (stops the audio eventually) - AudioClock.Stop(); - - // stop processing updatess on the offset clock (instantly freezes time for all our components) - FramedClock.ProcessSourceClockFrames = false; - + // stop the seekable clock (stops the audio eventually) + SeekableClock.Stop(); IsPaused = true; - // we need to do a final check after all of our children have processed up to the paused clock time. - // this is to cover cases where, for instance, the player fails in the current processing frame. - Schedule(() => - { - if (!CanPause) return; + OnPause?.Invoke(); + pauseOverlay.Show(); - lastPauseActionTime = Time.Current; - - OnPause?.Invoke(); - pauseOverlay.Show(); - }); - } + lastPauseActionTime = Time.Current; + }); public void Resume() { if (!IsPaused) return; IsPaused = false; - FramedClock.ProcessSourceClockFrames = true; - + IsResuming = false; lastPauseActionTime = Time.Current; - OnResume?.Invoke(); + // seek back to the time of the framed clock. + // this accounts for the audio clock potentially taking time to enter a completely stopped state. + SeekableClock.Seek(FramedClock.CurrentTime); + SeekableClock.Start(); + OnResume?.Invoke(); pauseOverlay.Hide(); - AudioClock.Start(); - IsResuming = false; } private OsuGameBase game; @@ -122,6 +119,9 @@ namespace osu.Game.Screens.Play if (!game.IsActive && CanPause) Pause(); + if (!IsPaused) + FramedClock.ProcessFrame(); + base.Update(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a4fdc8a053..e3ef0d333c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -158,16 +158,8 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - storyboardContainer = new Container + pauseContainer = new PauseContainer(offsetClock, decoupledClock) { - RelativeSizeAxes = Axes.Both, - Clock = offsetClock, - Alpha = 0, - }, - pauseContainer = new PauseContainer - { - AudioClock = decoupledClock, - FramedClock = offsetClock, OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, @@ -183,7 +175,15 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both, Clock = offsetClock, - Child = RulesetContainer, + Children = new[] + { + storyboardContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + RulesetContainer, + } }, new SkipButton(firstObjectTime) { @@ -338,7 +338,9 @@ namespace osu.Game.Screens.Play this.Delay(750).Schedule(() => { if (!pauseContainer.IsPaused) + { decoupledClock.Start(); + } }); }); }); @@ -365,9 +367,7 @@ namespace osu.Game.Screens.Play } if (loadedSuccessfully) - { pauseContainer?.Pause(); - } return true; } From 3d52ead213fe795cfaf0f7947a0ea2e6855a7156 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:05:21 +0900 Subject: [PATCH 06/22] Rename sourceClock --- osu.Game/Screens/Play/Player.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e3ef0d333c..ce2ae08bed 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -54,8 +54,13 @@ namespace osu.Game.Screens.Play public CursorContainer Cursor => RulesetContainer.Cursor; public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value; - private IAdjustableClock adjustableSourceClock; + private IAdjustableClock sourceClock; + + /// + /// The final usable gameplay clock with user-set offsets applied. + /// private FramedOffsetClock offsetClock; + private DecoupleableInterpolatingFramedClock decoupledClock; private PauseContainer pauseContainer; @@ -138,7 +143,7 @@ namespace osu.Game.Screens.Play return; } - adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); + sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var firstObjectTime = RulesetContainer.Objects.First().StartTime; @@ -236,11 +241,11 @@ namespace osu.Game.Screens.Play private void applyRateFromMods() { - if (adjustableSourceClock == null) return; + if (sourceClock == null) return; - adjustableSourceClock.Rate = 1; + sourceClock.Rate = 1; foreach (var mod in Beatmap.Value.Mods.Value.OfType()) - mod.ApplyToClock(adjustableSourceClock); + mod.ApplyToClock(sourceClock); } private void initializeStoryboard(bool asyncLoad) @@ -328,11 +333,11 @@ namespace osu.Game.Screens.Play Task.Run(() => { - adjustableSourceClock.Reset(); + sourceClock.Reset(); Schedule(() => { - decoupledClock.ChangeSource(adjustableSourceClock); + decoupledClock.ChangeSource(sourceClock); applyRateFromMods(); this.Delay(750).Schedule(() => From 37d2a2c3ccbe280e92c41777ea2673ef93461126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:10:12 +0900 Subject: [PATCH 07/22] Rename clock types to match across classes --- osu.Game/Screens/Play/PauseContainer.cs | 12 ++++++------ osu.Game/Screens/Play/Player.cs | 23 +++++++++++++---------- osu.Game/Screens/Play/SkipButton.cs | 4 ++-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index b2f2fe2d4c..8827b437ba 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -44,13 +44,13 @@ namespace osu.Game.Screens.Play public Action OnResume; public Action OnPause; - public readonly IAdjustableClock SeekableClock; + public readonly IAdjustableClock AdjustableClock; public readonly FramedClock FramedClock; - public PauseContainer(FramedClock framedClock, IAdjustableClock seekableClock) + public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock) { FramedClock = framedClock; - SeekableClock = seekableClock; + AdjustableClock = adjustableClock; RelativeSizeAxes = Axes.Both; @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play if (IsPaused) return; // stop the seekable clock (stops the audio eventually) - SeekableClock.Stop(); + AdjustableClock.Stop(); IsPaused = true; OnPause?.Invoke(); @@ -98,8 +98,8 @@ namespace osu.Game.Screens.Play // seek back to the time of the framed clock. // this accounts for the audio clock potentially taking time to enter a completely stopped state. - SeekableClock.Seek(FramedClock.CurrentTime); - SeekableClock.Start(); + AdjustableClock.Seek(FramedClock.CurrentTime); + AdjustableClock.Start(); OnResume?.Invoke(); pauseOverlay.Hide(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ce2ae08bed..a2e044b800 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -61,7 +61,10 @@ namespace osu.Game.Screens.Play /// private FramedOffsetClock offsetClock; - private DecoupleableInterpolatingFramedClock decoupledClock; + /// + /// The decoupled clock used for gameplay. Should be used for seeks and clock control. + /// + private DecoupleableInterpolatingFramedClock adjustableClock; private PauseContainer pauseContainer; @@ -144,16 +147,16 @@ namespace osu.Game.Screens.Play } sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); - decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var firstObjectTime = RulesetContainer.Objects.First().StartTime; - decoupledClock.Seek(AllowLeadIn + adjustableClock.Seek(AllowLeadIn ? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)) : firstObjectTime); - decoupledClock.ProcessFrame(); + adjustableClock.ProcessFrame(); - offsetClock = new FramedOffsetClock(decoupledClock); + offsetClock = new FramedOffsetClock(adjustableClock); userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.ValueChanged += v => offsetClock.Offset = v; @@ -163,7 +166,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - pauseContainer = new PauseContainer(offsetClock, decoupledClock) + pauseContainer = new PauseContainer(offsetClock, adjustableClock) { OnRetry = Restart, OnQuit = Exit, @@ -192,7 +195,7 @@ namespace osu.Game.Screens.Play }, new SkipButton(firstObjectTime) { - SeekableClock = decoupledClock, + AdjustableClock = adjustableClock, FramedClock = offsetClock, }, hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working) @@ -304,7 +307,7 @@ namespace osu.Game.Screens.Play if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - decoupledClock.Stop(); + adjustableClock.Stop(); HasFailed = true; failOverlay.Retries = RestartCount; @@ -337,14 +340,14 @@ namespace osu.Game.Screens.Play Schedule(() => { - decoupledClock.ChangeSource(sourceClock); + adjustableClock.ChangeSource(sourceClock); applyRateFromMods(); this.Delay(750).Schedule(() => { if (!pauseContainer.IsPaused) { - decoupledClock.Start(); + adjustableClock.Start(); } }); }); diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index 463dcc1644..b7e075d893 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public IAdjustableClock SeekableClock; + public IAdjustableClock AdjustableClock; public IFrameBasedClock FramedClock; private Button button; @@ -111,7 +111,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => SeekableClock?.Seek(startTime - skip_required_cutoff - fade_time); + button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time); displayTime = Time.Current; From 8e78a7b1143a2dc898adf645a691200bf7bdb05c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 16:10:21 +0900 Subject: [PATCH 08/22] Fix HUD using incorrect clock for time display --- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 231fcfb3e2..b0fbde74d2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, IAdjustableClock seekableClock, WorkingBeatmap working) + public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock) { RelativeSizeAxes = Axes.Both; @@ -66,13 +66,13 @@ namespace osu.Game.Screens.Play BindRulesetContainer(rulesetContainer); Progress.Objects = rulesetContainer.Objects; - Progress.AudioClock = seekableClock; + Progress.AudioClock = offsetClock; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded; - Progress.OnSeek = pos => seekableClock.Seek(pos); + Progress.OnSeek = pos => adjustableClock.Seek(pos); ModDisplay.Current.BindTo(working.Mods); - PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = seekableClock; + PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a2e044b800..938a67190c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -198,7 +198,7 @@ namespace osu.Game.Screens.Play AdjustableClock = adjustableClock, FramedClock = offsetClock, }, - hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working) + hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { Anchor = Anchor.Centre, Origin = Anchor.Centre From cff17f18648f273c71f0339cf73d81b0c508e64f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Feb 2018 17:23:12 +0900 Subject: [PATCH 09/22] Update in line with inverse ShoudProcessClock default --- .../UI/Cursor/CursorTrail.cs | 1 - osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 + osu.Game/Screens/Play/PauseContainer.cs | 19 ++++++++++--------- osu.Game/Screens/Play/Player.cs | 2 ++ osu.Game/Screens/Play/SkipButton.cs | 3 +++ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 3cbe37fed2..37ca0c021b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { // as we are currently very dependent on having a running clock, let's make our own clock for the time being. Clock = new FramedClock(); - ShouldProcessClock = true; RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 5053d582e2..b00bbf1e3a 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -101,6 +101,7 @@ namespace osu.Game.Rulesets.UI //our clock will now be our parent's clock, but we want to replace this to allow manual control. parentClock = Clock; + ShouldProcessClock = false; Clock = new FramedClock(clock = new ManualClock { CurrentTime = parentClock.CurrentTime, diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 8827b437ba..220a48e7c5 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -44,19 +44,20 @@ namespace osu.Game.Screens.Play public Action OnResume; public Action OnPause; - public readonly IAdjustableClock AdjustableClock; - public readonly FramedClock FramedClock; + private readonly IAdjustableClock adjustableClock; + private readonly FramedClock framedClock; public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock) { - FramedClock = framedClock; - AdjustableClock = adjustableClock; + this.framedClock = framedClock; + this.adjustableClock = adjustableClock; RelativeSizeAxes = Axes.Both; AddInternal(content = new Container { - Clock = FramedClock, + Clock = this.framedClock, + ShouldProcessClock = false, RelativeSizeAxes = Axes.Both }); @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Play if (IsPaused) return; // stop the seekable clock (stops the audio eventually) - AdjustableClock.Stop(); + adjustableClock.Stop(); IsPaused = true; OnPause?.Invoke(); @@ -98,8 +99,8 @@ namespace osu.Game.Screens.Play // seek back to the time of the framed clock. // this accounts for the audio clock potentially taking time to enter a completely stopped state. - AdjustableClock.Seek(FramedClock.CurrentTime); - AdjustableClock.Start(); + adjustableClock.Seek(framedClock.CurrentTime); + adjustableClock.Start(); OnResume?.Invoke(); pauseOverlay.Hide(); @@ -120,7 +121,7 @@ namespace osu.Game.Screens.Play Pause(); if (!IsPaused) - FramedClock.ProcessFrame(); + framedClock.ProcessFrame(); base.Update(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 938a67190c..a36d7e8e23 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both, Clock = offsetClock, + ShouldProcessClock = false, Children = new[] { storyboardContainer = new Container @@ -208,6 +209,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, Clock = offsetClock, + ShouldProcessClock = false, Breaks = beatmap.Breaks } } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index b7e075d893..e8b43b7c4e 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -63,7 +63,10 @@ namespace osu.Game.Screens.Play var baseClock = Clock; if (FramedClock != null) + { Clock = FramedClock; + ShouldProcessClock = false; + } Children = new Drawable[] { From 01fcf9c813169f0bf2e90480d386aaa739838d18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Feb 2018 21:50:52 +0900 Subject: [PATCH 10/22] Update in line with framework changes --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- osu.Game/Screens/Play/PauseContainer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/SkipButton.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index b00bbf1e3a..3f8a17e23d 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.UI //our clock will now be our parent's clock, but we want to replace this to allow manual control. parentClock = Clock; - ShouldProcessClock = false; + ProcessCustomClock = false; Clock = new FramedClock(clock = new ManualClock { CurrentTime = parentClock.CurrentTime, diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 220a48e7c5..40e734b7df 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play AddInternal(content = new Container { Clock = this.framedClock, - ShouldProcessClock = false, + ProcessCustomClock = false, RelativeSizeAxes = Axes.Both }); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a36d7e8e23..e89d522d90 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both, Clock = offsetClock, - ShouldProcessClock = false, + ProcessCustomClock = false, Children = new[] { storyboardContainer = new Container @@ -209,7 +209,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, Clock = offsetClock, - ShouldProcessClock = false, + ProcessCustomClock = false, Breaks = beatmap.Breaks } } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipButton.cs index e8b43b7c4e..08bb26c72b 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipButton.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play if (FramedClock != null) { Clock = FramedClock; - ShouldProcessClock = false; + ProcessCustomClock = false; } Children = new Drawable[] From f9faf8e3d826610200e5d32a2e8f2c455a528883 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Feb 2018 22:01:52 +0900 Subject: [PATCH 11/22] Localise offset clock usage out of Player as much as possible --- osu.Game/Screens/Play/Player.cs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e89d522d90..cab791f3a5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -56,11 +56,6 @@ namespace osu.Game.Screens.Play private IAdjustableClock sourceClock; - /// - /// The final usable gameplay clock with user-set offsets applied. - /// - private FramedOffsetClock offsetClock; - /// /// The decoupled clock used for gameplay. Should be used for seeks and clock control. /// @@ -156,7 +151,8 @@ namespace osu.Game.Screens.Play adjustableClock.ProcessFrame(); - offsetClock = new FramedOffsetClock(adjustableClock); + // the final usable gameplay clock with user-set offsets applied. + var offsetClock = new FramedOffsetClock(adjustableClock); userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); userAudioOffset.ValueChanged += v => offsetClock.Offset = v; @@ -179,28 +175,23 @@ namespace osu.Game.Screens.Play OnResume = () => hudOverlay.KeyCounter.IsCounting = true, Children = new Drawable[] { - new Container + storyboardContainer = new Container { RelativeSizeAxes = Axes.Both, - Clock = offsetClock, - ProcessCustomClock = false, - Children = new[] - { - storyboardContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - RulesetContainer, - } + Alpha = 0, }, + RulesetContainer, new SkipButton(firstObjectTime) { + Clock = Clock, // skip button doesn't want to use the audio clock directly + ProcessCustomClock = false, AdjustableClock = adjustableClock, FramedClock = offsetClock, }, hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { + Clock = Clock, // hud overlay doesn't want to use the audio clock directly + ProcessCustomClock = false, Anchor = Anchor.Centre, Origin = Anchor.Centre }, @@ -208,7 +199,6 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Clock = offsetClock, ProcessCustomClock = false, Breaks = beatmap.Breaks } From a71dacc58acc4ce372d652809b1726f7ffeaa8c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Mar 2018 21:02:10 +0900 Subject: [PATCH 12/22] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index e8ae207769..71900dc350 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit e8ae207769ec26fb7ddd67a2433514fcda354ecd +Subproject commit 71900dc350bcebbb60d912d4023a1d2a6bbbc3c1 From 26e50043b70a0c13eb769638fcccf08a7600529d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Mar 2018 14:07:15 +0900 Subject: [PATCH 13/22] Fix parallax container during rewinds --- osu.Game/Graphics/Containers/ParallaxContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index f4400b7df2..febe52d775 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Graphics.Containers { Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount; - content.Position = Interpolation.ValueAt(Clock.ElapsedFrameTime, content.Position, offset, 0, 1000, Easing.OutQuint); + content.Position = Interpolation.ValueAt(MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), content.Position, offset, 0, 1000, Easing.OutQuint); content.Scale = new Vector2(1 + ParallaxAmount); } From 659578e8fa8563b58ddda9e632f65ce443dafa70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Mar 2018 01:48:00 +0900 Subject: [PATCH 14/22] Add rewind support for storyboards --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 ++ osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs | 2 ++ osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index aaeaaabd55..9da92d8cb4 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -33,6 +33,8 @@ namespace osu.Game.Storyboards.Drawables } } + public override bool RemoveCompletedTransforms => false; + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index ef782abbe5..0b84ff3297 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -17,6 +17,8 @@ namespace osu.Game.Storyboards.Drawables public bool FlipH { get; set; } public bool FlipV { get; set; } + public override bool RemoveWhenNotAlive => false; + protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index a39805f74e..c4b9a3d47e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -17,6 +17,8 @@ namespace osu.Game.Storyboards.Drawables public bool FlipH { get; set; } public bool FlipV { get; set; } + public override bool RemoveWhenNotAlive => false; + protected override Vector2 DrawScale => new Vector2(FlipH ? -base.DrawScale.X : base.DrawScale.X, FlipV ? -base.DrawScale.Y : base.DrawScale.Y); From ea6e3938c039a27d29a13b17404fd8a582b197e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Mar 2018 03:00:13 +0900 Subject: [PATCH 15/22] Fix hard crash due to spinner spin requirement being zero Resolves #2133. --- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 2f238bb74b..b30e4cb932 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; @@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. - SpinsRequired = (int)(SpinsRequired * 0.6); + SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6); } } } From 30b14473181fe3eb11f2207c61949c3f99b70731 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Mar 2018 03:17:11 +0900 Subject: [PATCH 16/22] Fix skin file path lookup performance Move path mapping to the resource store, so caching can happen against the component's name rather than the skin path. Fixes regression of beatmap load time when a custom skin is selected. --- osu.Game/Skinning/LegacySkin.cs | 11 +++------- osu.Game/Skinning/SkinResourceStore.cs | 29 ++++++++++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Skinning/SkinResourceStore.cs diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5f34ddc2b5..97e0bdb942 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -1,9 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using System.IO; -using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; @@ -22,16 +19,14 @@ namespace osu.Game.Skinning public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : base(skin) { + storage = new SkinResourceStore(skin, storage); samples = audioManager.GetSampleManager(storage); textures = new TextureStore(new RawTextureLoaderStore(storage)); } - private string getPathForFile(string filename) => - SkinInfo.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; - public override Drawable GetDrawableComponent(string componentName) { - var texture = textures.Get(getPathForFile(componentName.Split('/').Last())); + var texture = textures.Get(componentName); if (texture == null) return null; return new Sprite @@ -42,6 +37,6 @@ namespace osu.Game.Skinning }; } - public override SampleChannel GetSample(string sampleName) => samples.Get(getPathForFile(sampleName.Split('/').Last())); + public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName); } } diff --git a/osu.Game/Skinning/SkinResourceStore.cs b/osu.Game/Skinning/SkinResourceStore.cs new file mode 100644 index 0000000000..58da8ad19a --- /dev/null +++ b/osu.Game/Skinning/SkinResourceStore.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using osu.Framework.IO.Stores; + +namespace osu.Game.Skinning +{ + public class SkinResourceStore : IResourceStore + { + private readonly SkinInfo skin; + private readonly IResourceStore underlyingStore; + + private string getPathForFile(string filename) => + skin.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename.Split('/').Last(), StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + + public SkinResourceStore(SkinInfo skin, IResourceStore underlyingStore) + { + this.skin = skin; + this.underlyingStore = underlyingStore; + } + + public Stream GetStream(string name) => underlyingStore.GetStream(getPathForFile(name)); + + byte[] IResourceStore.Get(string name) => underlyingStore.Get(getPathForFile(name)); + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 37e304d62d..d4bd5a0013 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -870,6 +870,7 @@ + From bc0bc8d459dc3f38098527fef9794b5326424036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Mar 2018 21:57:24 +0900 Subject: [PATCH 17/22] Add legacy timing offsets These have been in release builds since January, but implemented in a hacky way. This brings them with a sane implementation. --- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 3e7b36f324..7273fe999f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -8,6 +8,7 @@ using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; +using osu.Framework; namespace osu.Game.Beatmaps.Formats { @@ -21,6 +22,8 @@ namespace osu.Game.Beatmaps.Formats private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; + private readonly int timeOffset; + public LegacyBeatmapDecoder() { } @@ -28,6 +31,14 @@ namespace osu.Game.Beatmaps.Formats public LegacyBeatmapDecoder(string header) { BeatmapVersion = int.Parse(header.Substring(17)); + + // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) + timeOffset += BeatmapVersion < 5 ? 24 : 0; + + // lazer in general doesn't match stable. this is the result of user testing, albeit limited. + // only seems to be required on windows. + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + timeOffset += -22; } protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) @@ -102,7 +113,7 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); break; case @"PreviewTime": - metadata.PreviewTime = int.Parse(pair.Value); + metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value)); break; case @"Countdown": beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; @@ -257,8 +268,8 @@ namespace osu.Game.Beatmaps.Formats case EventType.Break: var breakEvent = new BreakPeriod { - StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), - EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo) + StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)), + EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo)) }; if (!breakEvent.HasEffect) @@ -273,7 +284,7 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); + double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo)); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; @@ -396,7 +407,14 @@ namespace osu.Game.Beatmaps.Formats var obj = parser.Parse(line); if (obj != null) + { + obj.StartTime = getOffsetTime(obj.StartTime); beatmap.HitObjects.Add(obj); + } } + + private int getOffsetTime(int time) => time + timeOffset; + + private double getOffsetTime(double time) => time + timeOffset; } } From e46f363fdc3ca492d8a0d1f4e68053276a49b491 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Mar 2018 22:13:43 +0900 Subject: [PATCH 18/22] Fix failing unit test --- .../Formats/LegacyBeatmapDecoderTest.cs | 8 +++---- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/IO/OszArchiveReaderTest.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 24 ++++++++++++------- .../Tests/Beatmaps/BeatmapConversionTest.cs | 2 +- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 21bbc4993c..2e774e0924 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeBeatmapGeneral() { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeBeatmapEvents() { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeBeatmapTimingPoints() { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { @@ -187,7 +187,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeBeatmapHitObjects() { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 186bd44640..8168de091e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var sr = new StreamReader(stream)) { - var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr); + var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.DecodeBeatmap(sr); using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 7a1c6d9b89..1f7246a119 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); Assert.AreEqual("Deif", meta.AuthorString); Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); - Assert.AreEqual(164471, meta.PreviewTime); + Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime); Assert.AreEqual(string.Empty, meta.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); Assert.AreEqual("Renatus", meta.Title); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 7273fe999f..1d54bc4b0c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -22,7 +22,18 @@ namespace osu.Game.Beatmaps.Formats private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; - private readonly int timeOffset; + /// + /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. + /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + /// + public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0; + + /// + /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. + /// + public bool ApplyOffsets = true; + + private readonly int offset = UniversalOffset; public LegacyBeatmapDecoder() { @@ -33,12 +44,7 @@ namespace osu.Game.Beatmaps.Formats BeatmapVersion = int.Parse(header.Substring(17)); // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) - timeOffset += BeatmapVersion < 5 ? 24 : 0; - - // lazer in general doesn't match stable. this is the result of user testing, albeit limited. - // only seems to be required on windows. - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - timeOffset += -22; + offset += BeatmapVersion < 5 ? 24 : 0; } protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) @@ -413,8 +419,8 @@ namespace osu.Game.Beatmaps.Formats } } - private int getOffsetTime(int time) => time + timeOffset; + private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0); - private double getOffsetTime(double time) => time + timeOffset; + private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 596dbe84ba..a9b13e87bf 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -109,7 +109,7 @@ namespace osu.Game.Tests.Beatmaps private Beatmap getBeatmap(string name) { - var decoder = new LegacyBeatmapDecoder(); + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; using (var resStream = openResource($"{resource_namespace}.{name}.osu")) using (var stream = new StreamReader(resStream)) return decoder.DecodeBeatmap(stream); From 7ce3e607224bcf981a4433154413e2975d9b923e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Mar 2018 21:17:26 +0900 Subject: [PATCH 19/22] Fix drumrolls giving GOOD judgements too one tick too late --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 29d464f614..f98e6b936e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -82,8 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; int countHit = NestedHitObjects.Count(o => o.IsHit); - - if (countHit > HitObject.RequiredGoodHits) + if (countHit >= HitObject.RequiredGoodHits) { AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good }); if (HitObject.IsStrong) From df84b238478bc354139e72303b56359fd339d9ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Mar 2018 21:19:04 +0900 Subject: [PATCH 20/22] Fix possible nullref when there are 0 drumroll ticks --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index f98e6b936e..2735d28769 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - int countHit = NestedHitObjects.Count(o => o.IsHit); + int countHit = NestedHitObjects?.Count(o => o.IsHit) ?? 0; if (countHit >= HitObject.RequiredGoodHits) { AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good }); From 02690e5f25135cbecfac4a1e65990d95f6f9ac0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Mar 2018 21:27:37 +0900 Subject: [PATCH 21/22] Move to private implementation --- osu.Game/Skinning/LegacySkin.cs | 24 ++++++++++++++++++++- osu.Game/Skinning/SkinResourceStore.cs | 29 -------------------------- osu.Game/osu.Game.csproj | 1 - 3 files changed, 23 insertions(+), 31 deletions(-) delete mode 100644 osu.Game/Skinning/SkinResourceStore.cs diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 97e0bdb942..17fe6369a7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -1,6 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.IO; +using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; @@ -19,7 +22,7 @@ namespace osu.Game.Skinning public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : base(skin) { - storage = new SkinResourceStore(skin, storage); + storage = new LegacySkinResourceStore(skin, storage); samples = audioManager.GetSampleManager(storage); textures = new TextureStore(new RawTextureLoaderStore(storage)); } @@ -38,5 +41,24 @@ namespace osu.Game.Skinning } public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName); + + private class LegacySkinResourceStore : IResourceStore + { + private readonly SkinInfo skin; + private readonly IResourceStore underlyingStore; + + private string getPathForFile(string filename) => + skin.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename.Split('/').Last(), StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + + public LegacySkinResourceStore(SkinInfo skin, IResourceStore underlyingStore) + { + this.skin = skin; + this.underlyingStore = underlyingStore; + } + + public Stream GetStream(string name) => underlyingStore.GetStream(getPathForFile(name)); + + byte[] IResourceStore.Get(string name) => underlyingStore.Get(getPathForFile(name)); + } } } diff --git a/osu.Game/Skinning/SkinResourceStore.cs b/osu.Game/Skinning/SkinResourceStore.cs deleted file mode 100644 index 58da8ad19a..0000000000 --- a/osu.Game/Skinning/SkinResourceStore.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using System.Linq; -using osu.Framework.IO.Stores; - -namespace osu.Game.Skinning -{ - public class SkinResourceStore : IResourceStore - { - private readonly SkinInfo skin; - private readonly IResourceStore underlyingStore; - - private string getPathForFile(string filename) => - skin.Files.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f.Filename), filename.Split('/').Last(), StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; - - public SkinResourceStore(SkinInfo skin, IResourceStore underlyingStore) - { - this.skin = skin; - this.underlyingStore = underlyingStore; - } - - public Stream GetStream(string name) => underlyingStore.GetStream(getPathForFile(name)); - - byte[] IResourceStore.Get(string name) => underlyingStore.Get(getPathForFile(name)); - } -} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d4bd5a0013..37e304d62d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -870,7 +870,6 @@ - From 2a9fb2c2c60966fd64333abffdf3e7bea79e9d49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Mar 2018 21:40:26 +0900 Subject: [PATCH 22/22] Make NestedHitObjects lazily-constructed --- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 16 +++++++--------- .../OverlappingSpeedChangeVisualiser.cs | 2 +- .../SequentialSpeedChangeVisualiser.cs | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2735d28769..f98e6b936e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - int countHit = NestedHitObjects?.Count(o => o.IsHit) ?? 0; + int countHit = NestedHitObjects.Count(o => o.IsHit); if (countHit >= HitObject.RequiredGoodHits) { AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good }); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4c2683b389..394b6fa9fd 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -35,8 +35,9 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual IEnumerable GetSamples() => HitObject.Samples; - private List nestedHitObjects; - public IReadOnlyList NestedHitObjects => nestedHitObjects; + private readonly Lazy> nestedHitObjects = new Lazy>(); + public bool HasNestedHitObjects => nestedHitObjects.IsValueCreated; + public IReadOnlyList NestedHitObjects => nestedHitObjects.Value; public event Action OnJudgement; public event Action OnJudgementRemoved; @@ -52,12 +53,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Whether this and all of its nested s have been hit. /// - public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (NestedHitObjects?.All(n => n.IsHit) ?? true); + public bool IsHit => Judgements.Any(j => j.Final && j.IsHit) && (!HasNestedHitObjects || NestedHitObjects.All(n => n.IsHit)); /// /// Whether this and all of its nested s have been judged. /// - public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true); + public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (!HasNestedHitObjects || NestedHitObjects.All(h => h.AllJudged)); /// /// Whether this can be judged. @@ -160,14 +161,11 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual void AddNested(DrawableHitObject h) { - if (nestedHitObjects == null) - nestedHitObjects = new List(); - h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j); h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j); h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j); - nestedHitObjects.Add(h); + nestedHitObjects.Value.Add(h); } /// @@ -211,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (AllJudged) return false; - if (NestedHitObjects != null) + if (HasNestedHitObjects) foreach (var d in NestedHitObjects) judgementOccurred |= d.UpdateJudgement(userTriggered); diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs index 4cce90ee94..48c212efa7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers var controlPoint = controlPointAt(obj.HitObject.StartTime); obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier; - if (obj.NestedHitObjects != null) + if (obj.HasNestedHitObjects) { ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs index 94705426f8..1b7c3714d6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers } } - if (obj.NestedHitObjects != null) + if (obj.HasNestedHitObjects) { ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);