From f0114d776db14f2c71e89eedefc710e9956921e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 12:56:47 +0900 Subject: [PATCH 01/20] Use interface to access API Allows for better testability. --- .../Visual/TestCaseMatchLeaderboard.cs | 2 +- ...stCaseUpdateableBeatmapBackgroundSprite.cs | 2 +- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 4 +- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Online/API/APIRequest.cs | 11 +++-- osu.Game/Online/API/DummyAPIAccess.cs | 32 +++++++++++++-- osu.Game/Online/API/IAPIProvider.cs | 40 +++++++++++++++++++ osu.Game/Online/API/IOnlineComponent.cs | 2 +- osu.Game/Online/Leaderboards/Leaderboard.cs | 6 +-- osu.Game/OsuGameBase.cs | 1 - .../Overlays/AccountCreation/ScreenEntry.cs | 4 +- .../Overlays/AccountCreation/ScreenWarning.cs | 4 +- osu.Game/Overlays/AccountCreationOverlay.cs | 4 +- .../BeatmapSet/Buttons/DownloadButton.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 4 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 4 +- osu.Game/Overlays/DirectOverlay.cs | 4 +- .../Profile/Sections/PaginatedContainer.cs | 4 +- .../Sections/Recent/DrawableRecentActivity.cs | 4 +- .../Sections/General/LoginSettings.cs | 8 ++-- osu.Game/Overlays/SocialOverlay.cs | 6 +-- .../Overlays/Toolbar/ToolbarUserButton.cs | 4 +- osu.Game/Overlays/UserProfileOverlay.cs | 4 +- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- osu.Game/Screens/Menu/Disclaimer.cs | 4 +- .../Multi/Lounge/Components/RoomInspector.cs | 2 +- osu.Game/Screens/Multi/Multiplayer.cs | 4 +- .../Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/Multi/RoomManager.cs | 2 +- osu.Game/Screens/Play/Player.cs | 4 +- osu.Game/Screens/Select/BeatmapDetails.cs | 4 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 33 files changed, 127 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs index 42a886a5a34e..484a212a38ef 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs @@ -28,7 +28,7 @@ public TestCaseMatchLeaderboard() } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs index 506121efd77e..ac90c264c4e9 100644 --- a/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/TestCaseUpdateableBeatmapBackgroundSprite.cs @@ -22,7 +22,7 @@ public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase private BeatmapManager beatmaps { get; set; } [BackgroundDependencyLoader] - private void load(OsuGameBase osu, APIAccess api, RulesetStore rulesets) + private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { Bindable beatmapBindable = new Bindable(); diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 726134294eb8..46ee74b69f70 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual public class TestCaseUserProfile : OsuTestCase { private readonly TestUserProfileOverlay profile; - private APIAccess api; + private IAPIProvider api; public override IReadOnlyList RequiredTypes => new[] { @@ -36,7 +36,7 @@ public TestCaseUserProfile() } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 711aa0b79bc7..0ef8098a7669 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -64,7 +64,7 @@ public partial class BeatmapManager : ArchiveModelManager currentDownloads = new List(); - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, GameHost host = null, + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, new BeatmapStore(contextFactory), host) { diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 5593abf3487d..3d861e44bf84 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -22,7 +22,7 @@ public class APIAccess : Component, IAPIProvider private readonly OsuConfigManager config; private readonly OAuth authentication; - public string Endpoint = @"https://osu.ppy.sh"; + public string Endpoint => @"https://osu.ppy.sh"; private const string client_id = @"5"; private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 2781a5709b88..96f3b8527237 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -61,9 +61,12 @@ public abstract class APIRequest private Action pendingFailure; - public void Perform(APIAccess api) + public void Perform(IAPIProvider api) { - API = api; + if (!(api is APIAccess apiAccess)) + throw new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."); + + API = apiAccess; if (checkAndScheduleFailure()) return; @@ -71,7 +74,7 @@ public void Perform(APIAccess api) WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; - WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); + WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); if (checkAndScheduleFailure()) return; @@ -85,7 +88,7 @@ public void Perform(APIAccess api) if (checkAndScheduleFailure()) return; - api.Schedule(delegate { Success?.Invoke(); }); + API.Schedule(delegate { Success?.Invoke(); }); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 096ab5d8c8fe..af2fe51b38c5 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -11,14 +11,16 @@ public class DummyAPIAccess : IAPIProvider public Bindable LocalUser { get; } = new Bindable(new User { Username = @"Dummy", - Id = 1, + Id = 1001, }); public bool IsLoggedIn => true; - public void Update() - { - } + public string ProvidedUsername => LocalUser.Value.Username; + + public string Endpoint => "https://test.com"; + + public APIState State => LocalUser.Value.Id == 1 ? APIState.Offline : APIState.Online; public virtual void Queue(APIRequest request) { @@ -26,6 +28,28 @@ public virtual void Queue(APIRequest request) public void Register(IOnlineComponent component) { + // todo: add support + } + + public void Unregister(IOnlineComponent component) + { + // todo: add support } + + public void Login(string username, string password) + { + LocalUser.Value = new User + { + Username = @"Dummy", + Id = 1, + }; + } + + public void Logout() + { + LocalUser.Value = new GuestUser(); + } + + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) => null; } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index e4533ecb3d41..7c1f85094362 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -18,6 +18,19 @@ public interface IAPIProvider /// bool IsLoggedIn { get; } + /// + /// The last username provided by the end-user. + /// May not be authenticated. + /// + string ProvidedUsername { get; } + + /// + /// The URL endpoint for this API. Does not include a trailing slash. + /// + string Endpoint { get; } + + APIState State { get; } + /// /// Queue a new request. /// @@ -29,5 +42,32 @@ public interface IAPIProvider /// /// The component to register. void Register(IOnlineComponent component); + + /// + /// Unregisters a component to receive state changes. + /// + /// The component to unregister. + void Unregister(IOnlineComponent component); + + /// + /// Attempt to login using the provided credentials. This is a non-blocking operation. + /// + /// The user's username. + /// The user's password. + void Login(string username, string password); + + /// + /// Log out the current user. + /// + void Logout(); + + /// + /// Create a new user account. This is a blocking operation. + /// + /// The email to create the account with. + /// The username to create the account with. + /// The password to create the account with. + /// Any errors encoutnered during account creation. + RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password); } } diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs index fb40bfd1e62d..da6b784759bc 100644 --- a/osu.Game/Online/API/IOnlineComponent.cs +++ b/osu.Game/Online/API/IOnlineComponent.cs @@ -5,6 +5,6 @@ namespace osu.Game.Online.API { public interface IOnlineComponent { - void APIStateChanged(APIAccess api, APIState state); + void APIStateChanged(IAPIProvider api, APIState state); } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index f5b6e185c7f2..ac1666f8eda1 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -174,12 +174,12 @@ protected Leaderboard() }; } - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingUpdateScores; [BackgroundDependencyLoader(true)] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; api?.Register(this); @@ -195,7 +195,7 @@ protected override void Dispose(bool isDisposing) private APIRequest getScoresRequest; - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { if (state == APIState.Online) UpdateScores(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 643a673faf8e..7d55d19e5010 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -152,7 +152,6 @@ private void load() API = new APIAccess(LocalConfig); - dependencies.Cache(API); dependencies.CacheAs(API); var defaultBeatmap = new DummyWorkingBeatmap(this); diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 2fc00edbc1e5..13d8df098f60 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -33,7 +33,7 @@ public class ScreenEntry : AccountCreationScreen private OsuTextBox emailTextBox; private OsuPasswordTextBox passwordTextBox; - private APIAccess api; + private IAPIProvider api; private ShakeContainer registerShake; private IEnumerable characterCheckText; @@ -42,7 +42,7 @@ public class ScreenEntry : AccountCreationScreen private GameHost host; [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, GameHost host) + private void load(OsuColour colours, IAPIProvider api, GameHost host) { this.api = api; this.host = host; diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs index 4e2cc1ea0087..be417f4aac9b 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -22,7 +22,7 @@ public class ScreenWarning : AccountCreationScreen { private OsuTextFlowContainer multiAccountExplanationText; private LinkFlowContainer furtherAssistance; - private APIAccess api; + private IAPIProvider api; private const string help_centre_url = "/help/wiki/Help_Centre#login"; @@ -39,7 +39,7 @@ public override void OnEntering(IScreen last) } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, APIAccess api, OsuGame game, TextureStore textures) + private void load(OsuColour colours, IAPIProvider api, OsuGame game, TextureStore textures) { this.api = api; diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index bc780538d5b6..e8e44c206ebb 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -30,7 +30,7 @@ public AccountCreationOverlay() } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { api.Register(this); @@ -96,7 +96,7 @@ protected override void PopOut() this.FadeOut(100); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index edd886f0f2c4..bbbcff0558a9 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -39,7 +39,7 @@ public DownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) } [BackgroundDependencyLoader] - private void load(APIAccess api, BeatmapManager beatmaps) + private void load(IAPIProvider api, BeatmapManager beatmaps) { FillFlowContainer textSprites; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 6c65d491af89..3dd03fcea61b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -45,7 +45,7 @@ public IEnumerable Scores } private GetScoresRequest getScoresRequest; - private APIAccess api; + private IAPIProvider api; public BeatmapInfo Beatmap { @@ -129,7 +129,7 @@ public ScoresContainer() } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; updateDisplay(); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 55c21b7fc933..c49268bc16b3 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -31,7 +31,7 @@ public class BeatmapSetOverlay : WaveOverlayContainer private readonly Header header; - private APIAccess api; + private IAPIProvider api; private RulesetStore rulesets; private readonly ScrollContainer scroll; @@ -101,7 +101,7 @@ public BeatmapSetOverlay() } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { this.api = api; this.rulesets = rulesets; diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index a3d25a7a1c56..34edbbcc8b5d 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -28,7 +28,7 @@ public class DirectOverlay : SearchableListOverlay User = new Bindable(); - protected APIAccess Api; + protected IAPIProvider Api; protected APIRequest RetrievalRequest; protected RulesetStore Rulesets; @@ -84,7 +84,7 @@ public PaginatedContainer(Bindable user, string header, string missing) } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { Api = api; Rulesets = rulesets; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 7e721ac807de..8fab29e42c3b 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public class DrawableRecentActivity : DrawableProfileRow { - private APIAccess api; + private IAPIProvider api; private readonly APIRecentActivity activity; @@ -28,7 +28,7 @@ public DrawableRecentActivity(APIRecentActivity activity) } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 026424e1fffd..d6738250f9a6 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -58,14 +58,14 @@ public LoginSettings() } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { this.colours = colours; api?.Register(this); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { form = null; @@ -194,7 +194,7 @@ private class LoginForm : FillFlowContainer { private TextBox username; private TextBox password; - private APIAccess api; + private IAPIProvider api; public Action RequestHide; @@ -205,7 +205,7 @@ private void performLogin() } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config, AccountCreationOverlay accountCreation) + private void load(IAPIProvider api, OsuConfigManager config, AccountCreationOverlay accountCreation) { this.api = api; Direction = FillDirection.Vertical; diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 3464058abba1..daf3d1c576a7 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays { public class SocialOverlay : SearchableListOverlay, IOnlineComponent { - private APIAccess api; + private IAPIProvider api; private readonly LoadingAnimation loading; private FillFlowContainer panels; @@ -89,7 +89,7 @@ public SocialOverlay() } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; api.Register(this); @@ -193,7 +193,7 @@ private void clearPanels() } } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index d47f3a7b16b5..8d1910fc19da 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -43,12 +43,12 @@ public ToolbarUserButton() } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { api.Register(this); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 1ff1c2ce9192..48ce05597566 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -26,7 +26,7 @@ public class UserProfileOverlay : WaveOverlayContainer private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - private APIAccess api; + private IAPIProvider api; protected ProfileHeader Header; private SectionsContainer sectionsContainer; private ProfileTabControl tabs; @@ -56,7 +56,7 @@ public UserProfileOverlay() } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1f6881866991..3df4ef9059f6 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -97,7 +97,7 @@ public ButtonSystem() private OsuGame game { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 89f4f9209295..7a80a686b18a 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -44,7 +44,7 @@ public Disclaimer() } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index fd9c8d7b3538..5798fce4577a 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -246,7 +246,7 @@ private void load() } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } private GetRoomScoresRequest request; diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index dd01ae416050..f38fc4e3ffbe 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -54,7 +54,7 @@ public class Multiplayer : OsuScreen, IOnlineComponent private OsuGameBase game { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved(CanBeNull = true)] private OsuLogo logo { get; set; } @@ -163,7 +163,7 @@ public void Start(Func player) this.Push(new PlayerLoader(player)); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { if (state != APIState.Online) forcefullyExit(); diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 6b88403b9e2a..d5b8f1f0c8c8 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -32,7 +32,7 @@ public class TimeshiftPlayer : Player private readonly PlaylistItem playlistItem; [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private IBindable ruleset { get; set; } diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index c15a8471a135..385cbe20e5ed 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -30,7 +30,7 @@ public class RoomManager : PollingComponent, IRoomManager private Bindable currentFilter { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [Resolved] private RulesetStore rulesets { get; set; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ced0a43679d4..b11c5e51c9e3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -60,7 +60,7 @@ public class Player : ScreenWithBeatmapBackground private RulesetInfo ruleset; - private APIAccess api; + private IAPIProvider api; private SampleChannel sampleRestart; @@ -85,7 +85,7 @@ public class Player : ScreenWithBeatmapBackground private GameplayClockContainer gameplayClockContainer; [BackgroundDependencyLoader] - private void load(AudioManager audio, APIAccess api, OsuConfigManager config) + private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) { this.api = api; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 604d7a132b49..751ff164e21d 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -36,7 +36,7 @@ public class BeatmapDetails : Container private readonly FailRetryGraph failRetryGraph; private readonly DimmedLoadingAnimation loading; - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingBeatmapSwitch; @@ -160,7 +160,7 @@ public BeatmapDetails() } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 61370f7ce3fd..ebb1d78ba053 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -43,7 +43,7 @@ public BeatmapInfo Beatmap private IBindable ruleset { get; set; } [Resolved] - private APIAccess api { get; set; } + private IAPIProvider api { get; set; } [BackgroundDependencyLoader] private void load() From 6aa3dc9f554e0d41dd42fc01c4115597574b05b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 13 Mar 2019 18:56:48 +0900 Subject: [PATCH 02/20] Use non-real domain Co-Authored-By: peppy --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index af2fe51b38c5..be3859b1eb7d 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -18,7 +18,7 @@ public class DummyAPIAccess : IAPIProvider public string ProvidedUsername => LocalUser.Value.Username; - public string Endpoint => "https://test.com"; + public string Endpoint => "http://localhost"; public APIState State => LocalUser.Value.Id == 1 ? APIState.Offline : APIState.Online; From 7d637691d77ccd7d490beff037ee1166daf022f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Mar 2019 19:01:42 +0900 Subject: [PATCH 03/20] Use non-guest user ID for non-guest user --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index be3859b1eb7d..0cb49951f7e5 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -41,7 +41,7 @@ public void Login(string username, string password) LocalUser.Value = new User { Username = @"Dummy", - Id = 1, + Id = 1001, }; } From 8c6caf0b18383b015a79f14976b668270c00214e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2019 01:03:13 +0900 Subject: [PATCH 04/20] Update framework --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e2c96effb60d..c56d50ae15f0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -16,7 +16,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 288630d1ec63..fedc20397d8c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -105,8 +105,8 @@ - - + + From 771d676ba1a8baed1661985929598598893b5a33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2019 13:47:11 +0900 Subject: [PATCH 05/20] Split RulesetInputManager out to FrameStabilityContainer --- .../Rulesets/UI/FrameStabilityContainer.cs | 161 ++++++++++++++++++ osu.Game/Rulesets/UI/RulesetContainer.cs | 14 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 140 +-------------- 3 files changed, 178 insertions(+), 137 deletions(-) create mode 100644 osu.Game/Rulesets/UI/FrameStabilityContainer.cs diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs new file mode 100644 index 000000000000..921ed46d0e07 --- /dev/null +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Input.Handlers; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which consumes a parent gameplay clock and standardises frame counts for children. + /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. + /// + public class FrameStabilityContainer : Container, IHasReplayHandler + { + public FrameStabilityContainer() + { + RelativeSizeAxes = Axes.Both; + gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + } + + #region Clock control + + private readonly ManualClock manualClock; + + private readonly FramedClock framedClock; + + [Cached] + private GameplayClock gameplayClock; + + private IFrameBasedClock parentGameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) + parentGameplayClock = clock; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + setClock(); + } + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + private bool requireMoreUpdateLoops; + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + private bool validState; + + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + + private bool isAttached => ReplayInputHandler != null; + + private const int max_catch_up_updates_per_frame = 50; + + private const double sixty_frame_time = 1000.0 / 60; + + public override bool UpdateSubTree() + { + requireMoreUpdateLoops = true; + validState = true; + + int loops = 0; + + while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + { + updateClock(); + + if (validState) + { + base.UpdateSubTree(); + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); + } + } + + return true; + } + + private void updateClock() + { + if (parentGameplayClock == null) + setClock(); // LoadComplete may not be run yet, but we still want the clock. + + validState = true; + + manualClock.Rate = parentGameplayClock.Rate; + manualClock.IsRunning = parentGameplayClock.IsRunning; + + var newProposedTime = parentGameplayClock.CurrentTime; + + try + { + if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = manualClock.Rate > 0 + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + } + + if (!isAttached) + { + manualClock.CurrentTime = newProposedTime; + } + else + { + double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); + + if (newTime == null) + { + // we shouldn't execute for this time value. probably waiting on more replay data. + validState = false; + + requireMoreUpdateLoops = true; + manualClock.CurrentTime = newProposedTime; + return; + } + + manualClock.CurrentTime = newTime.Value; + } + + requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; + } + finally + { + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + framedClock.ProcessFrame(); + } + } + + private void setClock() + { + // in case a parent gameplay clock isn't available, just use the parent clock. + if (parentGameplayClock == null) + parentGameplayClock = Clock; + + Clock = gameplayClock; + ProcessCustomClock = false; + } + + #endregion + + #region IHasReplayHandler + + public ReplayInputHandler ReplayInputHandler { get; set; } + + #endregion + } +} diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index ed5f23dc7f37..d8813631dcd1 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -132,6 +132,8 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + protected FrameStabilityContainer FrameStabilityContainer; + public Score ReplayScore { get; private set; } /// @@ -149,7 +151,11 @@ public virtual void SetReplayScore(Score replayScore) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); ReplayScore = replayScore; - ReplayInputManager.ReplayInputHandler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + var handler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + ReplayInputManager.ReplayInputHandler = handler; + FrameStabilityContainer.ReplayInputHandler = handler; HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; } @@ -243,7 +249,6 @@ protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); KeyBindingInputManager = CreateInputManager(); - KeyBindingInputManager.RelativeSizeAxes = Axes.Both; applyBeatmapMods(Mods); } @@ -262,7 +267,10 @@ private void load(OsuConfigManager config) InternalChildren = new Drawable[] { - KeyBindingInputManager, + FrameStabilityContainer = new FrameStabilityContainer + { + Child = KeyBindingInputManager, + }, Overlays = new Container { RelativeSizeAxes = Axes.Both } }; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 87220a37e874..e3031667740e 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; @@ -41,7 +39,12 @@ protected override InputState CreateInitialState() protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); - gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config) + { + mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } #region Action mapping (for replays) @@ -85,137 +88,6 @@ public ReplayInputHandler ReplayInputHandler #endregion - #region Clock control - - private readonly ManualClock manualClock; - - private readonly FramedClock framedClock; - - [Cached] - private GameplayClock gameplayClock; - - private IFrameBasedClock parentGameplayClock; - - [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, GameplayClock clock) - { - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - - if (clock != null) - parentGameplayClock = clock; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - setClock(); - } - - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; - - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - - private bool isAttached => replayInputHandler != null && !UseParentInput; - - private const int max_catch_up_updates_per_frame = 50; - - private const double sixty_frame_time = 1000.0 / 60; - - public override bool UpdateSubTree() - { - requireMoreUpdateLoops = true; - validState = true; - - int loops = 0; - - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) - { - updateClock(); - - if (validState) - { - base.UpdateSubTree(); - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } - } - - return true; - } - - private void updateClock() - { - if (parentGameplayClock == null) - setClock(); // LoadComplete may not be run yet, but we still want the clock. - - validState = true; - - manualClock.Rate = parentGameplayClock.Rate; - manualClock.IsRunning = parentGameplayClock.IsRunning; - - var newProposedTime = parentGameplayClock.CurrentTime; - - try - { - if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) - { - newProposedTime = manualClock.Rate > 0 - ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); - } - - if (!isAttached) - { - manualClock.CurrentTime = newProposedTime; - } - else - { - double? newTime = replayInputHandler.SetFrameFromTime(newProposedTime); - - if (newTime == null) - { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - - requireMoreUpdateLoops = true; - manualClock.CurrentTime = newProposedTime; - return; - } - - manualClock.CurrentTime = newTime.Value; - } - - requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; - } - finally - { - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - framedClock.ProcessFrame(); - } - } - - private void setClock() - { - // in case a parent gameplay clock isn't available, just use the parent clock. - if (parentGameplayClock == null) - parentGameplayClock = Clock; - - Clock = gameplayClock; - ProcessCustomClock = false; - } - - #endregion - #region Setting application (disables etc.) private Bindable mouseDisabled; From c496f6e56bc6440ba91f12a52971f4e8f4b4c65e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Mar 2019 13:43:23 +0900 Subject: [PATCH 06/20] Fix usages of OnLoadComplete --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 2 +- .../UpdateableBeatmapBackgroundSprite.cs | 2 +- .../Drawables/UpdateableBeatmapSetCover.cs | 7 +++++-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 7 +++++-- .../Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 7 +++++-- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 7 +++++-- .../Multi/Match/Components/Participants.cs | 16 +++++++++++----- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 11 ++++++++--- osu.Game/Users/UpdateableAvatar.cs | 2 +- osu.Game/Users/UserPanel.cs | 7 +++++-- 12 files changed, 49 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index d0f50c6af2af..c6dd0a86a019 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -60,7 +60,7 @@ void runAfterLoaded(Action action) if (lastPlateableFruit.IsLoaded) action(); else - lastPlateableFruit.OnLoadComplete = _ => action(); + lastPlateableFruit.OnLoadComplete += _ => action(); } if (result.IsHit && fruit.CanBePlated) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index ec75c1a1fbe4..ce7811fc0d1e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -51,7 +51,7 @@ protected override Drawable CreateDrawable(BeatmapInfo model) drawable.Anchor = Anchor.Centre; drawable.Origin = Anchor.Centre; drawable.FillMode = FillMode.Fill; - drawable.OnLoadComplete = d => d.FadeInFromZero(400); + drawable.OnLoadComplete += d => d.FadeInFromZero(400); return drawable; } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 367b63d6d188..c7c4c1fb1e40 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -67,16 +67,19 @@ private void updateCover() if (beatmapSet != null) { + BeatmapSetCover cover; + Add(displayedCover = new DelayedLoadWrapper( - new BeatmapSetCover(beatmapSet, coverType) + cover = new BeatmapSetCover(beatmapSet, coverType) { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), }) ); + + cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 34981bf849b4..c5602fc4ad75 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -64,6 +64,8 @@ private void load() statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + Avatar innerAvatar; + Children = new Drawable[] { new Container @@ -109,12 +111,11 @@ private void load() Children = new[] { avatar = new DelayedLoadWrapper( - new Avatar(user) + innerAvatar = new Avatar(user) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, Masking = true, - OnLoadComplete = d => d.FadeInFromZero(200), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -214,6 +215,8 @@ private void load() }, }, }; + + innerAvatar.OnLoadComplete += d => d.FadeInFromZero(200); } public override void Show() diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 8cedde82ff05..8111ac739433 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -28,6 +28,8 @@ public PrivateChannelTabItem(Channel value) if (value.Type != ChannelType.PM) throw new ArgumentException("Argument value needs to have the targettype user!"); + Avatar avatar; + AddRange(new Drawable[] { new Container @@ -49,11 +51,10 @@ public PrivateChannelTabItem(Channel value) Anchor = Anchor.Centre, Origin = Anchor.Centre, Masking = true, - Child = new DelayedLoadWrapper(new Avatar(value.Users.First()) + Child = new DelayedLoadWrapper(avatar = new Avatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, OpenOnClick = { Value = false }, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }) { RelativeSizeAxes = Axes.Both, @@ -63,6 +64,8 @@ public PrivateChannelTabItem(Channel value) }, }); + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; } diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index eeb42ec99192..431ae98c2c0c 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -109,7 +109,7 @@ public DrawableMedal(Medal medal) s.Font = s.Font.With(size: 16); }); - medalContainer.OnLoadComplete = d => + medalContainer.OnLoadComplete += d => { unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2641a0551dcf..c41d97770148 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -335,9 +335,12 @@ private void loadUser() Anchor = Anchor.Centre, Origin = Anchor.Centre, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), Depth = float.MaxValue, - }, coverContainer.Add); + }, background => + { + coverContainer.Add(background); + background.FadeInFromZero(200); + }); if (user.IsSupporter) SupporterTag.Show(); diff --git a/osu.Game/Screens/Multi/Match/Components/Participants.cs b/osu.Game/Screens/Multi/Match/Components/Participants.cs index 2d6099c65d92..09d25572ecab 100644 --- a/osu.Game/Screens/Multi/Match/Components/Participants.cs +++ b/osu.Game/Screens/Multi/Match/Components/Participants.cs @@ -52,12 +52,18 @@ private void load() Participants.BindValueChanged(participants => { - usersFlow.Children = participants.NewValue.Select(u => new UserPanel(u) + usersFlow.Children = participants.NewValue.Select(u => { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 300, - OnLoadComplete = d => d.FadeInFromZero(60), + var panel = new UserPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + }; + + panel.OnLoadComplete += d => d.FadeInFromZero(60); + + return panel; }).ToList(); }, true); } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 389f614ef2cf..bfd1d3d23662 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -577,7 +577,7 @@ void performMove(float y, float? startY = null) else { float y = currentY; - d.OnLoadComplete = _ => performMove(y, setY); + d.OnLoadComplete += _ => performMove(y, setY); } break; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index e01149ebc80c..51ca9902d232 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -49,11 +49,16 @@ private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay, Dial Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => - new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + { + var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint), - }, 300, 5000 + }; + + background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return background; + }, 300, 5000 ), new FillFlowContainer { diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index cefb91797b01..725946867415 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -57,9 +57,9 @@ private void updateAvatar() var avatar = new Avatar(user) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }; + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); avatar.OpenOnClick.BindTo(OpenOnClick); Add(displayedAvatar = new DelayedLoadWrapper(avatar)); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 4dfde04e0705..65062dc58ea7 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -59,6 +59,8 @@ private void load(OsuColour colours, UserProfileOverlay profile) FillFlowContainer infoContainer; + UserCoverBackground coverBackground; + AddInternal(content = new Container { RelativeSizeAxes = Axes.Both, @@ -73,13 +75,12 @@ private void load(OsuColour colours, UserProfileOverlay profile) Children = new Drawable[] { - new DelayedLoadWrapper(new UserCoverBackground(user) + new DelayedLoadWrapper(coverBackground = new UserCoverBackground(user) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) }, 300) { RelativeSizeAxes = Axes.Both }, new Box { @@ -181,6 +182,8 @@ private void load(OsuColour colours, UserProfileOverlay profile) } }); + coverBackground.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); + if (user.IsSupporter) { infoContainer.Add(new SupporterIcon From 9ca4d9d4d1acff69ae673621b7fdcec0b9b92178 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 10:48:44 +0900 Subject: [PATCH 07/20] Remove regions --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 921ed46d0e07..161e7aecb4e9 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -23,8 +23,6 @@ public FrameStabilityContainer() gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); } - #region Clock control - private readonly ManualClock manualClock; private readonly FramedClock framedClock; @@ -150,12 +148,6 @@ private void setClock() ProcessCustomClock = false; } - #endregion - - #region IHasReplayHandler - public ReplayInputHandler ReplayInputHandler { get; set; } - - #endregion } } From 8df47bc23edd9c63c8c008c55f4247c1ae402009 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 16:39:34 +0900 Subject: [PATCH 08/20] Increase flexibility of player test cases --- .../TestCaseAutoJuiceStream.cs | 2 +- .../TestCaseBananaShower.cs | 3 +- .../TestCaseCatchPlayer.cs | 3 +- .../TestCaseCatchStacker.cs | 3 +- .../TestCaseHyperDash.cs | 20 +-- .../TestCaseHitCircleLongCombo.cs | 3 +- .../TestCaseOsuPlayer.cs | 3 +- osu.Game.Tests/Visual/TestCaseAllPlayers.cs | 12 -- osu.Game.Tests/Visual/TestCaseAutoplay.cs | 10 +- .../Visual/TestCasePlayerReferenceLeaking.cs | 56 ++++++++ osu.Game.Tests/Visual/TestCaseReplay.cs | 10 +- osu.Game/Tests/Visual/AllPlayersTestCase.cs | 94 +++++++++++++ osu.Game/Tests/Visual/PlayerTestCase.cs | 60 ++++++++ osu.Game/Tests/Visual/TestCasePlayer.cs | 131 ------------------ 14 files changed, 239 insertions(+), 171 deletions(-) delete mode 100644 osu.Game.Tests/Visual/TestCaseAllPlayers.cs create mode 100644 osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs create mode 100644 osu.Game/Tests/Visual/AllPlayersTestCase.cs create mode 100644 osu.Game/Tests/Visual/PlayerTestCase.cs delete mode 100644 osu.Game/Tests/Visual/TestCasePlayer.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 9319fb3dfba6..fbb2db33b0ca 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseAutoJuiceStream : TestCasePlayer + public class TestCaseAutoJuiceStream : PlayerTestCase { public TestCaseAutoJuiceStream() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs index 9e1c44ba40b0..1bae55102f11 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs @@ -8,11 +8,12 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + public class TestCaseBananaShower : PlayerTestCase { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs index 8f9dd73b80f5..5b242d05d7c9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchPlayer : PlayerTestCase { public TestCaseCatchPlayer() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs index 1e3d60d9687e..5a16a23a4e6c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs @@ -4,11 +4,12 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + public class TestCaseCatchStacker : PlayerTestCase { public TestCaseCatchStacker() : base(new CatchRuleset()) diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs index 7451986a8b64..a7e7f0ab14c6 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs @@ -1,22 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer + public class TestCaseHyperDash : PlayerTestCase { public TestCaseHyperDash() : base(new CatchRuleset()) { } + [BackgroundDependencyLoader] + private void load() + { + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + } + protected override IBeatmap CreateBeatmap(Ruleset ruleset) { var beatmap = new Beatmap @@ -28,7 +34,7 @@ protected override IBeatmap CreateBeatmap(Ruleset ruleset) } }; - // Should produce a hperdash + // Should produce a hyper-dash beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true }); beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, }); @@ -38,11 +44,5 @@ protected override IBeatmap CreateBeatmap(Ruleset ruleset) return beatmap; } - - protected override void AddCheckSteps(Func player) - { - base.AddCheckSteps(player); - AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs index f5fe36b56a57..8d097ff1c1e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs @@ -4,12 +4,13 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer + public class TestCaseHitCircleLongCombo : PlayerTestCase { public TestCaseHitCircleLongCombo() : base(new OsuRuleset()) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs index 9f13c193909e..720c3c66fe2f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseOsuPlayer : Game.Tests.Visual.TestCasePlayer + public class TestCaseOsuPlayer : PlayerTestCase { public TestCaseOsuPlayer() : base(new OsuRuleset()) diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs deleted file mode 100644 index a5decaa9fb24..000000000000 --- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseAllPlayers : TestCasePlayer - { - } -} diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index 61339a6af8d2..c25088f8a447 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -11,7 +10,7 @@ namespace osu.Game.Tests.Visual { [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : TestCasePlayer + public class TestCaseAutoplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { @@ -24,11 +23,10 @@ protected override Player CreatePlayer(Ruleset ruleset) }; } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep(() => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0, "score above zero"); + AddUntilStep(() => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); } private class ScoreAccessiblePlayer : Player diff --git a/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs new file mode 100644 index 000000000000..04fb863f18dc --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePlayerReferenceLeaking.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Lists; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePlayerReferenceLeaking : AllPlayersTestCase + { + private readonly WeakList workingWeakReferences = new WeakList(); + + private readonly WeakList playerWeakReferences = new WeakList(); + + protected override void AddCheckSteps() + { + AddUntilStep(() => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + workingWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }, "no leaked beatmaps"); + + AddUntilStep(() => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + playerWeakReferences.ForEachAlive(_ => count++); + return count == 1; + }, "no leaked players"); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) + { + var working = base.CreateWorkingBeatmap(beatmap, clock); + workingWeakReferences.Add(working); + return working; + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + var player = base.CreatePlayer(ruleset); + playerWeakReferences.Add(player); + return player; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index c34190d56710..93a20b9425e2 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; @@ -12,7 +11,7 @@ namespace osu.Game.Tests.Visual { [Description("Player instantiated with a replay.")] - public class TestCaseReplay : TestCasePlayer + public class TestCaseReplay : AllPlayersTestCase { protected override Player CreatePlayer(Ruleset ruleset) { @@ -21,11 +20,10 @@ protected override Player CreatePlayer(Ruleset ruleset) return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); } - protected override void AddCheckSteps(Func player) + protected override void AddCheckSteps() { - base.AddCheckSteps(player); - AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddUntilStep(() => ((ScoreAccessibleReplayPlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); + AddUntilStep(() => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0, "score above zero"); + AddUntilStep(() => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys"); } private class ScoreAccessibleReplayPlayer : ReplayPlayer diff --git a/osu.Game/Tests/Visual/AllPlayersTestCase.cs b/osu.Game/Tests/Visual/AllPlayersTestCase.cs new file mode 100644 index 000000000000..507848730f0e --- /dev/null +++ b/osu.Game/Tests/Visual/AllPlayersTestCase.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + /// + /// A base class which runs test for all available rulesets. + /// Steps to be run for each ruleset should be added via . + /// + public abstract class AllPlayersTestCase : RateAdjustedBeatmapTestCase + { + protected Player Player; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Add(new Box + { + RelativeSizeAxes = Framework.Graphics.Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + foreach (var r in rulesets.AvailableRulesets) + { + Player p = null; + AddStep(r.Name, () => p = loadPlayerFor(r)); + AddUntilStep(() => + { + if (p?.IsLoaded == true) + { + p = null; + return true; + } + + return false; + }, "player loaded"); + + AddCheckSteps(); + } + } + + protected abstract void AddCheckSteps(); + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock clock) => + new TestWorkingBeatmap(beatmap, Clock); + + private Player loadPlayerFor(RulesetInfo ri) + { + Ruleset.Value = ri; + var r = ri.CreateInstance(); + + var beatmap = CreateBeatmap(r); + var working = CreateWorkingBeatmap(beatmap, Clock); + + Beatmap.Value = working; + Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + + Player?.Exit(); + Player = null; + + var player = CreatePlayer(r); + + LoadComponentAsync(player, p => + { + Player = p; + LoadScreen(p); + }); + + return player; + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } +} diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs new file mode 100644 index 000000000000..cdf53a362b40 --- /dev/null +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public abstract class PlayerTestCase : RateAdjustedBeatmapTestCase + { + private readonly Ruleset ruleset; + + protected Player Player; + + protected PlayerTestCase(Ruleset ruleset) + { + this.ruleset = ruleset; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Depth = int.MaxValue + }); + + AddStep(ruleset.RulesetInfo.Name, loadPlayer); + AddUntilStep(() => Player.IsLoaded, "player loaded"); + } + + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + + private void loadPlayer() + { + var beatmap = CreateBeatmap(ruleset); + + Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); + Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + LoadComponentAsync(Player = CreatePlayer(ruleset), p => + { + Player = p; + LoadScreen(p); + }); + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } +} diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs deleted file mode 100644 index 5ff798c40d51..000000000000 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; -using osu.Framework.Screens; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - public abstract class TestCasePlayer : RateAdjustedBeatmapTestCase - { - private readonly Ruleset ruleset; - - protected Player Player; - - protected TestCasePlayer(Ruleset ruleset) - { - this.ruleset = ruleset; - } - - protected TestCasePlayer() - { - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - - if (ruleset != null) - { - Player p = null; - AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset)); - AddCheckSteps(() => p); - } - else - { - foreach (var r in rulesets.AvailableRulesets) - { - Player p = null; - AddStep(r.Name, () => p = loadPlayerFor(r)); - AddCheckSteps(() => p); - - AddUntilStep(() => - { - p = null; - - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - workingWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked beatmaps"); - - AddUntilStep(() => - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - playerWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked players"); - } - } - } - - protected virtual void AddCheckSteps(Func player) - { - AddUntilStep(() => player().IsLoaded, "player loaded"); - } - - protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); - - private readonly WeakList workingWeakReferences = new WeakList(); - private readonly WeakList playerWeakReferences = new WeakList(); - - private Player loadPlayerFor(RulesetInfo ri) - { - Ruleset.Value = ri; - return loadPlayerFor(ri.CreateInstance()); - } - - private Player loadPlayerFor(Ruleset r) - { - var beatmap = CreateBeatmap(r); - var working = new TestWorkingBeatmap(beatmap, Clock); - - workingWeakReferences.Add(working); - - Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; - - Player?.Exit(); - - var player = CreatePlayer(r); - - playerWeakReferences.Add(player); - - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); - - return player; - } - - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; - } -} From a81461ba1222a4ba232591fab088c9b3a34a110b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 19:43:55 +0900 Subject: [PATCH 09/20] Add ability to test without nofail enabled --- osu.Game/Tests/Visual/PlayerTestCase.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index cdf53a362b40..4833d0ba0fc1 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; @@ -36,12 +36,16 @@ protected PlayerTestCase(Ruleset ruleset) protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); + protected virtual bool AllowFail => false; + private void loadPlayer() { var beatmap = CreateBeatmap(ruleset); Beatmap.Value = new TestWorkingBeatmap(beatmap, Clock); - Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + if (!AllowFail) + Beatmap.Value.Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; LoadComponentAsync(Player = CreatePlayer(ruleset), p => { From 15dd132c92e13ee2fb41f723a270d65a441d8d89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 19:44:14 +0900 Subject: [PATCH 10/20] Use SetUpSteps attribute --- osu.Game/Tests/Visual/PlayerTestCase.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestCase.cs b/osu.Game/Tests/Visual/PlayerTestCase.cs index 4833d0ba0fc1..ad01d8228146 100644 --- a/osu.Game/Tests/Visual/PlayerTestCase.cs +++ b/osu.Game/Tests/Visual/PlayerTestCase.cs @@ -1,9 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -29,7 +30,11 @@ protected PlayerTestCase(Ruleset ruleset) Colour = Color4.Black, Depth = int.MaxValue }); + } + [SetUpSteps] + public void SetUpSteps() + { AddStep(ruleset.RulesetInfo.Name, loadPlayer); AddUntilStep(() => Player.IsLoaded, "player loaded"); } From 465c95e952cceb7ff62b496861728b6d81e4b5b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Mar 2019 14:20:10 +0900 Subject: [PATCH 11/20] Refactor pause logic so GameplayClockContainer is in control --- .../Screens/Play/GameplayClockContainer.cs | 7 ++++- .../Screens/Play/PausableGameplayContainer.cs | 26 ++++++++---------- osu.Game/Screens/Play/Player.cs | 27 +++++++++++-------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3c2cec1d946b..1e4c9aba9276 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -118,11 +118,16 @@ public void Start() // This accounts for the audio clock source potentially taking time to enter a completely stopped state adjustableClock.Seek(adjustableClock.CurrentTime); adjustableClock.Start(); + IsPaused.Value = false; } public void Seek(double time) => adjustableClock.Seek(time); - public void Stop() => adjustableClock.Stop(); + public void Stop() + { + adjustableClock.Stop(); + IsPaused.Value = true; + } public void ResetLocalAdjustments() { diff --git a/osu.Game/Screens/Play/PausableGameplayContainer.cs b/osu.Game/Screens/Play/PausableGameplayContainer.cs index 99f0083b5588..6363b92a8f24 100644 --- a/osu.Game/Screens/Play/PausableGameplayContainer.cs +++ b/osu.Game/Screens/Play/PausableGameplayContainer.cs @@ -41,8 +41,8 @@ public int Retries public Action OnRetry; public Action OnQuit; - public Action Stop; - public Action Start; + public Action RequestPause; + public Action RequestResume; /// /// Creates a new . @@ -70,15 +70,12 @@ public PausableGameplayContainer() }; } - public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. + public void Pause() => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. { - if (!CanPause && !force) return; - - if (IsPaused.Value) return; + if (!CanPause) return; // stop the seekable clock (stops the audio eventually) - Stop?.Invoke(); - IsPaused.Value = true; + RequestPause?.Invoke(); pauseOverlay.Show(); @@ -89,14 +86,13 @@ public void Resume() { if (!IsPaused.Value) return; - IsResuming = false; - lastPauseActionTime = Time.Current; - - IsPaused.Value = false; - - Start?.Invoke(); - pauseOverlay.Hide(); + + RequestResume?.Invoke(() => + { + IsResuming = false; + lastPauseActionTime = Time.Current; + }); } private OsuGameBase game; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b11c5e51c9e3..7637cfe869d0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -111,10 +111,14 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) Retries = RestartCount, OnRetry = restart, OnQuit = performUserRequestedExit, - Start = gameplayClockContainer.Start, - Stop = gameplayClockContainer.Stop, + RequestResume = completion => + { + gameplayClockContainer.Start(); + completion(); + }, + RequestPause = gameplayClockContainer.Stop, IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, + CheckCanPause = () => CanPause, Children = new[] { StoryboardContainer = CreateStoryboardContainer(), @@ -337,6 +341,9 @@ public override void OnSuspending(IScreen next) base.OnSuspending(next); } + public bool CanPause => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value + && (PausableGameplayContainer?.IsPaused.Value == false || PausableGameplayContainer?.IsResuming == true); + public override bool OnExiting(IScreen next) { if (onCompletionEvent != null) @@ -346,18 +353,16 @@ public override bool OnExiting(IScreen next) return true; } - if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true)) + if (LoadedBeatmapSuccessfully && CanPause) { - gameplayClockContainer.ResetLocalAdjustments(); - - fadeOut(); - return base.OnExiting(next); + PausableGameplayContainer?.Pause(); + return true; } - if (LoadedBeatmapSuccessfully) - PausableGameplayContainer?.Pause(); + gameplayClockContainer.ResetLocalAdjustments(); - return true; + fadeOut(); + return base.OnExiting(next); } private void fadeOut(bool instant = false) From bcaff9f7b47efdc3c0087609daef32e48110e1ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 00:46:15 +0900 Subject: [PATCH 12/20] Add basic pause tests --- osu.Game.Tests/Visual/TestCasePause.cs | 47 +++++++++++++++++++ .../Screens/Play/GameplayClockContainer.cs | 6 +-- osu.Game/Screens/Play/Player.cs | 26 +++++----- 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCasePause.cs diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs new file mode 100644 index 000000000000..6966eb3de99b --- /dev/null +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestCasePause : TestCasePlayer + { + public TestCasePause() + : base(new OsuRuleset()) + { + } + + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + protected override void AddCheckSteps(Func player) + { + PausePlayer pausable() => (PausePlayer)player(); + + base.AddCheckSteps(player); + //AddUntilStep(() => pausable().ScoreProcessor.TotalScore.Value > 0, "score above zero"); + + AddStep("pause", () => pausable().PausableGameplayContainer.Pause()); + AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); + + AddStep("resume", () => pausable().PausableGameplayContainer.Resume()); + AddUntilStep(() => pausable().GameplayClockContainer.GameplayClock.IsRunning, "clock started"); + + AddStep("pause too soon", () => pausable().PausableGameplayContainer.Pause()); + AddAssert("clock not stopped", () => pausable().GameplayClockContainer.GameplayClock.IsRunning); + } + + private class PausePlayer : Player + { + public new PausableGameplayContainer PausableGameplayContainer => base.PausableGameplayContainer; + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + } + } +} diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 1e4c9aba9276..deac5e02bf9b 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play { /// - /// Encapsulates gameplay timing logic and provides a for children. + /// Encapsulates gameplay timing logic and provides a for children. /// public class GameplayClockContainer : Container { @@ -48,7 +48,7 @@ public class GameplayClockContainer : Container /// The final clock which is exposed to underlying components. /// [Cached] - private readonly GameplayClock gameplayClock; + public readonly GameplayClock GameplayClock; private Bindable userAudioOffset; @@ -78,7 +78,7 @@ public GameplayClockContainer(WorkingBeatmap beatmap, bool allowLeadIn, double g offsetClock = new FramedOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - gameplayClock = new GameplayClock(offsetClock); + GameplayClock = new GameplayClock(offsetClock); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7637cfe869d0..3d60a44c85bf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -82,7 +82,7 @@ public class Player : ScreenWithBeatmapBackground public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; - private GameplayClockContainer gameplayClockContainer; + protected GameplayClockContainer GameplayClockContainer { get; private set; } [BackgroundDependencyLoader] private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) @@ -102,9 +102,9 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) if (!ScoreProcessor.Mode.Disabled) config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); - gameplayClockContainer.Children = new Drawable[] + GameplayClockContainer.Children = new Drawable[] { PausableGameplayContainer = new PausableGameplayContainer { @@ -113,11 +113,11 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) OnQuit = performUserRequestedExit, RequestResume = completion => { - gameplayClockContainer.Start(); + GameplayClockContainer.Start(); completion(); }, - RequestPause = gameplayClockContainer.Stop, - IsPaused = { BindTarget = gameplayClockContainer.IsPaused }, + RequestPause = GameplayClockContainer.Stop, + IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, CheckCanPause = () => CanPause, Children = new[] { @@ -141,15 +141,15 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) { HoldToQuit = { Action = performUserRequestedExit }, - PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = gameplayClockContainer.UserPlaybackRate } } }, + PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, - RequestSeek = gameplayClockContainer.Seek, + RequestSeek = GameplayClockContainer.Seek, Anchor = Anchor.Centre, Origin = Anchor.Centre }, new SkipOverlay(RulesetContainer.GameplayStartTime) { - RequestSeek = gameplayClockContainer.Seek + RequestSeek = GameplayClockContainer.Seek }, } }, @@ -171,7 +171,7 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) }; // bind clock into components that require it - RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused); + RulesetContainer.IsPaused.BindTo(GameplayClockContainer.IsPaused); if (ShowStoryboard.Value) initializeStoryboard(false); @@ -295,7 +295,7 @@ private bool onFail() if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; - gameplayClockContainer.Stop(); + GameplayClockContainer.Stop(); HasFailed = true; failOverlay.Retries = RestartCount; @@ -329,7 +329,7 @@ public override void OnEntering(IScreen last) storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; - gameplayClockContainer.Restart(); + GameplayClockContainer.Restart(); PausableGameplayContainer.Alpha = 0; PausableGameplayContainer.FadeIn(750, Easing.OutQuint); @@ -359,7 +359,7 @@ public override bool OnExiting(IScreen next) return true; } - gameplayClockContainer.ResetLocalAdjustments(); + GameplayClockContainer.ResetLocalAdjustments(); fadeOut(); return base.OnExiting(next); From f13003c53b7957cf76be0042a85f7d99512349cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 11:12:47 +0900 Subject: [PATCH 13/20] Simplify and localise storyboard logic in Player.cs --- osu.Game/Screens/Play/Player.cs | 48 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d60a44c85bf..0e8bedefb09f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -70,9 +70,32 @@ public class Player : ScreenWithBeatmapBackground protected HUDOverlay HUDOverlay { get; private set; } private FailOverlay failOverlay; + #region Storyboard + private DrawableStoryboard storyboard; protected UserDimContainer StoryboardContainer { get; private set; } + private void initializeStoryboard(bool asyncLoad) + { + if (StoryboardContainer == null || storyboard != null) + return; + + if (!ShowStoryboard.Value) + return; + + var beatmap = Beatmap.Value; + + storyboard = beatmap.Storyboard.CreateDrawable(); + storyboard.Masking = true; + + if (asyncLoad) + LoadComponentAsync(storyboard, StoryboardContainer.Add); + else + StoryboardContainer.Add(storyboard); + } + + #endregion + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) { RelativeSizeAxes = Axes.Both, @@ -173,7 +196,7 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) // bind clock into components that require it RulesetContainer.IsPaused.BindTo(GameplayClockContainer.IsPaused); - if (ShowStoryboard.Value) + // load storyboard as part of player's load if we can initializeStoryboard(false); // Bind ScoreProcessor to ourselves @@ -317,10 +340,7 @@ public override void OnEntering(IScreen last) .Delay(250) .FadeIn(250); - ShowStoryboard.ValueChanged += enabled => - { - if (enabled.NewValue) initializeStoryboard(true); - }; + ShowStoryboard.ValueChanged += _ => initializeStoryboard(true); Background.EnableUserDim.Value = true; @@ -376,22 +396,6 @@ private void fadeOut(bool instant = false) protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value; - private void initializeStoryboard(bool asyncLoad) - { - if (StoryboardContainer == null || storyboard != null) - return; - - var beatmap = Beatmap.Value; - - storyboard = beatmap.Storyboard.CreateDrawable(); - storyboard.Masking = true; - - if (asyncLoad) - LoadComponentAsync(storyboard, StoryboardContainer.Add); - else - StoryboardContainer.Add(storyboard); - } - protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); } } From 536b5e0dab579ec7e55ee09d08397f30cfcb13b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 11:48:11 +0900 Subject: [PATCH 14/20] Remove PausableGameplayContainer --- .../Visual/TestCaseBackgroundScreenBeatmap.cs | 6 +- .../Visual/TestCaseGameplayMenuOverlay.cs | 6 +- osu.Game.Tests/Visual/TestCasePause.cs | 28 ++- .../Screens/Play/PausableGameplayContainer.cs | 133 ------------ osu.Game/Screens/Play/PauseOverlay.cs | 29 +++ osu.Game/Screens/Play/Player.cs | 190 ++++++++++++------ 6 files changed, 183 insertions(+), 209 deletions(-) delete mode 100644 osu.Game/Screens/Play/PausableGameplayContainer.cs create mode 100644 osu.Game/Screens/Play/PauseOverlay.cs diff --git a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs index 5484824c5b80..d62ae07f6ad8 100644 --- a/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs +++ b/osu.Game.Tests/Visual/TestCaseBackgroundScreenBeatmap.cs @@ -188,10 +188,10 @@ public void DisableUserDimTest() public void PauseTest() { performFullSetup(true); - AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause()); + AddStep("Pause", () => player.Pause()); waitForDim(); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); - AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume()); + AddStep("Unpause", () => player.Resume()); waitForDim(); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); } @@ -328,8 +328,6 @@ protected override UserDimContainer CreateStoryboardContainer() }; } - public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer; - public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; // Whether or not the player should be allowed to load. diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 93a059d214b3..c5ad57fec90a 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual [Description("player pause/fail screens")] public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PausableGameplayContainer) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; private FailOverlay failOverlay; - private PausableGameplayContainer.PauseOverlay pauseOverlay; + private PauseOverlay pauseOverlay; [BackgroundDependencyLoader] private void load() { - Add(pauseOverlay = new PausableGameplayContainer.PauseOverlay + Add(pauseOverlay = new PauseOverlay { OnResume = () => Logger.Log(@"Resume"), OnRetry = () => Logger.Log(@"Retry"), diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index 6966eb3de99b..622a12da8106 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -16,6 +17,8 @@ public TestCasePause() { } + protected override bool AllowFail => true; + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); protected override void AddCheckSteps(Func player) @@ -25,23 +28,36 @@ protected override void AddCheckSteps(Func player) base.AddCheckSteps(player); //AddUntilStep(() => pausable().ScoreProcessor.TotalScore.Value > 0, "score above zero"); - AddStep("pause", () => pausable().PausableGameplayContainer.Pause()); + AddStep("pause", () => pausable().Pause()); AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay shown", () => pausable().PauseOverlayVisible); - AddStep("resume", () => pausable().PausableGameplayContainer.Resume()); - AddUntilStep(() => pausable().GameplayClockContainer.GameplayClock.IsRunning, "clock started"); + AddStep("resume", () => pausable().Resume()); + AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - AddStep("pause too soon", () => pausable().PausableGameplayContainer.Pause()); + AddStep("pause too soon", () => pausable().Pause()); AddAssert("clock not stopped", () => pausable().GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); + + AddUntilStep(() => pausable().HasFailed, "wait for fail"); + + AddAssert("fail overlay shown", () => pausable().FailOverlayVisible); + + AddStep("try to pause", () => pausable().Pause()); + + AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); + AddAssert("fail overlay still shown", () => pausable().FailOverlayVisible); } private class PausePlayer : Player { - public new PausableGameplayContainer PausableGameplayContainer => base.PausableGameplayContainer; - public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible; + + public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible; } } } diff --git a/osu.Game/Screens/Play/PausableGameplayContainer.cs b/osu.Game/Screens/Play/PausableGameplayContainer.cs deleted file mode 100644 index 6363b92a8f24..000000000000 --- a/osu.Game/Screens/Play/PausableGameplayContainer.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - /// - /// A container which handles pausing children, displaying an overlay blocking its children during paused state. - /// - public class PausableGameplayContainer : Container - { - public readonly BindableBool IsPaused = new BindableBool(); - - public Func CheckCanPause; - - private const double pause_cooldown = 1000; - private double lastPauseActionTime; - - private readonly PauseOverlay pauseOverlay; - - private readonly Container content; - - protected override Container Content => content; - - public int Retries - { - set => pauseOverlay.Retries = value; - } - - public bool CanPause => (CheckCanPause?.Invoke() ?? true) && Time.Current >= lastPauseActionTime + pause_cooldown; - public bool IsResuming { get; private set; } - - public Action OnRetry; - public Action OnQuit; - - public Action RequestPause; - public Action RequestResume; - - /// - /// Creates a new . - /// - public PausableGameplayContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChildren = new[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both - }, - pauseOverlay = new PauseOverlay - { - OnResume = () => - { - IsResuming = true; - this.Delay(400).Schedule(Resume); - }, - OnRetry = () => OnRetry(), - OnQuit = () => OnQuit(), - } - }; - } - - public void Pause() => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. - { - if (!CanPause) return; - - // stop the seekable clock (stops the audio eventually) - RequestPause?.Invoke(); - - pauseOverlay.Show(); - - lastPauseActionTime = Time.Current; - }); - - public void Resume() - { - if (!IsPaused.Value) return; - - pauseOverlay.Hide(); - - RequestResume?.Invoke(() => - { - IsResuming = false; - lastPauseActionTime = Time.Current; - }); - } - - private OsuGameBase game; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - this.game = game; - } - - protected override void Update() - { - // eagerly pause when we lose window focus (if we are locally playing). - if (!game.IsActive.Value && CanPause) - Pause(); - - base.Update(); - } - - public class PauseOverlay : GameplayMenuOverlay - { - public Action OnResume; - - public override string Header => "paused"; - public override string Description => "you're not going to do what i think you're going to do, are ya?"; - - protected override Action BackAction => () => InternalButtons.Children.First().Click(); - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddButton("Continue", colours.Green, () => OnResume?.Invoke()); - AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); - AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - } - } - } -} diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs new file mode 100644 index 000000000000..6cc6027a037f --- /dev/null +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class PauseOverlay : GameplayMenuOverlay + { + public Action OnResume; + + public override string Header => "paused"; + public override string Description => "you're not going to do what i think you're going to do, are ya?"; + + protected override Action BackAction => () => InternalButtons.Children.First().Click(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddButton("Continue", colours.Green, () => OnResume?.Invoke()); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0e8bedefb09f..018ff900eedf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -56,8 +56,6 @@ public class Player : ScreenWithBeatmapBackground [Resolved] private ScoreManager scoreManager { get; set; } - protected PausableGameplayContainer PausableGameplayContainer { get; private set; } - private RulesetInfo ruleset; private IAPIProvider api; @@ -68,7 +66,6 @@ public class Player : ScreenWithBeatmapBackground protected RulesetContainer RulesetContainer { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } - private FailOverlay failOverlay; #region Storyboard @@ -127,57 +124,47 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) InternalChild = GameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime); - GameplayClockContainer.Children = new Drawable[] + GameplayClockContainer.Children = new[] { - PausableGameplayContainer = new PausableGameplayContainer + StoryboardContainer = CreateStoryboardContainer(), + new ScalingContainer(ScalingMode.Gameplay) { - Retries = RestartCount, - OnRetry = restart, - OnQuit = performUserRequestedExit, - RequestResume = completion => + Child = new LocalSkinOverrideContainer(working.Skin) { - GameplayClockContainer.Start(); - completion(); - }, - RequestPause = GameplayClockContainer.Stop, - IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, - CheckCanPause = () => CanPause, - Children = new[] - { - StoryboardContainer = CreateStoryboardContainer(), - new ScalingContainer(ScalingMode.Gameplay) - { - Child = new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Child = RulesetContainer - } - }, - new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Breaks = working.Beatmap.Breaks - }, - // display the cursor above some HUD elements. - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) - { - HoldToQuit = { Action = performUserRequestedExit }, - PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, - KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, - RequestSeek = GameplayClockContainer.Seek, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - new SkipOverlay(RulesetContainer.GameplayStartTime) - { - RequestSeek = GameplayClockContainer.Seek - }, + RelativeSizeAxes = Axes.Both, + Child = RulesetContainer } }, - failOverlay = new FailOverlay + new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = working.Beatmap.Breaks + }, + // display the cursor above some HUD elements. + RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working) + { + HoldToQuit = { Action = performUserRequestedExit }, + PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, + KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } }, + RequestSeek = GameplayClockContainer.Seek, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + new SkipOverlay(RulesetContainer.GameplayStartTime) + { + RequestSeek = GameplayClockContainer.Seek + }, + FailOverlay = new FailOverlay + { + OnRetry = restart, + OnQuit = performUserRequestedExit, + }, + PauseOverlay = new PauseOverlay { + OnResume = Resume, + Retries = RestartCount, OnRetry = restart, OnQuit = performUserRequestedExit, }, @@ -197,7 +184,7 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) RulesetContainer.IsPaused.BindTo(GameplayClockContainer.IsPaused); // load storyboard as part of player's load if we can - initializeStoryboard(false); + initializeStoryboard(false); // Bind ScoreProcessor to ourselves ScoreProcessor.AllJudged += onCompletion; @@ -313,6 +300,14 @@ protected virtual ScoreInfo CreateScore() return score; } + protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; + + protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + + #region Fail Logic + + protected FailOverlay FailOverlay { get; private set; } + private bool onFail() { if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) @@ -321,11 +316,87 @@ private bool onFail() GameplayClockContainer.Stop(); HasFailed = true; - failOverlay.Retries = RestartCount; - failOverlay.Show(); + + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) + // could process an extra frame after the GameplayClock is stopped. + // In such cases we want the fail state to precede a user triggered pause. + if (PauseOverlay.State == Visibility.Visible) + PauseOverlay.Hide(); + + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); return true; } + #endregion + + #region Pause Logic + + public bool IsResuming { get; private set; } + + /// + /// The amount of gameplay time after which a second pause is allowed. + /// + private const double pause_cooldown = 1000; + + protected PauseOverlay PauseOverlay { get; private set; } + + private double? lastPauseActionTime; + + private bool canPause => + // must pass basic screen conditions (beatmap loaded, instance allows pause) + LoadedBeatmapSuccessfully && AllowPause && ValidForResume + // replays cannot be paused and exit immediately + && !RulesetContainer.HasReplayLoaded.Value + // cannot pause if we are already in a fail state + && !HasFailed + // cannot pause if already paused (and not in the process of resuming) + && (GameplayClockContainer.IsPaused.Value == false || IsResuming) + // cannot pause too soon after previous pause + && (!lastPauseActionTime.HasValue || GameplayClockContainer.GameplayClock.CurrentTime >= lastPauseActionTime + pause_cooldown); + + private bool canResume => + // cannot resume from a non-paused state + GameplayClockContainer.IsPaused.Value + // cannot resume if we are already in a fail state + && !HasFailed + // already resuming + && !IsResuming; + + protected override void Update() + { + base.Update(); + + // eagerly pause when we lose window focus (if we are locally playing). + if (!Game.IsActive.Value) + Pause(); + } + + public void Pause() + { + if (!canPause) return; + + GameplayClockContainer.Stop(); + PauseOverlay.Show(); + lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; + } + + public void Resume() + { + if (!canResume) return; + + //todo: add resume request support to ruleset + IsResuming = true; + + GameplayClockContainer.Start(); + PauseOverlay.Hide(); + IsResuming = false; + } + + #endregion + + #region Screen Logic + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -350,9 +421,7 @@ public override void OnEntering(IScreen last) storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; GameplayClockContainer.Restart(); - - PausableGameplayContainer.Alpha = 0; - PausableGameplayContainer.FadeIn(750, Easing.OutQuint); + GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); } public override void OnSuspending(IScreen next) @@ -361,9 +430,6 @@ public override void OnSuspending(IScreen next) base.OnSuspending(next); } - public bool CanPause => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value - && (PausableGameplayContainer?.IsPaused.Value == false || PausableGameplayContainer?.IsResuming == true); - public override bool OnExiting(IScreen next) { if (onCompletionEvent != null) @@ -373,9 +439,9 @@ public override bool OnExiting(IScreen next) return true; } - if (LoadedBeatmapSuccessfully && CanPause) + if (LoadedBeatmapSuccessfully && canPause) { - PausableGameplayContainer?.Pause(); + Pause(); return true; } @@ -394,8 +460,6 @@ private void fadeOut(bool instant = false) storyboardReplacesBackground.Value = false; } - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value; - - protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + #endregion } } From 9433a977479792bc35b470ca8720e446df69c8cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 14:40:53 +0900 Subject: [PATCH 15/20] Add resume requesting support and fix exit scenarios --- osu.Game/Rulesets/UI/RulesetContainer.cs | 7 ++++++ osu.Game/Screens/Play/Player.cs | 27 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index d8813631dcd1..c522118962a7 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -130,6 +130,13 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl /// The input manager. public abstract PassThroughInputManager CreateInputManager(); + /// + /// Invoked when the interactive user requests resuming from a paused state. + /// Allows potentially delaying the resume process until an interaction is performed. + /// + /// The action to run when resuming is to be completed. + public void RequestResume(Action continueResume) => continueResume(); + protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; protected FrameStabilityContainer FrameStabilityContainer; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 018ff900eedf..b53ed8ae179b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -352,8 +352,10 @@ private bool onFail() && !HasFailed // cannot pause if already paused (and not in the process of resuming) && (GameplayClockContainer.IsPaused.Value == false || IsResuming) - // cannot pause too soon after previous pause - && (!lastPauseActionTime.HasValue || GameplayClockContainer.GameplayClock.CurrentTime >= lastPauseActionTime + pause_cooldown); + && (!pauseCooldownActive || IsResuming); + + private bool pauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; private bool canResume => // cannot resume from a non-paused state @@ -376,6 +378,7 @@ public void Pause() { if (!canPause) return; + IsResuming = false; GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; @@ -385,12 +388,20 @@ public void Resume() { if (!canResume) return; - //todo: add resume request support to ruleset IsResuming = true; - - GameplayClockContainer.Start(); PauseOverlay.Hide(); - IsResuming = false; + + // time-based conditions may allow instant resume. + if (GameplayClockContainer.GameplayClock.CurrentTime < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + completeResume(); + else + RulesetContainer.RequestResume(completeResume); + + void completeResume() + { + GameplayClockContainer.Start(); + IsResuming = false; + } } #endregion @@ -445,6 +456,10 @@ public override bool OnExiting(IScreen next) return true; } + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return true; + GameplayClockContainer.ResetLocalAdjustments(); fadeOut(); From 4f075f4740b8592a5d1c1426ca13cf1054da042b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 14:57:06 +0900 Subject: [PATCH 16/20] Add more comprehensive testing --- osu.Game.Tests/Visual/TestCasePause.cs | 13 ++++++++++++- osu.Game/Screens/Play/Player.cs | 8 ++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index 622a12da8106..f658dbee1684 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; @@ -26,7 +27,6 @@ protected override void AddCheckSteps(Func player) PausePlayer pausable() => (PausePlayer)player(); base.AddCheckSteps(player); - //AddUntilStep(() => pausable().ScoreProcessor.TotalScore.Value > 0, "score above zero"); AddStep("pause", () => pausable().Pause()); AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); @@ -47,6 +47,17 @@ protected override void AddCheckSteps(Func player) AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); AddAssert("fail overlay still shown", () => pausable().FailOverlayVisible); + + AddStep("restart", () => pausable().Restart()); + + AddUntilStep(() => + { + pausable().Pause(); + return pausable().PauseOverlayVisible; + }, "keep trying to pause"); + + AddStep("exit", () => pausable().Exit()); + AddUntilStep(() => !pausable().IsCurrentScreen(), "player exited"); } private class PausePlayer : Player diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b53ed8ae179b..162350e0889d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -158,14 +158,14 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) }, FailOverlay = new FailOverlay { - OnRetry = restart, + OnRetry = Restart, OnQuit = performUserRequestedExit, }, PauseOverlay = new PauseOverlay { OnResume = Resume, Retries = RestartCount, - OnRetry = restart, + OnRetry = Restart, OnQuit = performUserRequestedExit, }, new HotkeyRetryOverlay @@ -175,7 +175,7 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) if (!this.IsCurrentScreen()) return; fadeOut(true); - restart(); + Restart(); }, } }; @@ -246,7 +246,7 @@ private void performUserRequestedExit() this.Exit(); } - private void restart() + public void Restart() { if (!this.IsCurrentScreen()) return; From f56e8d9bfe26b9d7ca85111d6489f5d884985892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Mar 2019 19:44:21 +0900 Subject: [PATCH 17/20] Make tests better --- osu.Game.Tests/Visual/TestCasePause.cs | 75 +++++++++++++++----------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePause.cs b/osu.Game.Tests/Visual/TestCasePause.cs index f658dbee1684..f53177e86abf 100644 --- a/osu.Game.Tests/Visual/TestCasePause.cs +++ b/osu.Game.Tests/Visual/TestCasePause.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Rulesets; @@ -11,56 +11,69 @@ namespace osu.Game.Tests.Visual { - public class TestCasePause : TestCasePlayer + public class TestCasePause : PlayerTestCase { + protected new PausePlayer Player => (PausePlayer)base.Player; + public TestCasePause() : base(new OsuRuleset()) { } - protected override bool AllowFail => true; - - protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); - - protected override void AddCheckSteps(Func player) + [Test] + public void TestPauseResume() { - PausePlayer pausable() => (PausePlayer)player(); + AddStep("pause", () => Player.Pause()); + AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); - base.AddCheckSteps(player); - - AddStep("pause", () => pausable().Pause()); - AddAssert("clock stopped", () => !pausable().GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay shown", () => pausable().PauseOverlayVisible); - - AddStep("resume", () => pausable().Resume()); - AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - - AddStep("pause too soon", () => pausable().Pause()); - AddAssert("clock not stopped", () => pausable().GameplayClockContainer.GameplayClock.IsRunning); - AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); + AddStep("resume", () => Player.Resume()); + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + } - AddUntilStep(() => pausable().HasFailed, "wait for fail"); + [Test] + public void TestPauseTooSoon() + { + AddStep("pause", () => Player.Pause()); + AddAssert("clock stopped", () => !Player.GameplayClockContainer.GameplayClock.IsRunning); + AddStep("resume", () => Player.Resume()); + AddAssert("clock started", () => Player.GameplayClockContainer.GameplayClock.IsRunning); + AddStep("pause too soon", () => Player.Pause()); + AddAssert("clock not stopped", () => Player.GameplayClockContainer.GameplayClock.IsRunning); + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + } - AddAssert("fail overlay shown", () => pausable().FailOverlayVisible); + [Test] + public void TestPauseAfterFail() + { + AddUntilStep(() => Player.HasFailed, "wait for fail"); - AddStep("try to pause", () => pausable().Pause()); + AddAssert("fail overlay shown", () => Player.FailOverlayVisible); - AddAssert("pause overlay hidden", () => !pausable().PauseOverlayVisible); - AddAssert("fail overlay still shown", () => pausable().FailOverlayVisible); + AddStep("try to pause", () => Player.Pause()); - AddStep("restart", () => pausable().Restart()); + AddAssert("pause overlay hidden", () => !Player.PauseOverlayVisible); + AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); + } + [Test] + public void TestExitFromPause() + { AddUntilStep(() => { - pausable().Pause(); - return pausable().PauseOverlayVisible; + Player.Pause(); + return Player.PauseOverlayVisible; }, "keep trying to pause"); - AddStep("exit", () => pausable().Exit()); - AddUntilStep(() => !pausable().IsCurrentScreen(), "player exited"); + AddStep("exit", () => Player.Exit()); + AddUntilStep(() => !Player.IsCurrentScreen(), "player exited"); } - private class PausePlayer : Player + protected override bool AllowFail => true; + + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + protected class PausePlayer : Player { public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; From f35be9ba4ba752238e98c3d4fc460b2c6b969eb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 17:39:53 +0900 Subject: [PATCH 18/20] Move osu! cursor to its own class --- .../UI/Cursor/GameplayCursorContainer.cs | 140 ----------------- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 148 ++++++++++++++++++ 2 files changed, 148 insertions(+), 140 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs index 8c6723f5be36..b64561e4f7db 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursorContainer.cs @@ -1,19 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.Cursor { @@ -88,136 +79,5 @@ protected override void PopOut() fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); } - - public class OsuCursor : SkinReloadableDrawable - { - private bool cursorExpand; - - private Bindable cursorScale; - private Bindable autoCursorScale; - private readonly IBindable beatmap = new Bindable(); - - private Container expandTarget; - private Drawable scaleTarget; - - public OsuCursor() - { - Origin = Anchor.Centre; - Size = new Vector2(28); - } - - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IBindable beatmap) - { - InternalChild = expandTarget = new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 6, - BorderColour = Color4.White, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Pink.Opacity(0.5f), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 3, - BorderColour = Color4.White.Opacity(0.5f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - }, restrictSize: false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - } - }; - - this.beatmap.BindTo(beatmap); - this.beatmap.ValueChanged += _ => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += _ => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = (float)cursorScale.Value; - - if (autoCursorScale.Value && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); - } - - scaleTarget.Scale = new Vector2(scale); - } - - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; - - public void Expand() - { - if (!cursorExpand) return; - - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); - } - - public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); - } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs new file mode 100644 index 000000000000..ecdafb0fa287 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -0,0 +1,148 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursor : SkinReloadableDrawable + { + private bool cursorExpand; + + private Bindable cursorScale; + private Bindable autoCursorScale; + private readonly IBindable beatmap = new Bindable(); + + private Container expandTarget; + private Drawable scaleTarget; + + public OsuCursor() + { + Origin = Anchor.Centre; + Size = new Vector2(28); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, IBindable beatmap) + { + InternalChild = expandTarget = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 6, + BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Pink.Opacity(0.5f), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 3, + BorderColour = Color4.White.Opacity(0.5f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.1f), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + }, + }, + } + }, restrictSize: false) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + }; + + this.beatmap.BindTo(beatmap); + this.beatmap.ValueChanged += _ => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += _ => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = (float)cursorScale.Value; + + if (autoCursorScale.Value && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + } + + scaleTarget.Scale = new Vector2(scale); + } + + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + + public void Expand() + { + if (!cursorExpand) return; + + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + } + + public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + } +} From 82f3ee65b0c65d3140622e7569a6deab5b6562d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 19:04:07 +0900 Subject: [PATCH 19/20] osu! resume overlay --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 105 +++++++++++++++++++ osu.Game/Screens/Play/ResumeOverlay.cs | 74 +++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs create mode 100644 osu.Game/Screens/Play/ResumeOverlay.cs diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs new file mode 100644 index 000000000000..c2000131db97 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuResumeOverlay : ResumeOverlay + { + private OsuClickToResumeCursor clickToResumeCursor; + + private GameplayCursorContainer localCursorContainer; + + public override CursorContainer LocalCursor => State == Visibility.Visible ? localCursorContainer : null; + + protected override string Message => "Click the orange cursor to resume"; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); + } + + public override void Show() + { + base.Show(); + clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); + Add(localCursorContainer = new GameplayCursorContainer()); + } + + public override void Hide() + { + localCursorContainer.Expire(); + base.Hide(); + } + + protected override bool OnHover(HoverEvent e) => true; + + public class OsuClickToResumeCursor : OsuCursor, IKeyBindingHandler + { + public override bool HandlePositionalInput => true; + + public Action ResumeRequested; + + public OsuClickToResumeCursor() + { + RelativePositionAxes = Axes.Both; + } + + protected override bool OnHover(HoverEvent e) + { + updateColour(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateColour(); + base.OnHoverLost(e); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (!IsHovered) return false; + + this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); + + ResumeRequested?.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(OsuAction action) => false; + + public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => + { + updateColour(); + this.MoveTo(activeCursorPosition); + this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint); + }); + + private void updateColour() + { + this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Play/ResumeOverlay.cs b/osu.Game/Screens/Play/ResumeOverlay.cs new file mode 100644 index 000000000000..2ef76069c278 --- /dev/null +++ b/osu.Game/Screens/Play/ResumeOverlay.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// An overlay which can be used to require further user actions before gameplay is resumed. + /// + public abstract class ResumeOverlay : OverlayContainer + { + public CursorContainer GameplayCursor { get; set; } + + /// + /// The action to be performed to complete resuming. + /// + public Action ResumeAction { private get; set; } + + public virtual CursorContainer LocalCursor => null; + + protected const float TRANSITION_TIME = 500; + + protected override bool BlockPositionalInput => false; + + protected abstract string Message { get; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected ResumeOverlay() + { + RelativeSizeAxes = Axes.Both; + } + + protected void Resume() + { + ResumeAction?.Invoke(); + Hide(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRange(new Drawable[] + { + new OsuSpriteText + { + RelativePositionAxes = Axes.Both, + Y = 0.4f, + Text = Message, + Font = OsuFont.GetFont(size: 30), + Spacing = new Vector2(5, 0), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = colours.Yellow, + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f) + } + }); + } + + protected override void PopIn() => this.FadeIn(TRANSITION_TIME, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(TRANSITION_TIME, Easing.OutQuint); + } +} From fb5d19e264ae46b03e5ad507fe17082e4228f5d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Mar 2019 19:04:32 +0900 Subject: [PATCH 20/20] Ruleset implementation --- osu.Game/Rulesets/UI/RulesetContainer.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index c522118962a7..b251f0935987 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { @@ -135,7 +136,20 @@ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnl /// Allows potentially delaying the resume process until an interaction is performed. /// /// The action to run when resuming is to be completed. - public void RequestResume(Action continueResume) => continueResume(); + public void RequestResume(Action continueResume) + { + if (ResumeOverlay != null && (Cursor == null || Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + { + ResumeOverlay.ResumeAction = continueResume; + ResumeOverlay.Show(); + } + else + continueResume(); + } + + public ResumeOverlay ResumeOverlay { get; private set; } + + protected virtual ResumeOverlay CreateResumeOverlay() => null; protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; @@ -272,6 +286,8 @@ private void load(OsuConfigManager config) Playfield }); + var poop = CreateInputManager(); + InternalChildren = new Drawable[] { FrameStabilityContainer = new FrameStabilityContainer