<commit_before>// Copyright (c) ppy Pty Ltd <<EMAIL>>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK.Input;

namespace osu.Game.Tests.Visual.Gameplay
{
    public class TestScenePlayerLoader : OsuManualInputManagerTestScene
    {
        private TestPlayerLoader loader;
        private TestPlayerLoaderContainer container;
        private TestPlayer player;

        [Resolved]
        private AudioManager audioManager { get; set; }

        [Resolved]
        private SessionStatics sessionStatics { get; set; }

        /// <summary>
        /// Sets the input manager child to a new test player loader container instance.
        /// </summary>
        /// <param name="interactive">If the test player should behave like the production one.</param>
        /// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
        public void ResetPlayer(bool interactive, Action beforeLoadAction = null)
        {
            player = null;

            audioManager.Volume.SetDefault();

            InputManager.Clear();

            container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));

            beforeLoadAction?.Invoke();

            Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);

            foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
                mod.ApplyToTrack(Beatmap.Value.Track);

            InputManager.Child = container;
        }

        /// <summary>
        /// When <see cref="PlayerLoader"/> exits early, it has to wait for the player load task
        /// to complete before running disposal on player. This previously caused an issue where mod
        /// speed adjustments were undone too late, causing cross-screen pollution.
        /// </summary>
        [Test]
        public void TestEarlyExit()
        {
            AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
            AddUntilStep("wait for current", () => loader.IsCurrentScreen());
            AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
            AddStep("exit loader", () => loader.Exit());
            AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
            AddAssert("player did not load", () => player?.IsLoaded != true);
            AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true);
            AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
        }

        [Test]
        public void TestBlockLoadViaMouseMovement()
        {
            AddStep("load dummy beatmap", () => ResetPlayer(false));
            AddUntilStep("wait for current", () => loader.IsCurrentScreen());

            AddUntilStep("wait for load ready", () =>
            {
                moveMouse();
                return player?.LoadState == LoadState.Ready;
            });
            AddRepeatStep("move mouse", moveMouse, 20);

            AddAssert("loader still active", () => loader.IsCurrentScreen());<commit_msg>Add coverage of early exit with null and non-null player<commit_after>// Copyright (c) <NAME> <<EMAIL>>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK.Input;

namespace osu.Game.Tests.Visual.Gameplay
{
    public class TestScenePlayerLoader : OsuManualInputManagerTestScene
    {
        private TestPlayerLoader loader;
        private TestPlayerLoaderContainer container;
        private TestPlayer player;

        [Resolved]
        private AudioManager audioManager { get; set; }

        [Resolved]
        private SessionStatics sessionStatics { get; set; }

        /// <summary>
        /// Sets the input manager child to a new test player loader container instance.
        /// </summary>
        /// <param name="interactive">If the test player should behave like the production one.</param>
        /// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
        public void ResetPlayer(bool interactive, Action beforeLoadAction = null)
        {
            player = null;

            audioManager.Volume.SetDefault();

            InputManager.Clear();

            container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));

            beforeLoadAction?.Invoke();

            Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);

            foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
                mod.ApplyToTrack(Beatmap.Value.Track);

            InputManager.Child = container;
        }

        [Test]
        public void TestEarlyExitBeforePlayerConstruction()
        {
            AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
            AddUntilStep("wait for current", () => loader.IsCurrentScreen());
            AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
            AddStep("exit loader", () => loader.Exit());
            AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
            AddAssert("player did not load", () => player == null);
            AddUntilStep("player disposed", () => loader.DisposalTask == null);
            AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
        }

        /// <summary>
        /// When <see cref="PlayerLoader"/> exits early, it has to wait for the player load task
        /// to complete before running disposal on player. This previously caused an issue where mod
        /// speed adjustments were undone too late, causing cross-screen pollution.
        /// </summary>
        [Test]
        public void TestEarlyExitAfterPlayerConstruction()
        {
            AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
            AddUntilStep("wait for current", () => loader.IsCurrentScreen());
            AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
            AddUntilStep("wait for non-null player", () => player != null);
            AddStep("exit loader", () => loader.Exit());
            AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
            AddAssert("player did not load", () => !player.IsLoaded);
            AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true);
            AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
        }

        [Test]
        public void TestBlockLoadViaMouseMovement()
        {
            AddStep("load dummy beatmap", () => ResetPlayer(false));
            AddUntilStep("wait for current", () => loader.IsCurrentScreen());

            AddUntilStep("wait for load ready", () =>
            {
                moveMouse();
                return player?.LoadState == LoadState.Ready;
            });
            AddRepeatStep("move mouse", moveMouse, 20);

            AddAssert("loader still active", () => loader.IsCurrentScreen());
{'fraction_non_alphanumeric': 0.11873290793072015, 'fraction_numerical': 0.0011394712853236098, 'mean_word_length': 2.746052069995732, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3311174', 'n_tokens_mistral': 2354, 'n_tokens_neox': 2275, 'n_words': 626}