1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 22:47:24 +08:00
osu-lazer/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
Dean Herbert ff108416d8
Fix incorrect background being loaded due to async race
If the API login (and thus user set) completed between `load` and
`LoadComplete`, the re-fetch on user change would not yet be hooked up,
causing an incorrect default background to be used instead.

Of note, moving this out of async load doesn't really affect load
performance as the bulk of the load operation is already scheduled and
`LoadComponentAsync`ed anyway
2024-05-01 00:05:14 +08:00

207 lines
7.4 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Diagnostics;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Skinning;
namespace osu.Game.Screens.Backgrounds
{
public partial class BackgroundScreenDefault : BackgroundScreen
{
private Background background;
private int currentDisplay;
private const int background_count = 8;
private IBindable<APIUser> user;
private Bindable<Skin> skin;
private Bindable<BackgroundSource> source;
private Bindable<IntroSequence> introSequence;
private readonly SeasonalBackgroundLoader seasonalBackgroundLoader = new SeasonalBackgroundLoader();
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private GameHost gameHost { get; set; }
protected virtual bool AllowStoryboardBackground => true;
public BackgroundScreenDefault(bool animateOnEnter = true)
: base(animateOnEnter)
{
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config)
{
user = api.LocalUser.GetBoundCopy();
skin = skinManager.CurrentSkin.GetBoundCopy();
source = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource);
introSequence = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence);
AddInternal(seasonalBackgroundLoader);
}
protected override void LoadComplete()
{
base.LoadComplete();
user.ValueChanged += _ => Scheduler.AddOnce(next);
skin.ValueChanged += _ => Scheduler.AddOnce(next);
source.ValueChanged += _ => Scheduler.AddOnce(next);
beatmap.ValueChanged += _ => Scheduler.AddOnce(next);
introSequence.ValueChanged += _ => Scheduler.AddOnce(next);
seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(next);
currentDisplay = RNG.Next(0, background_count);
Next();
// helper function required for AddOnce usage.
void next() => Next();
}
private ScheduledDelegate storyboardUnloadDelegate;
public override void OnSuspending(ScreenTransitionEvent e)
{
var backgroundScreenStack = Parent as BackgroundScreenStack;
Debug.Assert(backgroundScreenStack != null);
if (background is BeatmapBackgroundWithStoryboard storyboardBackground)
storyboardUnloadDelegate = gameHost.UpdateThread.Scheduler.AddDelayed(storyboardBackground.UnloadStoryboard, TRANSITION_LENGTH);
base.OnSuspending(e);
}
public override void OnResuming(ScreenTransitionEvent e)
{
if (background is BeatmapBackgroundWithStoryboard storyboardBackground)
{
if (storyboardUnloadDelegate?.Completed == false)
storyboardUnloadDelegate.Cancel();
else
storyboardBackground.LoadStoryboard();
storyboardUnloadDelegate = null;
}
base.OnResuming(e);
}
private ScheduledDelegate nextTask;
private CancellationTokenSource cancellationTokenSource;
/// <summary>
/// Request loading the next background.
/// </summary>
/// <returns>Whether a new background was queued for load. May return false if the current background is still valid.</returns>
public virtual bool Next()
{
var nextBackground = createBackground();
// in the case that the background hasn't changed, we want to avoid cancelling any tasks that could still be loading.
if (nextBackground == background)
return false;
Logger.Log(@"🌅 Global background change queued");
cancellationTokenSource?.Cancel();
cancellationTokenSource = new CancellationTokenSource();
nextTask?.Cancel();
nextTask = Scheduler.AddDelayed(() =>
{
Logger.Log(@"🌅 Global background loading");
LoadComponentAsync(nextBackground, displayNext, cancellationTokenSource.Token);
}, 500);
return true;
}
private void displayNext(Background newBackground)
{
background?.FadeOut(800, Easing.OutQuint);
background?.Expire();
AddInternal(background = newBackground);
currentDisplay++;
}
private Background createBackground()
{
// seasonal background loading gets highest priority.
Background newBackground = seasonalBackgroundLoader.LoadNextBackground();
if (newBackground == null && user.Value?.IsSupporter == true)
{
switch (source.Value)
{
case BackgroundSource.Beatmap:
case BackgroundSource.BeatmapWithStoryboard:
{
if (source.Value == BackgroundSource.BeatmapWithStoryboard && AllowStoryboardBackground)
newBackground = new BeatmapBackgroundWithStoryboard(beatmap.Value, getBackgroundTextureName());
newBackground ??= new BeatmapBackground(beatmap.Value, getBackgroundTextureName());
break;
}
case BackgroundSource.Skin:
switch (skin.Value)
{
case TrianglesSkin:
case ArgonSkin:
case DefaultLegacySkin:
// default skins should use the default background rotation, which won't be the case if a SkinBackground is created for them.
break;
default:
newBackground = new SkinBackground(skin.Value, getBackgroundTextureName());
break;
}
break;
}
}
// this method is called in many cases where the background might not necessarily need to change.
// if an equivalent background is currently being shown, we don't want to load it again.
if (newBackground?.Equals(background) == true)
return background;
newBackground ??= new Background(getBackgroundTextureName());
newBackground.Depth = currentDisplay;
return newBackground;
}
private string getBackgroundTextureName()
{
switch (introSequence.Value)
{
case IntroSequence.Welcome:
return @"Intro/Welcome/menu-background";
default:
return $@"Menu/menu-background-{currentDisplay % background_count + 1}";
}
}
}
}