From 3a6a3a067b4816b81bf1851b75640a8e73349221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 14:54:21 +0100 Subject: [PATCH 01/68] Rewrite test to cover failure case --- .../Online/TestSceneAccountCreationOverlay.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index dcfe0432a8..3d65e7e4ba 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -1,6 +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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -13,13 +14,12 @@ namespace osu.Game.Tests.Visual.Online public class TestSceneAccountCreationOverlay : OsuTestScene { private readonly Container userPanelArea; + private readonly AccountCreationOverlay accountCreation; private IBindable localUser; public TestSceneAccountCreationOverlay() { - AccountCreationOverlay accountCreation; - Children = new Drawable[] { accountCreation = new AccountCreationOverlay(), @@ -31,8 +31,6 @@ namespace osu.Game.Tests.Visual.Online Origin = Anchor.TopRight, }, }; - - AddStep("show", () => accountCreation.Show()); } [BackgroundDependencyLoader] @@ -42,8 +40,19 @@ namespace osu.Game.Tests.Visual.Online localUser = API.LocalUser.GetBoundCopy(); localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true); + } - AddStep("logout", API.Logout); + [Test] + public void TestOverlayVisibility() + { + AddStep("start hidden", () => accountCreation.Hide()); + AddStep("log out", API.Logout); + + AddStep("show manually", () => accountCreation.Show()); + AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible); + + AddStep("log back in", () => API.Login("dummy", "password")); + AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); } } } From 0d8fb83d0a0a56359947ac8e52ff9e10a953db66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 15:37:14 +0100 Subject: [PATCH 02/68] Ensure account creation overlay is shown after logout Scheduling the entire API state change callback caused the scheduled hide to fire the first time the user attempted to display the account creation overlay after a logout, because the drawable wasn't present before that (so its scheduler wasn't running). It is not theoretically safe to run `Hide()` unscheduled at its present call site (as the value change callbacks are fired on the background API thread). This could also be fixed by setting `AlwaysPresent = true`, but that's a pretty ugly and unperformant change to make in general. --- osu.Game/Overlays/AccountCreationOverlay.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 58ede5502a..3084c7475a 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -93,6 +94,11 @@ namespace osu.Game.Overlays if (welcomeScreen.GetChildScreen() != null) welcomeScreen.MakeCurrent(); + + // there might be a stale scheduled hide from a previous API state change. + // cancel it here so that the overlay is not hidden again after one frame. + scheduledHide?.Cancel(); + scheduledHide = null; } protected override void PopOut() @@ -101,7 +107,9 @@ namespace osu.Game.Overlays this.FadeOut(100); } - private void apiStateChanged(ValueChangedEvent state) => Schedule(() => + private ScheduledDelegate scheduledHide; + + private void apiStateChanged(ValueChangedEvent state) { switch (state.NewValue) { @@ -113,9 +121,10 @@ namespace osu.Game.Overlays break; case APIState.Online: - Hide(); + scheduledHide?.Cancel(); + scheduledHide = Schedule(Hide); break; } - }); + } } } From 0bd9f68cbd6758faa79fbb8a3feac064edb2bdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 18:39:55 +0100 Subject: [PATCH 03/68] Refactor update stream colour mapping code --- .../API/Requests/Responses/APIUpdateStream.cs | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs index d9e48373bb..5af7d6a01c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using Newtonsoft.Json; using osu.Framework.Graphics.Colour; using osuTK.Graphics; @@ -27,34 +28,16 @@ namespace osu.Game.Online.API.Requests.Responses public bool Equals(APIUpdateStream other) => Id == other?.Id; - public ColourInfo Colour + internal static readonly Dictionary KNOWN_STREAMS = new Dictionary { - get - { - switch (Name) - { - case "stable40": - return new Color4(102, 204, 255, 255); + ["stable40"] = new Color4(102, 204, 255, 255), + ["stable"] = new Color4(34, 153, 187, 255), + ["beta40"] = new Color4(255, 221, 85, 255), + ["cuttingedge"] = new Color4(238, 170, 0, 255), + [OsuGameBase.CLIENT_STREAM_NAME] = new Color4(237, 18, 33, 255), + ["web"] = new Color4(136, 102, 238, 255) + }; - case "stable": - return new Color4(34, 153, 187, 255); - - case "beta40": - return new Color4(255, 221, 85, 255); - - case "cuttingedge": - return new Color4(238, 170, 0, 255); - - case OsuGameBase.CLIENT_STREAM_NAME: - return new Color4(237, 18, 33, 255); - - case "web": - return new Color4(136, 102, 238, 255); - - default: - return new Color4(0, 0, 0, 255); - } - } - } + public ColourInfo Colour => KNOWN_STREAMS.TryGetValue(Name, out var colour) ? colour : new Color4(0, 0, 0, 255); } } From dacf6d5a34134b2d10070a0fc74232492e37e237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 19:28:33 +0100 Subject: [PATCH 04/68] Decouple changelog test scene from web --- .../Online/TestSceneChangelogOverlay.cs | 132 ++++++++++++------ 1 file changed, 92 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 998e42b478..9f617d49da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -1,8 +1,13 @@ // 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.Collections.Generic; +using System.Linq; +using Humanizer; using NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Changelog; @@ -12,15 +17,63 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneChangelogOverlay : OsuTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private readonly Dictionary streams; + private readonly Dictionary builds; + + private APIChangelogBuild requestedBuild; private TestChangelogOverlay changelog; - protected override bool UseOnlineAPI => true; + public TestSceneChangelogOverlay() + { + streams = APIUpdateStream.KNOWN_STREAMS.Keys.Select((stream, id) => new APIUpdateStream + { + Id = id + 1, + Name = stream, + DisplayName = stream.Humanize(), // not quite there, but good enough. + }).ToDictionary(stream => stream.Name); + + string version = DateTimeOffset.Now.ToString("yyyy.Mdd.0"); + builds = APIUpdateStream.KNOWN_STREAMS.Keys.Select(stream => new APIChangelogBuild + { + Version = version, + DisplayVersion = version, + UpdateStream = streams[stream], + ChangelogEntries = new List() + }).ToDictionary(build => build.UpdateStream.Name); + + foreach (var stream in streams.Values) + stream.LatestBuild = builds[stream.Name]; + } [SetUp] - public void SetUp() => Schedule(() => + public void SetUp() { - Child = changelog = new TestChangelogOverlay(); - }); + requestedBuild = null; + + dummyAPI.HandleRequest = request => + { + switch (request) + { + case GetChangelogRequest changelogRequest: + var changelogResponse = new APIChangelogIndex + { + Streams = streams.Values.ToList(), + Builds = builds.Values.ToList() + }; + changelogRequest.TriggerSuccess(changelogResponse); + break; + + case GetChangelogBuildRequest buildRequest: + if (requestedBuild != null) + buildRequest.TriggerSuccess(requestedBuild); + break; + } + }; + + Schedule(() => Child = changelog = new TestChangelogOverlay()); + } [Test] public void ShowWithNoFetch() @@ -41,26 +94,22 @@ namespace osu.Game.Tests.Visual.Online } [Test] - [Ignore("needs to be updated to not be so server dependent")] public void ShowWithBuild() { - AddStep(@"Show with Lazer 2018.712.0", () => + showBuild(() => new APIChangelogBuild { - changelog.ShowBuild(new APIChangelogBuild + Version = "2018.712.0", + DisplayVersion = "2018.712.0", + UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME], + ChangelogEntries = new List { - Version = "2018.712.0", - DisplayVersion = "2018.712.0", - UpdateStream = new APIUpdateStream { Id = 5, Name = OsuGameBase.CLIENT_STREAM_NAME }, - ChangelogEntries = new List + new APIChangelogEntry { - new APIChangelogEntry - { - Category = "Test", - Title = "Title", - MessageHtml = "Message", - } + Category = "Test", + Title = "Title", + MessageHtml = "Message", } - }); + } }); AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0); @@ -71,35 +120,38 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestHTMLUnescaping() { - AddStep(@"Ensure HTML string unescaping", () => + showBuild(() => new APIChangelogBuild { - changelog.ShowBuild(new APIChangelogBuild + Version = "2019.920.0", + DisplayVersion = "2019.920.0", + UpdateStream = new APIUpdateStream { - Version = "2019.920.0", - DisplayVersion = "2019.920.0", - UpdateStream = new APIUpdateStream + Name = "Test", + DisplayName = "Test" + }, + ChangelogEntries = new List + { + new APIChangelogEntry { - Name = "Test", - DisplayName = "Test" - }, - ChangelogEntries = new List - { - new APIChangelogEntry + Category = "Testing HTML strings unescaping", + Title = "Ensuring HTML strings are being unescaped", + MessageHtml = """"This text should appear triple-quoted""" >_<", + GithubUser = new APIChangelogUser { - Category = "Testing HTML strings unescaping", - Title = "Ensuring HTML strings are being unescaped", - MessageHtml = """"This text should appear triple-quoted""" >_<", - GithubUser = new APIChangelogUser - { - DisplayName = "Dummy", - OsuUsername = "Dummy", - } - }, - } - }); + DisplayName = "Dummy", + OsuUsername = "Dummy", + } + }, + } }); } + private void showBuild(Func build) + { + AddStep("set up build", () => requestedBuild = build.Invoke()); + AddStep("show build", () => changelog.ShowBuild(requestedBuild)); + } + private class TestChangelogOverlay : ChangelogOverlay { public new List Streams => base.Streams; From 5f43299d3779ffbde32d3bd2f735592f30fad820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 19:49:41 +0100 Subject: [PATCH 05/68] Fix tests failing due to base logic firing It turns out that the changelog code was semi-intentionally relying on the request to get release streams to be slow to initially show the listing of all streams. Locally suppress the base tab control logic to fix this. --- osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs index 509a6dabae..6bbff045b5 100644 --- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs +++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs @@ -8,5 +8,11 @@ namespace osu.Game.Overlays.Changelog public class ChangelogUpdateStreamControl : OverlayStreamControl { protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value); + + protected override void LoadComplete() + { + // suppress base logic of immediately selecting first item if one exists + // (we always want to start with no stream selected). + } } } From 3ac618778f11010de62bedec719141319b37cc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:08:57 +0100 Subject: [PATCH 06/68] Handle all changelog entry types correctly --- .../Online/TestSceneChangelogOverlay.cs | 20 +++++++++++++++--- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 21 ++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 9f617d49da..42f822664f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -105,9 +105,23 @@ namespace osu.Game.Tests.Visual.Online { new APIChangelogEntry { - Category = "Test", - Title = "Title", - MessageHtml = "Message", + Type = ChangelogEntryType.Fix, + Category = "osu!", + Title = "Fix thing", + MessageHtml = "Additional info goes here.", + }, + new APIChangelogEntry + { + Type = ChangelogEntryType.Add, + Category = "osu!", + Title = "Add thing", + Major = true + }, + new APIChangelogEntry + { + Type = ChangelogEntryType.Misc, + Category = "Code quality", + Title = "Clean up thing" } } }); diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 65ff0fef92..6ec3f08a2b 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Changelog Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, Size = new Vector2(10), - Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, + Icon = getIconForChangelogEntry(entry.Type), Colour = entryColour.Opacity(0.5f), Margin = new MarginPadding { Right = 5 }, }, @@ -186,6 +186,25 @@ namespace osu.Game.Overlays.Changelog } } + private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType) + { + // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11 + switch (entryType) + { + case ChangelogEntryType.Add: + return FontAwesome.Solid.Plus; + + case ChangelogEntryType.Fix: + return FontAwesome.Solid.Check; + + case ChangelogEntryType.Misc: + return FontAwesome.Regular.Circle; + + default: + throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}"); + } + } + protected virtual FillFlowContainer CreateHeader() => new FillFlowContainer { Anchor = Anchor.TopCentre, From 0aedc720f2c61a9f0d8d4223d3896259181ad071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:31:12 +0100 Subject: [PATCH 07/68] Extract changelog entry component --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 146 +------------ osu.Game/Overlays/Changelog/ChangelogEntry.cs | 202 ++++++++++++++++++ 2 files changed, 203 insertions(+), 145 deletions(-) create mode 100644 osu.Game/Overlays/Changelog/ChangelogEntry.cs diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 6ec3f08a2b..2d071b7345 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -9,14 +9,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System; using System.Linq; -using System.Text.RegularExpressions; using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK.Graphics; using osu.Framework.Allocation; -using System.Net; -using osuTK; -using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Overlays.Changelog { @@ -63,145 +57,7 @@ namespace osu.Game.Overlays.Changelog Margin = new MarginPadding { Top = 35, Bottom = 15 }, }); - var fontLarge = OsuFont.GetFont(size: 16); - var fontMedium = OsuFont.GetFont(size: 12); - - foreach (var entry in categoryEntries) - { - var entryColour = entry.Major ? colours.YellowLight : Color4.White; - - LinkFlowContainer title; - - var titleContainer = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Vertical = 5 }, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Size = new Vector2(10), - Icon = getIconForChangelogEntry(entry.Type), - Colour = entryColour.Opacity(0.5f), - Margin = new MarginPadding { Right = 5 }, - }, - title = new LinkFlowContainer - { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.BottomLeft, - } - } - }; - - title.AddText(entry.Title, t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - - if (!string.IsNullOrEmpty(entry.Repository)) - { - title.AddText(" (", t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, - creationParameters: t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - title.AddText(")", t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - } - - title.AddText("by ", t => - { - t.Font = fontMedium; - t.Colour = entryColour; - t.Padding = new MarginPadding { Left = 10 }; - }); - - if (entry.GithubUser != null) - { - if (entry.GithubUser.UserId != null) - { - title.AddUserLink(new User - { - Username = entry.GithubUser.OsuUsername, - Id = entry.GithubUser.UserId.Value - }, t => - { - t.Font = fontMedium; - t.Colour = entryColour; - }); - } - else if (entry.GithubUser.GithubUrl != null) - { - title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => - { - t.Font = fontMedium; - t.Colour = entryColour; - }); - } - else - { - title.AddText(entry.GithubUser.DisplayName, t => - { - t.Font = fontMedium; - t.Colour = entryColour; - }); - } - } - - ChangelogEntries.Add(titleContainer); - - if (!string.IsNullOrEmpty(entry.MessageHtml)) - { - var message = new TextFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }; - - // todo: use markdown parsing once API returns markdown - message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t => - { - t.Font = fontMedium; - t.Colour = colourProvider.Foreground1; - }); - - ChangelogEntries.Add(message); - } - } - } - } - - private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType) - { - // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11 - switch (entryType) - { - case ChangelogEntryType.Add: - return FontAwesome.Solid.Plus; - - case ChangelogEntryType.Fix: - return FontAwesome.Solid.Check; - - case ChangelogEntryType.Misc: - return FontAwesome.Regular.Circle; - - default: - throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}"); + ChangelogEntries.AddRange(categoryEntries.Select(entry => new ChangelogEntry(entry))); } } diff --git a/osu.Game/Overlays/Changelog/ChangelogEntry.cs b/osu.Game/Overlays/Changelog/ChangelogEntry.cs new file mode 100644 index 0000000000..55edb40283 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogEntry.cs @@ -0,0 +1,202 @@ +// 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.Net; +using System.Text.RegularExpressions; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogEntry : FillFlowContainer + { + private readonly APIChangelogEntry entry; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private FontUsage fontLarge; + private FontUsage fontMedium; + + public ChangelogEntry(APIChangelogEntry entry) + { + this.entry = entry; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + } + + [BackgroundDependencyLoader] + private void load() + { + fontLarge = OsuFont.GetFont(size: 16); + fontMedium = OsuFont.GetFont(size: 12); + + Children = new[] + { + createTitle(), + createMessage() + }; + } + + private Drawable createTitle() + { + var entryColour = entry.Major ? colours.YellowLight : Color4.White; + + LinkFlowContainer title; + + var titleContainer = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Vertical = 5 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(10), + Icon = getIconForChangelogEntry(entry.Type), + Colour = entryColour.Opacity(0.5f), + Margin = new MarginPadding { Right = 5 }, + }, + title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.BottomLeft, + } + } + }; + + title.AddText(entry.Title, t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + + if (!string.IsNullOrEmpty(entry.Repository)) + addRepositoryReference(title, entryColour); + + if (entry.GithubUser != null) + addGithubAuthorReference(title, entryColour); + + return titleContainer; + } + + private void addRepositoryReference(LinkFlowContainer title, Color4 entryColour) + { + title.AddText(" (", t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, + t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + title.AddText(")", t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + } + + private void addGithubAuthorReference(LinkFlowContainer title, Color4 entryColour) + { + title.AddText("by ", t => + { + t.Font = fontMedium; + t.Colour = entryColour; + t.Padding = new MarginPadding { Left = 10 }; + }); + + if (entry.GithubUser.UserId != null) + { + title.AddUserLink(new User + { + Username = entry.GithubUser.OsuUsername, + Id = entry.GithubUser.UserId.Value + }, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + } + else if (entry.GithubUser.GithubUrl != null) + { + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + } + else + { + title.AddText(entry.GithubUser.DisplayName, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + } + } + + private Drawable createMessage() + { + if (string.IsNullOrEmpty(entry.MessageHtml)) + return Empty(); + + var message = new TextFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + // todo: use markdown parsing once API returns markdown + message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t => + { + t.Font = fontMedium; + t.Colour = colourProvider.Foreground1; + }); + + return message; + } + + private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType) + { + // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11 + switch (entryType) + { + case ChangelogEntryType.Add: + return FontAwesome.Solid.Plus; + + case ChangelogEntryType.Fix: + return FontAwesome.Solid.Check; + + case ChangelogEntryType.Misc: + return FontAwesome.Regular.Circle; + + default: + throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}"); + } + } + } +} From c32fc05f69435a8b5adc6b7c715ce57b75cb404e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:41:35 +0100 Subject: [PATCH 08/68] Improve test scene coverage of corner cases --- .../Online/TestSceneChangelogOverlay.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 42f822664f..eef2892290 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -109,19 +109,43 @@ namespace osu.Game.Tests.Visual.Online Category = "osu!", Title = "Fix thing", MessageHtml = "Additional info goes here.", + Repository = "osu", + GithubPullRequestId = 11100, + GithubUser = new APIChangelogUser + { + OsuUsername = "smoogipoo", + UserId = 1040328 + } }, new APIChangelogEntry { Type = ChangelogEntryType.Add, Category = "osu!", Title = "Add thing", - Major = true + Major = true, + Repository = "ppy/osu-framework", + GithubPullRequestId = 4444, + GithubUser = new APIChangelogUser + { + DisplayName = "frenzibyte", + GithubUrl = "https://github.com/frenzibyte" + } }, new APIChangelogEntry { Type = ChangelogEntryType.Misc, Category = "Code quality", - Title = "Clean up thing" + Title = "Clean up thing", + GithubUser = new APIChangelogUser + { + DisplayName = "some dude" + } + }, + new APIChangelogEntry + { + Type = ChangelogEntryType.Misc, + Category = "Code quality", + Title = "Clean up another thing" } } }); From 09b0a57290fe224f9a45bd759ae4c85856c3f518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:44:19 +0100 Subject: [PATCH 09/68] Schedule all of setup to avoid headless test fail --- osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index eef2892290..cd2c4e9346 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online } [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => { requestedBuild = null; @@ -72,8 +72,8 @@ namespace osu.Game.Tests.Visual.Online } }; - Schedule(() => Child = changelog = new TestChangelogOverlay()); - } + Child = changelog = new TestChangelogOverlay(); + }); [Test] public void ShowWithNoFetch() From ff57562956515a4895c6f50c0ec49746a2ad4963 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 11:34:05 +0900 Subject: [PATCH 10/68] Fix multiplayer leaderboard not unsubscribing from quit users --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 5 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 61 ++++++++++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs index 8078c7b994..98a3ce9b47 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs @@ -20,11 +20,12 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; +using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Online; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneMultiplayerGameplayLeaderboard : OsuTestScene + public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { [Cached(typeof(SpectatorStreamingClient))] private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(16); @@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { AddStep("create leaderboard", () => { diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index c10ec9e004..ce6e19aea0 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; @@ -18,10 +22,21 @@ namespace osu.Game.Screens.Play.HUD { private readonly ScoreProcessor scoreProcessor; - private readonly int[] userIds; - private readonly Dictionary userScores = new Dictionary(); + [Resolved] + private SpectatorStreamingClient streamingClient { get; set; } + + [Resolved] + private StatefulMultiplayerClient multiplayerClient { get; set; } + + [Resolved] + private UserLookupCache userLookupCache { get; set; } + + private Bindable scoringMode; + + private readonly BindableList playingUsers; + /// /// Construct a new leaderboard. /// @@ -33,32 +48,24 @@ namespace osu.Game.Screens.Play.HUD this.scoreProcessor = scoreProcessor; // todo: this will likely be passed in as User instances. - this.userIds = userIds; + playingUsers = new BindableList(userIds); } - [Resolved] - private SpectatorStreamingClient streamingClient { get; set; } - - [Resolved] - private UserLookupCache userLookupCache { get; set; } - - private Bindable scoringMode; - [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { streamingClient.OnNewFrames += handleIncomingFrames; - foreach (var user in userIds) + foreach (var userId in playingUsers) { - streamingClient.WatchUser(user); + streamingClient.WatchUser(userId); // probably won't be required in the final implementation. - var resolvedUser = userLookupCache.GetUserAsync(user).Result; + var resolvedUser = userLookupCache.GetUserAsync(userId).Result; var trackedUser = new TrackedUserData(); - userScores[user] = trackedUser; + userScores[userId] = trackedUser; var leaderboardScore = AddPlayer(resolvedUser, resolvedUser.Id == api.LocalUser.Value.Id); ((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy); @@ -70,6 +77,28 @@ namespace osu.Game.Screens.Play.HUD scoringMode.BindValueChanged(updateAllScores, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + playingUsers.BindCollectionChanged(usersChanged); + playingUsers.BindTo(multiplayerClient.PlayingUsers); + } + + private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Remove: + foreach (var userId in e.OldItems.OfType()) + { + streamingClient.StopWatchingUser(userId); + } + + break; + } + } + private void updateAllScores(ValueChangedEvent mode) { foreach (var trackedData in userScores.Values) @@ -91,7 +120,7 @@ namespace osu.Game.Screens.Play.HUD if (streamingClient != null) { - foreach (var user in userIds) + foreach (var user in playingUsers) { streamingClient.StopWatchingUser(user); } From 116acc2b5e073dc6578ad1169ce109a09bc76d24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 11:35:51 +0900 Subject: [PATCH 11/68] Add flow for marking user as quit for further handling --- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index ce6e19aea0..a71c4685d9 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Play.HUD foreach (var userId in e.OldItems.OfType()) { streamingClient.StopWatchingUser(userId); + + if (userScores.TryGetValue(userId, out var trackedData)) + trackedData.MarkUserQuit(); } break; @@ -143,11 +146,19 @@ namespace osu.Game.Screens.Play.HUD private readonly BindableInt currentCombo = new BindableInt(); + public IBindable UserQuit => userQuit; + + private readonly BindableBool userQuit = new BindableBool(); + [CanBeNull] public FrameHeader LastHeader; + public void MarkUserQuit() => userQuit.Value = true; + public void UpdateScore(ScoreProcessor processor, ScoringMode mode) { + Debug.Assert(UserQuit.Value); + if (LastHeader == null) return; From 71dcbeaf7ce57eb0aae0cd72be4f3a74a2e505bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 11:43:10 +0900 Subject: [PATCH 12/68] Mark user as quit visually on the leaderboard --- .../Screens/Play/HUD/GameplayLeaderboardScore.cs | 13 +++++++++++++ osu.Game/Screens/Play/HUD/ILeaderboardScore.cs | 2 ++ .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 12 ++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 58281debf1..ed86f3241d 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -34,6 +34,7 @@ namespace osu.Game.Screens.Play.HUD public BindableDouble TotalScore { get; } = new BindableDouble(); public BindableDouble Accuracy { get; } = new BindableDouble(1); public BindableInt Combo { get; } = new BindableInt(); + public BindableBool HasQuit { get; } = new BindableBool(); private int? scorePosition; @@ -230,6 +231,15 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); + HasQuit.BindValueChanged(v => + { + if (v.NewValue) + { + // we will probably want to display this in a better way once we have a design. + // and also show states other than quit. + panelColour = Color4.Gray; + } + }, true); } protected override void LoadComplete() @@ -244,6 +254,9 @@ namespace osu.Game.Screens.Play.HUD private void updateColour() { + if (HasQuit.Value) + return; + if (scorePosition == 1) { mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic); diff --git a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs index bc1a03c5aa..83b6f6621b 100644 --- a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs @@ -10,5 +10,7 @@ namespace osu.Game.Screens.Play.HUD BindableDouble TotalScore { get; } BindableDouble Accuracy { get; } BindableInt Combo { get; } + + BindableBool HasQuit { get; } } } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index a71c4685d9..6b0ca4d74c 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -71,6 +71,7 @@ namespace osu.Game.Screens.Play.HUD ((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy); ((IBindable)leaderboardScore.TotalScore).BindTo(trackedUser.Score); ((IBindable)leaderboardScore.Combo).BindTo(trackedUser.CurrentCombo); + ((IBindable)leaderboardScore.HasQuit).BindTo(trackedUser.UserQuit); } scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); @@ -81,8 +82,15 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - playingUsers.BindCollectionChanged(usersChanged); + // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. + foreach (int userId in playingUsers) + { + if (!multiplayerClient.PlayingUsers.Contains(userId)) + usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); + } + playingUsers.BindTo(multiplayerClient.PlayingUsers); + playingUsers.BindCollectionChanged(usersChanged); } private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -157,7 +165,7 @@ namespace osu.Game.Screens.Play.HUD public void UpdateScore(ScoreProcessor processor, ScoringMode mode) { - Debug.Assert(UserQuit.Value); + Debug.Assert(!UserQuit.Value); if (LastHeader == null) return; From 2599e95335a95ac8ae6b4a9685ac074432089a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 12:11:19 +0900 Subject: [PATCH 13/68] Add test coverage --- .../TestSceneMultiplayerGameplayLeaderboard.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs index 98a3ce9b47..c214a34fe3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,8 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { + private const int users = 16; + [Cached(typeof(SpectatorStreamingClient))] - private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(16); + private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(users); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); @@ -59,6 +61,9 @@ namespace osu.Game.Tests.Visual.Gameplay streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + Client.PlayingUsers.Clear(); + Client.PlayingUsers.AddRange(streamingClient.PlayingUsers); + Children = new Drawable[] { scoreProcessor = new OsuScoreProcessor(), @@ -82,6 +87,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100); } + [Test] + public void TestUserQuit() + { + AddRepeatStep("mark user quit", () => Client.PlayingUsers.RemoveAt(0), users); + } + public class TestMultiplayerStreaming : SpectatorStreamingClient { public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; From 966a2151e3c2af58e9cdb506ffe8637dc5ce9fe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 17:55:24 +0900 Subject: [PATCH 14/68] Ensure the previous leaderboard is removed --- .../Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs index c214a34fe3..975c54c3f6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs @@ -54,6 +54,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create leaderboard", () => { + leaderboard?.Expire(); + OsuScoreProcessor scoreProcessor; Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); From fa0576f47f05faa4631e7a00a5a50b4c50d38eb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Dec 2020 13:40:02 +0900 Subject: [PATCH 15/68] Move quit colour change implementation to updateColour for better coverage --- .../Play/HUD/GameplayLeaderboardScore.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index ed86f3241d..4aeb65bb01 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -231,15 +231,7 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); - HasQuit.BindValueChanged(v => - { - if (v.NewValue) - { - // we will probably want to display this in a better way once we have a design. - // and also show states other than quit. - panelColour = Color4.Gray; - } - }, true); + HasQuit.BindValueChanged(v => updateColour()); } protected override void LoadComplete() @@ -255,7 +247,14 @@ namespace osu.Game.Screens.Play.HUD private void updateColour() { if (HasQuit.Value) + { + // we will probably want to display this in a better way once we have a design. + // and also show states other than quit. + mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic); + panelColour = Color4.Gray; + textColour = Color4.White; return; + } if (scorePosition == 1) { From d14a8d24b5c3cb7669a3b7537ba69820aa1e0fa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Dec 2020 16:42:20 +0900 Subject: [PATCH 16/68] Remove assert for now --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 6b0ca4d74c..42df0cfe8c 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -165,8 +165,6 @@ namespace osu.Game.Screens.Play.HUD public void UpdateScore(ScoreProcessor processor, ScoringMode mode) { - Debug.Assert(!UserQuit.Value); - if (LastHeader == null) return; From 1b34f2115f6ef323aa66bf07d3e0b7ac05e76606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Dec 2020 16:57:23 +0900 Subject: [PATCH 17/68] Remove dignostics using --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 42df0cfe8c..00e2b8bfa7 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; From 6b6b1514e2b7b2249db579297cfe94aa44a8b52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Dec 2020 12:58:37 +0100 Subject: [PATCH 18/68] Rename method to be less misleading As it doesn't only change colour, but also width. --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 4aeb65bb01..1bd279922c 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play.HUD positionText.Text = $"#{scorePosition.Value.FormatRank()}"; positionText.FadeTo(scorePosition.HasValue ? 1 : 0); - updateColour(); + updateState(); } } @@ -231,20 +231,20 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); - HasQuit.BindValueChanged(v => updateColour()); + HasQuit.BindValueChanged(v => updateState()); } protected override void LoadComplete() { base.LoadComplete(); - updateColour(); + updateState(); FinishTransforms(true); } private const double panel_transition_duration = 500; - private void updateColour() + private void updateState() { if (HasQuit.Value) { From f75dccc9e4872477c66a484ddc13ef943511e43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Dec 2020 13:00:27 +0100 Subject: [PATCH 19/68] Explicitly use discard in value changed callback --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 1bd279922c..43e259695e 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); - HasQuit.BindValueChanged(v => updateState()); + HasQuit.BindValueChanged(_ => updateState()); } protected override void LoadComplete() From 4d61c143db058bcc47bac98490dbaa246e873872 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 14:56:29 +0900 Subject: [PATCH 20/68] Fix lookup cache throwing a null reference if no matches were successful --- osu.Game/Database/UserLookupCache.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 05d6930992..e7ddf4c567 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -72,6 +72,7 @@ namespace osu.Game.Database var request = new GetUsersRequest(userTasks.Keys.ToArray()); // rather than queueing, we maintain our own single-threaded request stream. + // todo: we probably want retry logic here. api.Perform(request); // Create a new request task if there's still more users to query. @@ -82,14 +83,19 @@ namespace osu.Game.Database createNewTask(); } - foreach (var user in request.Result.Users) - { - if (userTasks.TryGetValue(user.Id, out var tasks)) - { - foreach (var task in tasks) - task.SetResult(user); + List foundUsers = request.Result?.Users; - userTasks.Remove(user.Id); + if (foundUsers != null) + { + foreach (var user in foundUsers) + { + if (userTasks.TryGetValue(user.Id, out var tasks)) + { + foreach (var task in tasks) + task.SetResult(user); + + userTasks.Remove(user.Id); + } } } From 046a76cb1d1597414ae08e9e47a2dfa1dcac8e09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 14:56:53 +0900 Subject: [PATCH 21/68] Allow null users to still be displayed in the participant list The fix here is correcting the access of `user.Country`. The deicision to have null users display is because this is the best we can do (if osu-web could not resolve the user). We still want the users in the lobby to be aware of this user's presence, rather than hiding them from view. osu-stable does a similar thing, showing these users as `[Loading]`. I decided to go with blank names instead because having *any* text there causes confusion. We can iterate on this in future design updates. --- .../TestSceneMultiplayerParticipantsList.cs | 10 ++++++++++ .../Multiplayer/Participants/ParticipantPanel.cs | 13 ++++++------- .../Visual/Multiplayer/TestMultiplayerClient.cs | 2 ++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 9181170bee..968a869532 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -43,6 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); } + [Test] + public void TestAddNullUser() + { + AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); + + AddStep("add non-resolvable user", () => Client.AddNullUser(-3)); + + AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); + } + [Test] public void TestRemoveUser() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index de3069b2f6..f99655e305 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -45,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [BackgroundDependencyLoader] private void load() { - Debug.Assert(User.User != null); + var user = User.User; var backgroundColour = Color4Extensions.FromHex("#33413C"); @@ -82,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, Width = 0.75f, - User = User.User, + User = user, Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f)) }, new FillFlowContainer @@ -98,28 +97,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, - User = User.User + User = user }, new UpdateableFlag { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(30, 20), - Country = User.User.Country + Country = user?.Country }, new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), - Text = User.User.Username + Text = user?.Username }, new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 14), - Text = User.User.CurrentModeRank != null ? $"#{User.User.CurrentModeRank}" : string.Empty + Text = user?.CurrentModeRank != null ? $"#{user.CurrentModeRank}" : string.Empty } } }, diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 9a839c8d22..2ce5211757 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -28,6 +28,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public void AddUser(User user) => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(user.Id) { User = user }); + public void AddNullUser(int userId) => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(userId)); + public void RemoveUser(User user) { Debug.Assert(Room != null); From bdbc210f6d6cbea01a5d8106b31c34be5c43955c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 16:51:45 +0900 Subject: [PATCH 22/68] Update fastlane and dependencies --- Gemfile.lock | 56 +++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a4b49af7e4..8ac863c9a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,27 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.2) + CFPropertyList (3.0.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.354.0) - aws-sdk-core (3.104.3) + aws-partitions (1.413.0) + aws-sdk-core (3.110.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.36.0) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-kms (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.78.0) - aws-sdk-core (~> 3, >= 3.104.3) + aws-sdk-s3 (1.87.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.1) + aws-sigv4 (1.2.2) aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.3) + babosa (1.0.4) claide (1.0.3) colored (1.2) colored2 (3.1.2) @@ -29,22 +29,23 @@ GEM highline (~> 1.7.2) declarative (0.0.20) declarative-option (0.1.0) - digest-crc (0.6.1) - rake (~> 13.0) + digest-crc (0.6.3) + rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.0.0) - excon (0.76.0) - faraday (1.0.1) + emoji_regex (3.2.1) + excon (0.78.1) + faraday (1.2.0) multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) + ruby2_keywords + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) http-cookie (~> 1.0.0) faraday_middleware (1.0.0) faraday (~> 1.0) - fastimage (2.2.0) - fastlane (2.156.0) + fastimage (2.2.1) + fastlane (2.170.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) aws-sdk-s3 (~> 1.0) @@ -96,17 +97,17 @@ GEM google-cloud-core (1.5.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.3.3) + google-cloud-env (1.4.0) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.1) - google-cloud-storage (1.27.0) + google-cloud-storage (1.29.2) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.13.1) + googleauth (0.14.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -118,10 +119,10 @@ GEM domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.4.0) - json (2.3.1) - jwt (2.2.1) + json (2.5.1) + jwt (2.2.2) memoist (0.16.2) - mini_magick (4.10.1) + mini_magick (4.11.0) mini_mime (1.0.2) mini_portile2 (2.4.0) multi_json (1.15.0) @@ -132,14 +133,15 @@ GEM mini_portile2 (~> 2.4.0) os (1.1.1) plist (3.5.0) - public_suffix (4.0.5) - rake (13.0.1) + public_suffix (4.0.6) + rake (13.0.3) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) + ruby2_keywords (0.0.2) rubyzip (2.3.0) security (0.1.3) signet (0.14.0) @@ -168,7 +170,7 @@ GEM unf_ext (0.0.7.7) unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.18.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 2e3537e9664d24dbb6f5fec80e0c9a3e42cb1664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 16:52:54 +0900 Subject: [PATCH 23/68] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index fc01f9bf1d..cd2ce58c55 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cbf9f6f1bd..3e1b56c29c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index adbcc0ef1c..85ba0590ea 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 5ca9a6a98018544d67f61ef1912596084a70999a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 20:05:48 +0900 Subject: [PATCH 24/68] Add xmldoc on UserLookupCache's lookup method --- osu.Game/Database/UserLookupCache.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 05d6930992..8a99e27708 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -17,6 +17,12 @@ namespace osu.Game.Database [Resolved] private IAPIProvider api { get; set; } + /// + /// Perform an API lookup on the specified user, populating a model. + /// + /// The user to lookup. + /// An optional cancellation token. + /// The populated user, or null if the user does not exist or the request could not be satisfied. public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) From 545dcac4ec0950f2ade486c55a88f9f39b1802ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 20:13:24 +0900 Subject: [PATCH 25/68] Add null hinting on UserLookupCache query method --- osu.Game/Database/UserLookupCache.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 8a99e27708..49ea82a4fe 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -23,6 +24,7 @@ namespace osu.Game.Database /// The user to lookup. /// An optional cancellation token. /// The populated user, or null if the user does not exist or the request could not be satisfied. + [ItemCanBeNull] public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) From 447a55ce11cf144eddc85dea8e37d99c2dbb46e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 20:16:53 +0900 Subject: [PATCH 26/68] Fix incorrect null handling in GameplayLeaderboard --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 6 ++++-- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 83b70911c6..cb20deb272 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -1,6 +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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -56,6 +57,7 @@ namespace osu.Game.Screens.Play.HUD } } + [CanBeNull] public User User { get; } private readonly bool trackedPlayer; @@ -68,7 +70,7 @@ namespace osu.Game.Screens.Play.HUD /// /// The score's player. /// Whether the player is the local user or a replay player. - public GameplayLeaderboardScore(User user, bool trackedPlayer) + public GameplayLeaderboardScore([CanBeNull] User user, bool trackedPlayer) { User = user; this.trackedPlayer = trackedPlayer; @@ -180,7 +182,7 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreLeft, Colour = Color4.White, Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), - Text = User.Username, + Text = User?.Username, Truncate = true, Shadow = false, } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 00e2b8bfa7..e7e5459f76 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play.HUD var trackedUser = new TrackedUserData(); userScores[userId] = trackedUser; - var leaderboardScore = AddPlayer(resolvedUser, resolvedUser.Id == api.LocalUser.Value.Id); + var leaderboardScore = AddPlayer(resolvedUser, resolvedUser?.Id == api.LocalUser.Value.Id); ((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy); ((IBindable)leaderboardScore.TotalScore).BindTo(trackedUser.Score); From 8f0413472cfd57b480d0feedd365a5ce22aae3ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 20:30:08 +0900 Subject: [PATCH 27/68] Add test coverage of null users in scoreboard --- .../Visual/Online/TestSceneCurrentlyPlayingDisplay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index 1666c9cde4..1baa07f208 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -90,11 +90,17 @@ namespace osu.Game.Tests.Visual.Online }; protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) - => Task.FromResult(new User + { + // tests against failed lookups + if (lookup == 13) + return Task.FromResult(null); + + return Task.FromResult(new User { Id = lookup, Username = usernames[lookup % usernames.Length], }); + } } } } From 6254907ef984032f158bb0a3164862cc13a5d435 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 20:31:08 +0900 Subject: [PATCH 28/68] Move multiplayer leaderboard test to correct namespace --- .../TestSceneMultiplayerGameplayLeaderboard.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename osu.Game.Tests/Visual/{Gameplay => Multiplayer}/TestSceneMultiplayerGameplayLeaderboard.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs similarity index 98% rename from osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 975c54c3f6..d0b1e77549 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -20,10 +20,9 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; -using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Online; -namespace osu.Game.Tests.Visual.Gameplay +namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { From fb21b7c0167bed184df6c71dcfcd7a1f29c3ecfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 12:09:32 +0100 Subject: [PATCH 29/68] Add failing test cases --- .../TestSceneMultiplayerMatchSongSelect.cs | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs new file mode 100644 index 0000000000..95c333e9f4 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -0,0 +1,145 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Taiko; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerMatchSongSelect : RoomTestScene + { + private BeatmapManager manager; + private RulesetStore rulesets; + + private List beatmaps; + + private TestMultiplayerMatchSongSelect songSelect; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + beatmaps = new List(); + + for (int i = 0; i < 8; ++i) + { + int beatmapId = 10 * 10 + i; + + int length = RNG.Next(30000, 200000); + double bpm = RNG.NextSingle(80, 200); + + beatmaps.Add(new BeatmapInfo + { + Ruleset = rulesets.GetRuleset(i % 4), + OnlineBeatmapID = beatmapId, + Length = length, + BPM = bpm, + BaseDifficulty = new BeatmapDifficulty() + }); + } + + manager.Import(new BeatmapSetInfo + { + OnlineBeatmapSetID = 10, + Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + Artist = "Some Artist", + Title = "Some Beatmap", + AuthorString = "Some Author" + }, + Beatmaps = beatmaps, + DateAdded = DateTimeOffset.UtcNow + }).Wait(); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("reset", () => + { + Ruleset.Value = new OsuRuleset().RulesetInfo; + Beatmap.SetDefault(); + SelectedMods.SetDefault(); + }); + + AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect())); + AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); + } + + [Test] + public void TestBeatmapRevertedOnExitIfNoSelection() + { + BeatmapInfo selectedBeatmap = null; + + AddStep("select beatmap", + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.RulesetID == new OsuRuleset().LegacyID).ElementAt(1))); + AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); + + AddStep("exit song select", () => songSelect.Exit()); + AddAssert("beatmap reverted", () => Beatmap.IsDefault); + } + + [Test] + public void TestModsRevertedOnExitIfNoSelection() + { + AddStep("change mods", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); + + AddStep("exit song select", () => songSelect.Exit()); + AddAssert("mods reverted", () => SelectedMods.Value.Count == 0); + } + + [Test] + public void TestRulesetRevertedOnExitIfNoSelection() + { + AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo); + + AddStep("exit song select", () => songSelect.Exit()); + AddAssert("ruleset reverted", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); + } + + [Test] + public void TestBeatmapConfirmed() + { + BeatmapInfo selectedBeatmap = null; + + AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("select beatmap", + () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.RulesetID == new TaikoRuleset().LegacyID))); + AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); + AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() }); + + AddStep("confirm selection", () => songSelect.FinaliseSelection()); + AddStep("exit song select", () => songSelect.Exit()); + + AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); + AddAssert("ruleset not changed", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo)); + AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime); + } + + private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect + { + public new BeatmapCarousel Carousel => base.Carousel; + } + } +} From f16b516e5880d8d27a439ab0a6615535879eee34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 12:32:06 +0100 Subject: [PATCH 30/68] Revert user changes if no selection was made --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 0842574f54..72539a2e3a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,6 +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.Collections.Generic; using System.Linq; using Humanizer; using osu.Framework.Allocation; @@ -8,9 +9,12 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; namespace osu.Game.Screens.OnlinePlay.Multiplayer @@ -29,6 +33,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private LoadingLayer loadingLayer; + private WorkingBeatmap initialBeatmap; + private RulesetInfo initialRuleset; + private IReadOnlyList initialMods; + + private bool itemSelected; + public MultiplayerMatchSongSelect() { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; @@ -38,10 +48,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void load() { AddInternal(loadingLayer = new LoadingLayer(Carousel)); + initialBeatmap = Beatmap.Value; + initialRuleset = Ruleset.Value; + initialMods = Mods.Value.ToList(); } protected override bool OnStart() { + itemSelected = true; var item = new PlaylistItem(); item.Beatmap.Value = Beatmap.Value.BeatmapInfo; @@ -82,6 +96,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } + public override bool OnExiting(IScreen next) + { + if (!itemSelected) + { + Beatmap.Value = initialBeatmap; + Ruleset.Value = initialRuleset; + Mods.Value = initialMods; + } + + return base.OnExiting(next); + } + protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); } } From 92d74a9343df3fd7378205fe26ff1753ebf52134 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 20:48:14 +0900 Subject: [PATCH 31/68] Fix potential nullref in test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index c0a021436e..17fe09f2c6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public bool CheckPositionByUsername(string username, int? expectedPosition) { - var scoreItem = this.FirstOrDefault(i => i.User.Username == username); + var scoreItem = this.FirstOrDefault(i => i.User?.Username == username); return scoreItem != null && scoreItem.ScorePosition == expectedPosition; } From a9822800fc368986a152723b883d9842b920f54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 13:00:05 +0100 Subject: [PATCH 32/68] Add more null hinting in GameplayLeaderboard --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index e33cc05e64..7b94bf19ec 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -42,7 +43,7 @@ namespace osu.Game.Screens.Play.HUD /// Whether the player should be tracked on the leaderboard. /// Set to true for the local player or a player whose replay is currently being played. /// - public ILeaderboardScore AddPlayer(User user, bool isTracked) + public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked) { var drawable = new GameplayLeaderboardScore(user, isTracked) { From b352c1503ff2eff470dd573d08ad8e2015c07906 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 28 Dec 2020 15:13:33 +0100 Subject: [PATCH 33/68] Fix IntentFilter capturing all file types Removed string arrays and split the IntentFilter into multiple. Also added DataHost and DataMimeType --- osu.Android/OsuGameActivity.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 953c06f4e2..7abaff3cdb 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -16,7 +16,10 @@ using osu.Framework.Android; namespace osu.Android { [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] - [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataPathPatterns = new[] { ".*\\.osz", ".*\\.osk" }, DataMimeType = "application/*")] + [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "file", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "file", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { From d971aa5295b7d20a471d8be62fdc4c6e263dd139 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 28 Dec 2020 15:54:21 +0100 Subject: [PATCH 34/68] Remove file intents and add Send intent Removed IntentFilters with DataScheme = "file" Added Intent.ActionSend with application/octet-stream and application/zip --- osu.Android/OsuGameActivity.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 7abaff3cdb..cdf033e685 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -16,10 +16,10 @@ using osu.Framework.Android; namespace osu.Android { [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] - [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] - [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] - [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "file", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] - [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "file", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "application/octet-stream")] + [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "application/zip")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { From d2301068b6c2a04961986570075ef5075c2cf168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 16:35:33 +0100 Subject: [PATCH 35/68] Fix changelog header staying dimmed after build show --- .../Overlays/Changelog/ChangelogUpdateStreamControl.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs index 6bbff045b5..aa36a5c8fd 100644 --- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs +++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs @@ -7,12 +7,11 @@ namespace osu.Game.Overlays.Changelog { public class ChangelogUpdateStreamControl : OverlayStreamControl { - protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value); - - protected override void LoadComplete() + public ChangelogUpdateStreamControl() { - // suppress base logic of immediately selecting first item if one exists - // (we always want to start with no stream selected). + SelectFirstTabByDefault = false; } + + protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value); } } From 1d311a66805aad4c784ff0a51bc523e69019ccde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 19:11:44 +0100 Subject: [PATCH 36/68] Change PlayingUsers population logic to match expectations --- .../Multiplayer/StatefulMultiplayerClient.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index fcb0977f53..c15401e99d 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -133,6 +133,7 @@ namespace osu.Game.Online.Multiplayer apiRoom = null; Room = null; + PlayingUsers.Clear(); RoomUpdated?.Invoke(); }, false); @@ -302,8 +303,7 @@ namespace osu.Game.Online.Multiplayer Room.Users.Single(u => u.UserID == userId).State = state; - if (state != MultiplayerUserState.Playing) - PlayingUsers.Remove(userId); + updatePlayingUsers(userId, state); RoomUpdated?.Invoke(); }, false); @@ -337,8 +337,6 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - PlayingUsers.AddRange(Room.Users.Where(u => u.State == MultiplayerUserState.Playing).Select(u => u.UserID)); - MatchStarted?.Invoke(); }, false); @@ -454,5 +452,17 @@ namespace osu.Game.Online.Multiplayer apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. apiRoom.Playlist.Add(playlistItem); } + + private void updatePlayingUsers(int userId, MultiplayerUserState state) + { + bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; + bool wasPlaying = PlayingUsers.Contains(userId); + + if (!wasPlaying && isPlaying) + PlayingUsers.Add(userId); + + if (wasPlaying && !isPlaying) + PlayingUsers.Remove(userId); + } } } From a014d0ec18953854650c33263489273171d828f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 19:12:58 +0100 Subject: [PATCH 37/68] Use PlayingUsers when constructing player directly --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 58314c3774..ba0ed16cf4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(client.Room != null); - int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray(); + int[] userIds = client.PlayingUsers.ToArray(); StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); } From f7407347f78445a141c196fbcda5282f1ba14250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Dec 2020 20:01:17 +0100 Subject: [PATCH 38/68] Add test coverage of PlayingUsers tracking --- .../StatefulMultiplayerClientTest.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs new file mode 100644 index 0000000000..8d543e7485 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -0,0 +1,57 @@ +// 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 Humanizer; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Online.Multiplayer; +using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Tests.NonVisual.Multiplayer +{ + [HeadlessTest] + public class StatefulMultiplayerClientTest : MultiplayerTestScene + { + [Test] + public void TestPlayingUserTracking() + { + int id = 2000; + + AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5); + checkPlayingUserCount(0); + + changeState(3, MultiplayerUserState.WaitingForLoad); + checkPlayingUserCount(3); + + changeState(3, MultiplayerUserState.Playing); + checkPlayingUserCount(3); + + changeState(3, MultiplayerUserState.Results); + checkPlayingUserCount(0); + + changeState(6, MultiplayerUserState.WaitingForLoad); + checkPlayingUserCount(6); + + AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User)); + checkPlayingUserCount(5); + + AddStep("leave room", () => Client.LeaveRoom()); + checkPlayingUserCount(0); + } + + private void checkPlayingUserCount(int expectedCount) + => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.PlayingUsers.Count == expectedCount); + + private void changeState(int userCount, MultiplayerUserState state) + => AddStep($"{"user".ToQuantity(userCount)} in {state}", () => + { + for (int i = 0; i < userCount; ++i) + { + var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!"); + Client.ChangeUserState(userId, state); + } + }); + } +} From 770a5a85dff5cff621287732639eb33534ea95ad Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Mon, 28 Dec 2020 20:57:08 +0100 Subject: [PATCH 39/68] Merge Intent.ActionSend into one IntentFilter Co-authored-by: Lucas A. --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index cdf033e685..da69b516dd 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -19,7 +19,7 @@ namespace osu.Android [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "application/octet-stream")] - [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "application/zip")] + [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { From 355ecc4499bf1357612c6c6607b65a46e84ea432 Mon Sep 17 00:00:00 2001 From: TheOmyNomy Date: Tue, 29 Dec 2020 12:37:57 +1100 Subject: [PATCH 40/68] Change cursor trail blending mode to match stable --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index f18d3191ca..af9ea99232 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private double lastTrailTime; private IBindable cursorSize; - public LegacyCursorTrail() - { - Blending = BlendingParameters.Additive; - } - [BackgroundDependencyLoader] private void load(ISkinSource skin, OsuConfigManager config) { Texture = skin.GetTexture("cursortrail"); disjointTrail = skin.GetTexture("cursormiddle") == null; + Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit; + if (Texture != null) { // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. From 6aeb7ece660ef5600f8b8da00ac38fd05a5def41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 14:25:08 +0900 Subject: [PATCH 41/68] Tidy up update state code, naming, xmldoc --- .../Multiplayer/StatefulMultiplayerClient.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index c15401e99d..bf62c450c9 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -303,7 +303,7 @@ namespace osu.Game.Online.Multiplayer Room.Users.Single(u => u.UserID == userId).State = state; - updatePlayingUsers(userId, state); + updateUserPlayingState(userId, state); RoomUpdated?.Invoke(); }, false); @@ -453,15 +453,22 @@ namespace osu.Game.Online.Multiplayer apiRoom.Playlist.Add(playlistItem); } - private void updatePlayingUsers(int userId, MultiplayerUserState state) + /// + /// For the provided user ID, update whether the user is included in . + /// + /// The user's ID. + /// The new state of the user. + private void updateUserPlayingState(int userId, MultiplayerUserState state) { - bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; bool wasPlaying = PlayingUsers.Contains(userId); + bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; - if (!wasPlaying && isPlaying) + if (isPlaying == wasPlaying) + return; + + if (isPlaying) PlayingUsers.Add(userId); - - if (wasPlaying && !isPlaying) + else PlayingUsers.Remove(userId); } } From e3a41f61186525d18d01bd8306e1d2a6f7a2baa3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 14:27:33 +0900 Subject: [PATCH 42/68] Rename variable to make more sense It needs to be explicitly stated that the users in this list are related to the *joined* room. Especially since it's sharing its variable name with `SpectatorStreamingClient` where it has the opposite meaning (is a list of *globally* playing players). --- .../Multiplayer/StatefulMultiplayerClientTest.cs | 2 +- .../TestSceneMultiplayerGameplayLeaderboard.cs | 6 +++--- .../Multiplayer/StatefulMultiplayerClient.cs | 16 ++++++++-------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 8d543e7485..a2ad37cf4a 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer } private void checkPlayingUserCount(int expectedCount) - => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.PlayingUsers.Count == expectedCount); + => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount); private void changeState(int userCount, MultiplayerUserState state) => AddStep($"{"user".ToQuantity(userCount)} in {state}", () => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index d0b1e77549..d016accc25 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -62,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); - Client.PlayingUsers.Clear(); - Client.PlayingUsers.AddRange(streamingClient.PlayingUsers); + Client.CurrentMatchPlayingUserIds.Clear(); + Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers); Children = new Drawable[] { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestUserQuit() { - AddRepeatStep("mark user quit", () => Client.PlayingUsers.RemoveAt(0), users); + AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users); } public class TestMultiplayerStreaming : SpectatorStreamingClient diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index bf62c450c9..bb690d786a 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -61,9 +61,9 @@ namespace osu.Game.Online.Multiplayer public MultiplayerRoom? Room { get; private set; } /// - /// The users currently in gameplay. + /// The users in the joined which are currently in gameplay. /// - public readonly BindableList PlayingUsers = new BindableList(); + public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; @@ -133,7 +133,7 @@ namespace osu.Game.Online.Multiplayer apiRoom = null; Room = null; - PlayingUsers.Clear(); + CurrentMatchPlayingUserIds.Clear(); RoomUpdated?.Invoke(); }, false); @@ -254,7 +254,7 @@ namespace osu.Game.Online.Multiplayer return; Room.Users.Remove(user); - PlayingUsers.Remove(user.UserID); + CurrentMatchPlayingUserIds.Remove(user.UserID); RoomUpdated?.Invoke(); }, false); @@ -454,22 +454,22 @@ namespace osu.Game.Online.Multiplayer } /// - /// For the provided user ID, update whether the user is included in . + /// For the provided user ID, update whether the user is included in . /// /// The user's ID. /// The new state of the user. private void updateUserPlayingState(int userId, MultiplayerUserState state) { - bool wasPlaying = PlayingUsers.Contains(userId); + bool wasPlaying = CurrentMatchPlayingUserIds.Contains(userId); bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay; if (isPlaying == wasPlaying) return; if (isPlaying) - PlayingUsers.Add(userId); + CurrentMatchPlayingUserIds.Add(userId); else - PlayingUsers.Remove(userId); + CurrentMatchPlayingUserIds.Remove(userId); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ba0ed16cf4..ffa36ecfdb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Debug.Assert(client.Room != null); - int[] userIds = client.PlayingUsers.ToArray(); + int[] userIds = client.CurrentMatchPlayingUserIds.ToArray(); StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds)); } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index e7e5459f76..d4ce542a67 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -84,11 +84,11 @@ namespace osu.Game.Screens.Play.HUD // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. foreach (int userId in playingUsers) { - if (!multiplayerClient.PlayingUsers.Contains(userId)) + if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId)) usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); } - playingUsers.BindTo(multiplayerClient.PlayingUsers); + playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds); playingUsers.BindCollectionChanged(usersChanged); } From f31a0e455a7834e839643a711df9dcd96409c315 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 14:29:40 +0900 Subject: [PATCH 43/68] Minor xmldoc rewording --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index bb690d786a..39d119b2a4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Multiplayer public MultiplayerRoom? Room { get; private set; } /// - /// The users in the joined which are currently in gameplay. + /// The users in the joined which are participating in the current gameplay loop. /// public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); From 45c578b85780f81051035dee3094efeef995fff9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 15:10:08 +0900 Subject: [PATCH 44/68] Remove selection polling from multiplayer Looks like this was just copy-paste without any thought into whether it should exist. It really shouldn't exist. This is a thing for the playlists system because the *whole system* there relies on polling the web API to get updated information. In the case of mutliplayer, we hand off all communications to the realtime server at the point of joining the rooms. The argument that this was there to do faster polling on the selection isn't valid since the polling times were the same for both cases. Closes #11348. --- .../OnlinePlay/Multiplayer/Multiplayer.cs | 5 +--- .../Multiplayer/MultiplayerRoomManager.cs | 28 +------------------ 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 76f5c74433..310617a0bc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -33,7 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) { multiplayerRoomManager.TimeBetweenListingPolls.Value = 0; - multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0; } else { @@ -41,18 +40,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { case LoungeSubScreen _: multiplayerRoomManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000; - multiplayerRoomManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000; break; // Don't poll inside the match or anywhere else. default: multiplayerRoomManager.TimeBetweenListingPolls.Value = 0; - multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0; break; } } - Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})"); + Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value})"); } protected override Room CreateNewRoom() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 3cb263298f..5c327266a3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private StatefulMultiplayerClient multiplayerClient { get; set; } public readonly Bindable TimeBetweenListingPolls = new Bindable(); - public readonly Bindable TimeBetweenSelectionPolls = new Bindable(); + private readonly IBindable isConnected = new Bindable(); private readonly Bindable allowPolling = new Bindable(); @@ -119,11 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls }, AllowPolling = { BindTarget = allowPolling } }, - new MultiplayerSelectionPollingComponent - { - TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls }, - AllowPolling = { BindTarget = allowPolling } - } }; private class MultiplayerListingPollingComponent : ListingPollingComponent @@ -146,26 +141,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll(); } - - private class MultiplayerSelectionPollingComponent : SelectionPollingComponent - { - public readonly IBindable AllowPolling = new Bindable(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - AllowPolling.BindValueChanged(allowPolling => - { - if (!allowPolling.NewValue) - return; - - if (IsLoaded) - PollImmediately(); - }); - } - - protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll(); - } } } From 2cb84c5111b9e244d98492e4b7fe43e3ef553492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 15:19:52 +0900 Subject: [PATCH 45/68] Fix error message being shown to user on multiplayer disconnection when not in room --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 39d119b2a4..dc80488d39 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -84,7 +84,7 @@ namespace osu.Game.Online.Multiplayer IsConnected.BindValueChanged(connected => { // clean up local room state on server disconnect. - if (!connected.NewValue) + if (!connected.NewValue && Room != null) { Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); LeaveRoom().CatchUnobservedExceptions(); From 03b78d1c4b2de871626068a454b163448f216e09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 15:27:22 +0900 Subject: [PATCH 46/68] Handle SocketExceptions and HttpRequestExceptions more silently These can occur when a network connection is completely unavailable (ie. host resolution failures are occurring). Currently these would appear as important errors which spammed the notification overlay every retry forever, while no network connection is available. I also took this opportunity to remove a lot of `bool` passing which was no longer in use (previously the fail count / retry process was different to what we have today). --- osu.Game/Online/API/APIAccess.cs | 46 ++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 133ba22406..2aaea22155 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -293,8 +294,21 @@ namespace osu.Game.Online.API failureCount = 0; return true; } + catch (HttpRequestException re) + { + log.Add($"{nameof(HttpRequestException)} while performing request {req}: {re.Message}"); + handleFailure(); + return false; + } + catch (SocketException se) + { + log.Add($"{nameof(SocketException)} while performing request {req}: {se.Message}"); + handleFailure(); + return false; + } catch (WebException we) { + log.Add($"{nameof(WebException)} while performing request {req}: {we.Message}"); handleWebException(we); return false; } @@ -312,7 +326,7 @@ namespace osu.Game.Online.API /// public IBindable State => state; - private bool handleWebException(WebException we) + private void handleWebException(WebException we) { HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); @@ -330,26 +344,24 @@ namespace osu.Game.Online.API { case HttpStatusCode.Unauthorized: Logout(); - return true; + break; case HttpStatusCode.RequestTimeout: - failureCount++; - log.Add($@"API failure count is now {failureCount}"); - - if (failureCount < 3) - // we might try again at an api level. - return false; - - if (State.Value == APIState.Online) - { - state.Value = APIState.Failing; - flushQueue(); - } - - return true; + handleFailure(); + break; } + } - return true; + private void handleFailure() + { + failureCount++; + log.Add($@"API failure count is now {failureCount}"); + + if (failureCount >= 3 && State.Value == APIState.Online) + { + state.Value = APIState.Failing; + flushQueue(); + } } public bool IsLoggedIn => localUser.Value.Id > 1; From 4d04e0dee7c73eaab08ecb4a47cee7822221972a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 16:25:51 +0900 Subject: [PATCH 47/68] Disallow entering the playlists/multiplayer screens if API is failing --- osu.Game/Screens/Menu/ButtonSystem.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 474cbde192..65a16f2fcb 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Menu private void onMultiplayer() { - if (!api.IsLoggedIn) + if (!api.IsLoggedIn || api.State.Value != APIState.Online) { notifications?.Post(new SimpleNotification { @@ -177,11 +177,11 @@ namespace osu.Game.Screens.Menu private void onPlaylists() { - if (!api.IsLoggedIn) + if (!api.IsLoggedIn || api.State.Value != APIState.Online) { notifications?.Post(new SimpleNotification { - Text = "You gotta be logged in to multi 'yo!", + Text = "You gotta be logged in to view playlists 'yo!", Icon = FontAwesome.Solid.Globe, Activated = () => { From 906a9b79b5b62a6d25c304549a6308451127079b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 16:47:36 +0900 Subject: [PATCH 48/68] Show an error when forcefully exiting online play due to API failure --- osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 4074dd1573..75612516a9 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; @@ -165,7 +166,10 @@ namespace osu.Game.Screens.OnlinePlay private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { if (state.NewValue != APIState.Online) + { + Logger.Log("API connection was lost, can't continue with online play", LoggingTarget.Network, LogLevel.Important); Schedule(forcefullyExit); + } }); protected override void LoadComplete() From 6bbd0ecfac9fc77ae680064ec61b29f2409380e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Dec 2020 17:39:00 +0900 Subject: [PATCH 49/68] Remove unused lock object --- osu.Game/Online/Multiplayer/MultiplayerRoom.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index 2134e50d72..99f0eae2b3 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -42,8 +42,6 @@ namespace osu.Game.Online.Multiplayer /// public MultiplayerRoomUser? Host { get; set; } - private object writeLock = new object(); - [JsonConstructor] public MultiplayerRoom(in long roomId) { From 5d2319923324b48fa749474c11686db3f82b41d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 29 Dec 2020 10:56:29 +0100 Subject: [PATCH 50/68] Trim redundant IsLoggedIn checks --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 65a16f2fcb..3ac9bb6c24 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Menu private void onMultiplayer() { - if (!api.IsLoggedIn || api.State.Value != APIState.Online) + if (api.State.Value != APIState.Online) { notifications?.Post(new SimpleNotification { @@ -177,7 +177,7 @@ namespace osu.Game.Screens.Menu private void onPlaylists() { - if (!api.IsLoggedIn || api.State.Value != APIState.Online) + if (api.State.Value != APIState.Online) { notifications?.Post(new SimpleNotification { From 361d215ab4def9965856c4e998a6373c504bfc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 29 Dec 2020 10:56:59 +0100 Subject: [PATCH 51/68] Reword notification messages to match new logic --- osu.Game/Screens/Menu/ButtonSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 3ac9bb6c24..fd4d74dc6b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Menu { notifications?.Post(new SimpleNotification { - Text = "You gotta be logged in to multi 'yo!", + Text = "You gotta be online to multi 'yo!", Icon = FontAwesome.Solid.Globe, Activated = () => { @@ -181,7 +181,7 @@ namespace osu.Game.Screens.Menu { notifications?.Post(new SimpleNotification { - Text = "You gotta be logged in to view playlists 'yo!", + Text = "You gotta be online to view playlists 'yo!", Icon = FontAwesome.Solid.Globe, Activated = () => { From f2163a471a83eee0c0d41486f256a9b607113e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 29 Dec 2020 11:53:42 +0100 Subject: [PATCH 52/68] Trim missed reference to deleted member --- .../Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 7a3845cbf3..80d1acd145 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomManager = { TimeBetweenListingPolls = { Value = 1 }, - TimeBetweenSelectionPolls = { Value = 1 } } }; From 3552034ffe0044dc5edff9195e6e609504336c84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Dec 2020 00:55:27 +0900 Subject: [PATCH 53/68] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index cd2ce58c55..611f0d05f4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3e1b56c29c..93aa2bc701 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 85ba0590ea..5445adb3fb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 497d644a19f04ad19d990bbe7a042f1a556265f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Dec 2020 20:24:50 +0900 Subject: [PATCH 54/68] Move thread safety / locking logic from MultiplayerRoom --- .../Online/Multiplayer/MultiplayerRoom.cs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index 99f0eae2b3..12fcf25ace 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -5,9 +5,7 @@ using System; using System.Collections.Generic; -using System.Threading; using Newtonsoft.Json; -using osu.Framework.Allocation; namespace osu.Game.Online.Multiplayer { @@ -48,27 +46,6 @@ namespace osu.Game.Online.Multiplayer RoomID = roomId; } - private object updateLock = new object(); - - private ManualResetEventSlim freeForWrite = new ManualResetEventSlim(true); - - /// - /// Request a lock on this room to perform a thread-safe update. - /// - public IDisposable LockForUpdate() - { - // ReSharper disable once InconsistentlySynchronizedField - freeForWrite.Wait(); - - lock (updateLock) - { - freeForWrite.Wait(); - freeForWrite.Reset(); - - return new ValueInvokeOnDisposal(this, r => freeForWrite.Set()); - } - } - public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]"; } } From eb64e6bf4da427df3b3b0f1bfab8dbab42f4819f Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 30 Dec 2020 23:35:07 +0100 Subject: [PATCH 55/68] Remove duplicate application/octet-stream --- osu.Android/OsuGameActivity.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index da69b516dd..9d28ad7c5b 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -18,7 +18,6 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] - [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = "application/octet-stream")] [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity From f9196ae9767653c75dc2d9ff6a0a273671a4466c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Dec 2020 16:36:20 +0900 Subject: [PATCH 56/68] Fix PerformFromMenuRunner failing if CurrentScreen is null --- osu.Game/PerformFromMenuRunner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index e2d4fc6051..9222a64023 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -73,13 +73,16 @@ namespace osu.Game // find closest valid target IScreen current = getCurrentScreen(); + if (current == null) + return; + // a dialog may be blocking the execution for now. if (checkForDialog(current)) return; game?.CloseAllOverlays(false); // we may already be at the target screen type. - if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled) + if (validScreens.Contains(current.GetType()) && !beatmap.Disabled) { complete(); return; From 00c6703c51df3cd05788d1c41d84b64d88de1ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 31 Dec 2020 10:27:42 +0100 Subject: [PATCH 57/68] Inline complete method as well For better guarantees that `finalAction` is actually called on the same screen that `checkCanComplete()` was (uses result of one `getCurrentScreen()` call throughout instead of calling multiple times). --- osu.Game/PerformFromMenuRunner.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 9222a64023..7999023998 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -84,7 +84,8 @@ namespace osu.Game // we may already be at the target screen type. if (validScreens.Contains(current.GetType()) && !beatmap.Disabled) { - complete(); + finalAction(current); + Cancel(); return; } @@ -138,11 +139,5 @@ namespace osu.Game lastEncounteredDialogScreen = current; return true; } - - private void complete() - { - finalAction(getCurrentScreen()); - Cancel(); - } } } From 2d279350ad5f6a4e544601c7be7bca768bef1cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 31 Dec 2020 11:29:02 +0100 Subject: [PATCH 58/68] Catch multiplayer client-related unobserved exceptions better Silencing an exception from a task continuation requires accessing `task.Exception` in any way, which was not done previously if `logOnError` was false. To resolve without having to worry whether the compiler will optimise away a useless access or now, just always log, but switch the logging level. The unimportant errors will be logged as debug and therefore essentially silenced on release builds (but could still be potentially useful in debugging). --- osu.Game/Extensions/TaskExtensions.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index a1215d786b..4138c2757a 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System; using System.Threading.Tasks; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; namespace osu.Game.Extensions @@ -13,13 +17,19 @@ namespace osu.Game.Extensions /// Avoids unobserved exceptions from being fired. /// /// The task. - /// Whether errors should be logged as important, or silently ignored. - public static void CatchUnobservedExceptions(this Task task, bool logOnError = false) + /// + /// Whether errors should be logged as errors visible to users, or as debug messages. + /// Logging as debug will essentially silence the errors on non-release builds. + /// + public static void CatchUnobservedExceptions(this Task task, bool logAsError = false) { task.ContinueWith(t => { - if (logOnError) - Logger.Log($"Error running task: {t.Exception?.Message ?? "unknown"}", LoggingTarget.Runtime, LogLevel.Important); + Exception? exception = t.Exception?.AsSingular(); + if (logAsError) + Logger.Error(exception, $"Error running task: {exception?.Message ?? "(unknown)"}", LoggingTarget.Runtime, true); + else + Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug); }, TaskContinuationOptions.NotOnRanToCompletion); } } From 17abe90c27ff837d39e97ec3678dad33bad3e6c9 Mon Sep 17 00:00:00 2001 From: mcendu Date: Thu, 31 Dec 2020 20:23:13 +0800 Subject: [PATCH 59/68] move SkinnableHealthDisplay Similar components are in osu.Game.Screens.Play.HUD while this is not --- .../Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs | 2 +- osu.Game/Screens/Play/{ => HUD}/SkinnableHealthDisplay.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename osu.Game/Screens/Play/{ => HUD}/SkinnableHealthDisplay.cs (95%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index e1b0820662..5bac8582d7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs similarity index 95% rename from osu.Game/Screens/Play/SkinnableHealthDisplay.cs rename to osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index d35d15d665..1f91f5e50f 100644 --- a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -5,10 +5,9 @@ using System; using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.HUD { public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay { From 7441cfd94ee93e764102eead7839bb2c862ce541 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:53:29 +0000 Subject: [PATCH 60/68] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/aspnet/AspNetCore) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/aspnet/AspNetCore/releases) - [Commits](https://github.com/aspnet/AspNetCore/compare/v3.1.9...v3.1.10) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 93aa2bc701..4c57eb6d00 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + From 0fd2e368c1c73e0d2e4932a620f2f4c6021633c7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:53:41 +0000 Subject: [PATCH 61/68] Bump Microsoft.NET.Test.Sdk from 16.8.0 to 16.8.3 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.8.0 to 16.8.3. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.8.0...v16.8.3) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 61ecd79e3d..51d2032795 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index fa7bfd7169..3261f632f2 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index d6a03da807..32243e0bc3 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a89645d881..210f81d111 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 83d7b4135a..9049b67f90 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index bc6b994988..dc4f22788d 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 652b0ccd8fe5c47dbde90e41f15d8d11baea76f7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:54:11 +0000 Subject: [PATCH 62/68] Bump Microsoft.AspNetCore.SignalR.Client from 3.1.9 to 3.1.10 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/aspnet/AspNetCore) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/aspnet/AspNetCore/releases) - [Commits](https://github.com/aspnet/AspNetCore/compare/v3.1.9...v3.1.10) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 93aa2bc701..3c8df17ee5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 5445adb3fb..f5c133263b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -78,7 +78,7 @@ $(NoWarn);NU1605 - + From 6cd838fd4b8e510739891e64019c192122e08f2f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:55:04 +0000 Subject: [PATCH 63/68] Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 3.3.1 to 3.3.2 Bumps [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v3.3.1...v3.3.2) Signed-off-by: dependabot-preview[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 551cb75077..9ec442aafa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + From fa73d0172e01492f6f22c8c457751c0ea37fb659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 2 Jan 2021 00:11:21 +0100 Subject: [PATCH 64/68] Keep SignalR at last working version on iOS --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index f5c133263b..5445adb3fb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -78,7 +78,7 @@ $(NoWarn);NU1605 - + From 72a6ca77559c409bc0a5671663510d1fbcbaffb6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Jan 2021 16:47:00 +0900 Subject: [PATCH 65/68] Allow signalr to retry connecting when connection is closed without an exception --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 24ea6abc4a..7cd1ef78f7 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -88,11 +88,12 @@ namespace osu.Game.Online.Multiplayer { isConnected.Value = false; - if (ex != null) - { - Logger.Log($"Multiplayer client lost connection: {ex}", LoggingTarget.Network); + Logger.Log(ex != null + ? $"Multiplayer client lost connection: {ex}" + : "Multiplayer client disconnected", LoggingTarget.Network); + + if (connection != null) await tryUntilConnected(); - } }; await tryUntilConnected(); From 66bd847b4507cb575549c01496dbabf819c6164b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 2 Jan 2021 11:53:06 +0100 Subject: [PATCH 66/68] Bump InspectCode tool to 2020.3.2 --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index dd53eefd23..58c24181d3 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "jetbrains.resharper.globaltools": { - "version": "2020.2.4", + "version": "2020.3.2", "commands": [ "jb" ] From 18ac97ca563e9a02d56c469e5c084ccfa4b1e978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 2 Jan 2021 12:21:53 +0100 Subject: [PATCH 67/68] Disable "merge sequential patterns" suggestions As they were considered to be detrimental to code readability. --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 22ea73858e..aa8f8739c1 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -106,6 +106,7 @@ HINT WARNING WARNING + DO_NOT_SHOW WARNING WARNING WARNING From 924af58f5b1ba2b5fcdaa00a79db21ce6c19bceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 2 Jan 2021 12:25:16 +0100 Subject: [PATCH 68/68] Replace using static with explicit nested reference This seems to be an inspectcode bug, as the code is correct and compiles, but let's just work around it for now. --- .../Visual/Components/TestScenePreviewTrackManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index a3db20ce83..9a999a4931 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; -using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager; namespace osu.Game.Tests.Visual.Components { @@ -100,7 +99,7 @@ namespace osu.Game.Tests.Visual.Components [Test] public void TestNonPresentTrack() { - TestPreviewTrack track = null; + TestPreviewTrackManager.TestPreviewTrack track = null; AddStep("get non-present track", () => { @@ -182,9 +181,9 @@ namespace osu.Game.Tests.Visual.Components AddAssert("track stopped", () => !track.IsRunning); } - private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); + private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null); - private TestPreviewTrack getOwnedTrack() + private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack() { var track = getTrack();