diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 66c4cf8686..7bde2e747d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -195,11 +195,17 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for player", () => { DismissAnyNotifications(); - return (player = Game.ScreenStack.CurrentScreen as Player) != null; + player = Game.ScreenStack.CurrentScreen as Player; + return player?.IsLoaded == true; }); AddAssert("retry count is 0", () => player.RestartCount == 0); + // todo: see https://github.com/ppy/osu/issues/22220 + // tests are supposed to be immune to this edge case by the logic in TestPlayer, + // but we're running a full game instance here, so we have to work around it manually. + AddStep("end spectator before retry", () => Game.SpectatorClient.EndPlaying(player.GameplayState)); + AddStep("attempt to retry", () => player.ChildrenOfType().First().Action()); AddUntilStep("wait for old player gone", () => Game.ScreenStack.CurrentScreen != player); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b60cef2835..55ec75f4ce 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online.Spectator /// /// Whether the local user is playing. /// - protected bool IsPlaying { get; private set; } + protected internal bool IsPlaying { get; private set; } /// /// Called whenever new frames arrive from the server. diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 33d33fe181..b27be37591 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -189,7 +189,7 @@ namespace osu.Game private RulesetConfigCache rulesetConfigCache; - private SpectatorClient spectatorClient; + protected SpectatorClient SpectatorClient { get; private set; } protected MultiplayerClient MultiplayerClient { get; private set; } @@ -300,7 +300,7 @@ namespace osu.Game // TODO: OsuGame or OsuGameBase? dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); - dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); + dependencies.CacheAs(SpectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher()); @@ -346,7 +346,7 @@ namespace osu.Game if (API is APIAccess apiAccess) base.Content.Add(apiAccess); - base.Content.Add(spectatorClient); + base.Content.Add(SpectatorClient); base.Content.Add(MultiplayerClient); base.Content.Add(metadataClient); base.Content.Add(soloStatisticsWatcher); diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 1bf1fbf6ab..94be4a375d 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -20,6 +20,7 @@ using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -148,6 +149,8 @@ namespace osu.Game.Tests.Visual public new Bindable> SelectedMods => base.SelectedMods; + public new SpectatorClient SpectatorClient => base.SpectatorClient; + // if we don't apply these changes, when running under nUnit the version that gets populated is that of nUnit. public override Version AssemblyVersion => new Version(0, 0); public override string Version => "test game"; diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 81195ebed9..d9cae6b03b 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; @@ -103,6 +104,19 @@ namespace osu.Game.Tests.Visual ScoreProcessor.NewJudgement += r => Results.Add(r); } + public override bool OnExiting(ScreenExitEvent e) + { + bool exiting = base.OnExiting(e); + + // SubmittingPlayer performs EndPlaying on a fire-and-forget async task, which allows for the chance of BeginPlaying to be called before EndPlaying is called here. + // Until this is handled properly at game-side, ensure EndPlaying is called before exiting player. + // see: https://github.com/ppy/osu/issues/22220 + if (LoadedBeatmapSuccessfully) + spectatorClient?.EndPlaying(GameplayState); + + return exiting; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing);