From 8585327858a00b6c15612bf29e446ccb733773d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:08:53 +0900 Subject: [PATCH 01/10] Ensure `DrawableMedal` loading doesn't ever block on online resources --- .../Overlays/MedalSplash/DrawableMedal.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 2beed6645a..adad540c34 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -28,16 +29,14 @@ namespace osu.Game.Overlays.MedalSplash [CanBeNull] public event Action StateChanged; - private readonly Medal medal; private readonly Container medalContainer; - private readonly Sprite medalSprite, medalGlow; + private readonly Sprite medalGlow; private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; public DrawableMedal(Medal medal) { - this.medal = medal; Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; @@ -51,7 +50,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Children = new Drawable[] { - medalSprite = new Sprite + new DelayedLoadWrapper(() => new MedalOnlineSprite(medal), 0) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -122,11 +121,12 @@ namespace osu.Game.Overlays.MedalSplash } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) + private void load(OsuColour colours, TextureStore textures) { - medalSprite.Texture = largeTextures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; + + Logger.Log("loaded"); } protected override void LoadComplete() @@ -191,6 +191,31 @@ namespace osu.Game.Overlays.MedalSplash break; } } + + private partial class MedalOnlineSprite : Sprite + { + private readonly Medal medal; + + public MedalOnlineSprite(Medal medal) + { + this.medal = medal; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) + { + Texture = largeTextures.Get(medal.ImageUrl); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(150, Easing.OutQuint); + } + } } public enum DisplayState From d057dc9a95cf76f6888e6e0d8f8a60dca3705343 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:13:07 +0900 Subject: [PATCH 02/10] Refactor `MedalOverlay` to be more readable Shouldn't really have any functionality changes, just fixing some old code that I can't easily parse these days. --- osu.Game/Overlays/MedalOverlay.cs | 78 ++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 19f61cb910..7303a57cd0 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } = null!; private Container medalContainer = null!; - private MedalAnimation? lastAnimation; + private MedalAnimation? currentMedalDisplay; [BackgroundDependencyLoader] private void load() @@ -54,11 +54,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - OverlayActivationMode.BindValueChanged(val => - { - if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any() || lastAnimation?.IsLoaded == false)) - Show(); - }, true); + OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); } private void handleMedalMessages(SocketMessage obj) @@ -86,31 +82,13 @@ namespace osu.Game.Overlays queuedMedals.Enqueue(medalAnimation); Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - if (OverlayActivationMode.Value == OverlayActivation.All) - Scheduler.AddOnce(Show); - } - - protected override void Update() - { - base.Update(); - - if (medalContainer.Any() || lastAnimation?.IsLoaded == false) - return; - - if (!queuedMedals.TryDequeue(out lastAnimation)) - { - Logger.Log("All queued medals have been displayed!"); - Hide(); - return; - } - - Logger.Log($"Preparing to display \"{lastAnimation.Medal.Name}\""); - LoadComponentAsync(lastAnimation, medalContainer.Add); + Schedule(displayIfReady); } protected override bool OnClick(ClickEvent e) { - lastAnimation?.Dismiss(); + dismissDisplayedMedal(); + loadNextMedal(); return true; } @@ -118,13 +96,57 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - lastAnimation?.Dismiss(); + dismissDisplayedMedal(); + loadNextMedal(); return true; } return base.OnPressed(e); } + private void dismissDisplayedMedal() + { + if (currentMedalDisplay?.IsLoaded == false) + return; + + currentMedalDisplay?.Dismiss(); + currentMedalDisplay = null; + } + + private void displayIfReady() + { + if (OverlayActivationMode.Value != OverlayActivation.All) + return; + + if (currentMedalDisplay != null) + { + Show(); + return; + } + + if (queuedMedals.Any()) + { + Show(); + loadNextMedal(); + } + } + + private void loadNextMedal() + { + if (currentMedalDisplay != null) + return; + + if (!queuedMedals.TryDequeue(out currentMedalDisplay)) + { + Logger.Log("All queued medals have been displayed!"); + Hide(); + return; + } + + Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); + LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 672dbe6e03bd95971f46ace7685d91cd75729bb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:42:30 +0900 Subject: [PATCH 03/10] Better control of show/hide of overlay --- osu.Game/Overlays/MedalOverlay.cs | 55 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 7303a57cd0..b7e68fd557 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -57,6 +57,11 @@ namespace osu.Game.Overlays OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); } + public override void Hide() + { + // don't allow hiding the overlay via any method other than our own. + } + private void handleMedalMessages(SocketMessage obj) { if (obj.Event != @"new") @@ -87,8 +92,7 @@ namespace osu.Game.Overlays protected override bool OnClick(ClickEvent e) { - dismissDisplayedMedal(); - loadNextMedal(); + progressDisplayByUser(); return true; } @@ -96,21 +100,31 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - dismissDisplayedMedal(); - loadNextMedal(); + progressDisplayByUser(); return true; } return base.OnPressed(e); } - private void dismissDisplayedMedal() + private void progressDisplayByUser() { + // For now, we want to make sure that medals are definitely seen by the user. + // So we block exiting the overlay until the load of the active medal completes. if (currentMedalDisplay?.IsLoaded == false) return; currentMedalDisplay?.Dismiss(); currentMedalDisplay = null; + + if (!queuedMedals.Any()) + { + Logger.Log("All queued medals have been displayed, hiding overlay!"); + base.Hide(); + return; + } + + showNextMedal(); } private void displayIfReady() @@ -118,33 +132,26 @@ namespace osu.Game.Overlays if (OverlayActivationMode.Value != OverlayActivation.All) return; - if (currentMedalDisplay != null) - { - Show(); - return; - } - - if (queuedMedals.Any()) - { - Show(); - loadNextMedal(); - } + if (currentMedalDisplay != null || queuedMedals.Any()) + showNextMedal(); } - private void loadNextMedal() + private void showNextMedal() { + // A medal is already loading / loaded, so just ensure the overlay is visible. if (currentMedalDisplay != null) - return; - - if (!queuedMedals.TryDequeue(out currentMedalDisplay)) { - Logger.Log("All queued medals have been displayed!"); - Hide(); + Show(); return; } - Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); - LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + if (queuedMedals.TryDequeue(out currentMedalDisplay)) + { + Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); + + Show(); + LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + } } protected override void Dispose(bool isDisposing) From e8fae85e8d5b0077b9825a650180b89511c38d87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:45:40 +0900 Subject: [PATCH 04/10] Fix hidden dissmissing logic --- osu.Game/Overlays/MedalAnimation.cs | 5 +++-- osu.Game/Overlays/MedalOverlay.cs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MedalAnimation.cs b/osu.Game/Overlays/MedalAnimation.cs index daceeedf47..fdca0b2cc7 100644 --- a/osu.Game/Overlays/MedalAnimation.cs +++ b/osu.Game/Overlays/MedalAnimation.cs @@ -245,18 +245,19 @@ namespace osu.Game.Overlays this.FadeOut(200); } - public void Dismiss() + public bool Dismiss() { if (drawableMedal != null && drawableMedal.State != DisplayState.Full) { // if we haven't yet, play out the animation fully drawableMedal.State = DisplayState.Full; FinishTransforms(true); - return; + return false; } Hide(); Expire(); + return true; } private partial class BackgroundStrip : Container diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index b7e68fd557..736f744429 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -114,7 +114,10 @@ namespace osu.Game.Overlays if (currentMedalDisplay?.IsLoaded == false) return; - currentMedalDisplay?.Dismiss(); + // Dismissing may sometimes play out the medal animation rather than immediately dismissing. + if (currentMedalDisplay?.Dismiss() == false) + return; + currentMedalDisplay = null; if (!queuedMedals.Any()) From 1e6c04e98b092e52a35b20d07e9a5a67e61de1b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 16:05:04 +0900 Subject: [PATCH 05/10] Remove debug logging --- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index adad540c34..460239f620 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -125,8 +124,6 @@ namespace osu.Game.Overlays.MedalSplash { medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; - - Logger.Log("loaded"); } protected override void LoadComplete() From 98044c108e7f7e9e8723c61ff1ca3823a95feaeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:41:00 +0900 Subject: [PATCH 06/10] Revert "Ensure `DrawableMedal` loading doesn't ever block on online resources" This reverts commit 8585327858a00b6c15612bf29e446ccb733773d9. --- .../Overlays/MedalSplash/DrawableMedal.cs | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 460239f620..2beed6645a 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -28,14 +28,16 @@ namespace osu.Game.Overlays.MedalSplash [CanBeNull] public event Action StateChanged; + private readonly Medal medal; private readonly Container medalContainer; - private readonly Sprite medalGlow; + private readonly Sprite medalSprite, medalGlow; private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; public DrawableMedal(Medal medal) { + this.medal = medal; Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; @@ -49,7 +51,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Children = new Drawable[] { - new DelayedLoadWrapper(() => new MedalOnlineSprite(medal), 0) + medalSprite = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -120,8 +122,9 @@ namespace osu.Game.Overlays.MedalSplash } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures) + private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) { + medalSprite.Texture = largeTextures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; } @@ -188,31 +191,6 @@ namespace osu.Game.Overlays.MedalSplash break; } } - - private partial class MedalOnlineSprite : Sprite - { - private readonly Medal medal; - - public MedalOnlineSprite(Medal medal) - { - this.medal = medal; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) - { - Texture = largeTextures.Get(medal.ImageUrl); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - this.FadeInFromZero(150, Easing.OutQuint); - } - } } public enum DisplayState From 71294c312b1a29d2ca73c1f335140e4f350754d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:58:50 +0900 Subject: [PATCH 07/10] Change point of queueing to avoid loading-from-in-queue --- osu.Game/Overlays/MedalOverlay.cs | 34 +++++++++++++------------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 736f744429..c24b209b3a 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); + OverlayActivationMode.BindValueChanged(_ => showNextMedal(), true); } public override void Hide() @@ -84,10 +84,13 @@ namespace osu.Game.Overlays var medalAnimation = new MedalAnimation(medal); - queuedMedals.Enqueue(medalAnimation); Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - Schedule(displayIfReady); + LoadComponentAsync(medalAnimation, m => + { + queuedMedals.Enqueue(m); + showNextMedal(); + }); } protected override bool OnClick(ClickEvent e) @@ -130,30 +133,21 @@ namespace osu.Game.Overlays showNextMedal(); } - private void displayIfReady() - { - if (OverlayActivationMode.Value != OverlayActivation.All) - return; - - if (currentMedalDisplay != null || queuedMedals.Any()) - showNextMedal(); - } - private void showNextMedal() { - // A medal is already loading / loaded, so just ensure the overlay is visible. - if (currentMedalDisplay != null) - { - Show(); + // If already displayed, keep displaying medals regardless of activation mode changes. + if (OverlayActivationMode.Value != OverlayActivation.All && State.Value == Visibility.Hidden) + return; + + // A medal is already displaying. + if (currentMedalDisplay != null) return; - } if (queuedMedals.TryDequeue(out currentMedalDisplay)) { - Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); - + Logger.Log($"Displaying \"{currentMedalDisplay.Medal.Name}\""); + medalContainer.Add(currentMedalDisplay); Show(); - LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); } } From bf29e3ae718373f16ff1edc5e35c412fa89e9902 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 18:00:32 +0900 Subject: [PATCH 08/10] Simplify hide code by moving to common method --- osu.Game/Overlays/MedalOverlay.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index c24b209b3a..512cb697dd 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -112,24 +111,11 @@ namespace osu.Game.Overlays private void progressDisplayByUser() { - // For now, we want to make sure that medals are definitely seen by the user. - // So we block exiting the overlay until the load of the active medal completes. - if (currentMedalDisplay?.IsLoaded == false) - return; - // Dismissing may sometimes play out the medal animation rather than immediately dismissing. if (currentMedalDisplay?.Dismiss() == false) return; currentMedalDisplay = null; - - if (!queuedMedals.Any()) - { - Logger.Log("All queued medals have been displayed, hiding overlay!"); - base.Hide(); - return; - } - showNextMedal(); } @@ -149,6 +135,11 @@ namespace osu.Game.Overlays medalContainer.Add(currentMedalDisplay); Show(); } + else if (State.Value == Visibility.Visible) + { + Logger.Log("All queued medals have been displayed, hiding overlay!"); + base.Hide(); + } } protected override void Dispose(bool isDisposing) From b0958c8d418db28022fe2d12dd0ca2722ddad14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 10:24:56 +0100 Subject: [PATCH 09/10] Attempt to fix test failures --- osu.Game/Overlays/MedalOverlay.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 512cb697dd..e102feb3e2 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -144,10 +144,12 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); - + // this event subscription fires async loads, which hard-fail if `CompositeDrawable.disposalCancellationSource` is canceled, which happens in the base call. + // therefore, unsubscribe from this event early to reduce the chances of a stray event firing at an inconvenient spot. if (api.IsNotNull()) api.NotificationsClient.MessageReceived -= handleMedalMessages; + + base.Dispose(isDisposing); } } } From c14fe21219acc24741b7d6b31106763fdd488796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 11:19:00 +0100 Subject: [PATCH 10/10] Fix LCA call crashing in actual usage It's not allowed to call `LoadComponentsAsync()` on a background thread: https://github.com/ppy/osu-framework/blob/fd64f2f0d47f0ee1aaa596bde1e83e527d610340/osu.Framework/Graphics/Containers/CompositeDrawable.cs#L147 and in this case the event that causes the LCA call is dispatched from a websocket client, which is not on the update thread, so scheduling is required. --- osu.Game/Overlays/MedalOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index e102feb3e2..25e22ffbda 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -85,11 +85,11 @@ namespace osu.Game.Overlays Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - LoadComponentAsync(medalAnimation, m => + Schedule(() => LoadComponentAsync(medalAnimation, m => { queuedMedals.Enqueue(m); showNextMedal(); - }); + })); } protected override bool OnClick(ClickEvent e)