From 8d30c35104378baee846b75c338313291543c724 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 03:10:58 +0300 Subject: [PATCH 01/57] Implement sorting --- osu.Game/Overlays/SocialOverlay.cs | 65 +++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 4def249200..dc63b0ab9a 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; using osu.Framework.Threading; +using System; namespace osu.Game.Overlays { @@ -71,7 +72,7 @@ namespace osu.Game.Overlays Filter.Tabs.Current.ValueChanged += _ => queueUpdate(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdate(); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => onDropdownChanged(); currentQuery.BindTo(Filter.Search.Current); currentQuery.ValueChanged += query => @@ -175,11 +176,71 @@ namespace osu.Game.Overlays private void updateUsers(IEnumerable newUsers) { - Users = newUsers; + var sortDirection = Filter.DisplayStyleControl.Dropdown.Current.Value; + + IEnumerable sortedUsers = newUsers; + + switch (Filter.Tabs.Current.Value) + { + case SocialSortCriteria.Location: + switch (sortDirection) + { + case SortDirection.Ascending: + sortedUsers = newUsers.OrderBy(u => u.Country.FullName); + break; + + case SortDirection.Descending: + sortedUsers = newUsers.OrderByDescending(u => u.Country.FullName); + break; + } + break; + + case SocialSortCriteria.Name: + switch (sortDirection) + { + case SortDirection.Ascending: + sortedUsers = newUsers.OrderBy(u => u.Username); + break; + + case SortDirection.Descending: + sortedUsers = newUsers.OrderByDescending(u => u.Username); + break; + } + break; + + case SocialSortCriteria.Rank: + if (newUsers.FirstOrDefault().Statistics != null) + { + switch (sortDirection) + { + case SortDirection.Ascending: + sortedUsers = newUsers.OrderBy(u => u.Statistics?.Ranks.Global); + break; + + case SortDirection.Descending: + sortedUsers = newUsers.OrderByDescending(u => u.Statistics?.Ranks.Global); + break; + } + } + break; + } + + Users = sortedUsers; loading.Hide(); recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); } + private void onDropdownChanged() + { + if (Users == null) + { + queueUpdate(); + return; + } + + updateUsers(Users); + } + private void clearPanels() { if (panels != null) From cb81d1dd2fea0a174777044e7fdd8fa451f977f6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 03:53:16 +0300 Subject: [PATCH 02/57] Better use of loading animation --- osu.Game/Overlays/SocialOverlay.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index dc63b0ab9a..2202c473f6 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -103,7 +103,6 @@ namespace osu.Game.Overlays Users = null; clearPanels(); - loading.Hide(); getUsersRequest?.Cancel(); if (API?.IsLoggedIn != true) @@ -131,8 +130,13 @@ namespace osu.Game.Overlays { clearPanels(); + loading.Show(); + if (Users == null) + { + loading.Hide(); return; + } var newPanels = new FillFlowContainer { @@ -167,15 +171,15 @@ namespace osu.Game.Overlays LoadComponentAsync(newPanels, f => { - if (panels != null) - ScrollFlow.Remove(panels); - + loading.Hide(); ScrollFlow.Add(panels = newPanels); }); } private void updateUsers(IEnumerable newUsers) { + loading.Show(); + var sortDirection = Filter.DisplayStyleControl.Dropdown.Current.Value; IEnumerable sortedUsers = newUsers; @@ -226,7 +230,6 @@ namespace osu.Game.Overlays } Users = sortedUsers; - loading.Hide(); recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); } From 7cc6494482c7ba413012c5aa527da25b1c66eb41 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 03:54:49 +0300 Subject: [PATCH 03/57] Remove sorting by rank Since it isn't working for any case currently --- osu.Game/Overlays/SocialOverlay.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 2202c473f6..8353e2d683 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -211,22 +211,6 @@ namespace osu.Game.Overlays break; } break; - - case SocialSortCriteria.Rank: - if (newUsers.FirstOrDefault().Statistics != null) - { - switch (sortDirection) - { - case SortDirection.Ascending: - sortedUsers = newUsers.OrderBy(u => u.Statistics?.Ranks.Global); - break; - - case SortDirection.Descending: - sortedUsers = newUsers.OrderByDescending(u => u.Statistics?.Ranks.Global); - break; - } - } - break; } Users = sortedUsers; From 66b27875e06dedb156b76caacdedc213d6affb87 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 04:00:22 +0300 Subject: [PATCH 04/57] Fix possible null exception --- osu.Game/Overlays/SocialOverlay.cs | 49 ++++++++++++++++-------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 8353e2d683..22d1471229 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -184,33 +184,36 @@ namespace osu.Game.Overlays IEnumerable sortedUsers = newUsers; - switch (Filter.Tabs.Current.Value) + if (sortedUsers.Any()) { - case SocialSortCriteria.Location: - switch (sortDirection) - { - case SortDirection.Ascending: - sortedUsers = newUsers.OrderBy(u => u.Country.FullName); - break; + switch (Filter.Tabs.Current.Value) + { + case SocialSortCriteria.Location: + switch (sortDirection) + { + case SortDirection.Ascending: + sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName); + break; - case SortDirection.Descending: - sortedUsers = newUsers.OrderByDescending(u => u.Country.FullName); - break; - } - break; + case SortDirection.Descending: + sortedUsers = sortedUsers.OrderByDescending(u => u.Country.FullName); + break; + } + break; - case SocialSortCriteria.Name: - switch (sortDirection) - { - case SortDirection.Ascending: - sortedUsers = newUsers.OrderBy(u => u.Username); - break; + case SocialSortCriteria.Name: + switch (sortDirection) + { + case SortDirection.Ascending: + sortedUsers = sortedUsers.OrderBy(u => u.Username); + break; - case SortDirection.Descending: - sortedUsers = newUsers.OrderByDescending(u => u.Username); - break; - } - break; + case SortDirection.Descending: + sortedUsers = sortedUsers.OrderByDescending(u => u.Username); + break; + } + break; + } } Users = sortedUsers; From 385bc6f52988c3f8c9858b8525aeceb11e6b33fa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 04:11:22 +0300 Subject: [PATCH 05/57] Remove using and add blank lines --- osu.Game/Overlays/SocialOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 22d1471229..23185ed989 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -16,7 +16,6 @@ using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; using osu.Framework.Threading; -using System; namespace osu.Game.Overlays { @@ -199,6 +198,7 @@ namespace osu.Game.Overlays sortedUsers = sortedUsers.OrderByDescending(u => u.Country.FullName); break; } + break; case SocialSortCriteria.Name: @@ -212,6 +212,7 @@ namespace osu.Game.Overlays sortedUsers = sortedUsers.OrderByDescending(u => u.Username); break; } + break; } } From 9223a1ba8a4ff25c82e719374e37a3f3f3a90f81 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 05:03:47 +0300 Subject: [PATCH 06/57] Simplify sorting logic --- osu.Game/Overlays/SocialOverlay.cs | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 23185ed989..e2179361b1 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; using osu.Framework.Threading; +using System; namespace osu.Game.Overlays { @@ -188,31 +189,11 @@ namespace osu.Game.Overlays switch (Filter.Tabs.Current.Value) { case SocialSortCriteria.Location: - switch (sortDirection) - { - case SortDirection.Ascending: - sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName); - break; - - case SortDirection.Descending: - sortedUsers = sortedUsers.OrderByDescending(u => u.Country.FullName); - break; - } - + sortedUsers = sortBy(sortedUsers, u => u.Country.FullName, sortDirection); break; case SocialSortCriteria.Name: - switch (sortDirection) - { - case SortDirection.Ascending: - sortedUsers = sortedUsers.OrderBy(u => u.Username); - break; - - case SortDirection.Descending: - sortedUsers = sortedUsers.OrderByDescending(u => u.Username); - break; - } - + sortedUsers = sortBy(sortedUsers, u => u.Username, sortDirection); break; } } @@ -221,6 +202,9 @@ namespace osu.Game.Overlays recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); } + private IEnumerable sortBy(IEnumerable users, Func condition, SortDirection sortDirection) => + sortDirection == SortDirection.Ascending ? users.OrderBy(condition) : users.OrderByDescending(condition); + private void onDropdownChanged() { if (Users == null) From 11df8c5576478f920f3c09e3b8c884623a0cb836 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Aug 2019 01:02:26 +0300 Subject: [PATCH 07/57] Remove a lot of loading animation calls --- osu.Game/Overlays/SocialOverlay.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index e2179361b1..a467ae6dfa 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -122,16 +122,12 @@ namespace osu.Game.Overlays API.Queue(getUsersRequest = userRequest); break; } - - loading.Show(); } private void recreatePanels(PanelDisplayStyle displayStyle) { clearPanels(); - loading.Show(); - if (Users == null) { loading.Hide(); @@ -178,8 +174,6 @@ namespace osu.Game.Overlays private void updateUsers(IEnumerable newUsers) { - loading.Show(); - var sortDirection = Filter.DisplayStyleControl.Dropdown.Current.Value; IEnumerable sortedUsers = newUsers; @@ -218,6 +212,8 @@ namespace osu.Game.Overlays private void clearPanels() { + loading.Show(); + if (panels != null) { panels.Expire(); From 9b1e8cf48b701aad40f4a3b34a4d14205bc5bb77 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Aug 2019 01:03:51 +0300 Subject: [PATCH 08/57] Use CancelDelayedTasks instead of private ScheduledDelegate --- osu.Game/Overlays/SocialOverlay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index a467ae6dfa..5ed6a9a703 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -77,12 +77,12 @@ namespace osu.Game.Overlays currentQuery.BindTo(Filter.Search.Current); currentQuery.ValueChanged += query => { - queryChangedDebounce?.Cancel(); + Scheduler.CancelDelayedTasks(); if (string.IsNullOrEmpty(query.NewValue)) queueUpdate(); else - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); + Scheduler.AddDelayed(updateSearch, 500); }; } @@ -90,13 +90,11 @@ namespace osu.Game.Overlays private readonly Bindable currentQuery = new Bindable(); - private ScheduledDelegate queryChangedDebounce; - private void queueUpdate() => Scheduler.AddOnce(updateSearch); private void updateSearch() { - queryChangedDebounce?.Cancel(); + Scheduler.CancelDelayedTasks(); if (!IsLoaded) return; From 2d7024ffd92656a9d3646abc0e87111709e6bfe4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Aug 2019 01:14:35 +0300 Subject: [PATCH 09/57] Use CancellationTokenSource to avoid unwanted panels creation --- osu.Game/Overlays/SocialOverlay.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 5ed6a9a703..ee3adf65b9 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Overlays.Social; using osu.Game.Users; using osu.Framework.Threading; using System; +using System.Threading; namespace osu.Game.Overlays { @@ -92,6 +93,8 @@ namespace osu.Game.Overlays private void queueUpdate() => Scheduler.AddOnce(updateSearch); + private CancellationTokenSource loadCancellation; + private void updateSearch() { Scheduler.CancelDelayedTasks(); @@ -132,6 +135,8 @@ namespace osu.Game.Overlays return; } + loadCancellation = new CancellationTokenSource(); + var newPanels = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -167,7 +172,7 @@ namespace osu.Game.Overlays { loading.Hide(); ScrollFlow.Add(panels = newPanels); - }); + }, loadCancellation.Token); } private void updateUsers(IEnumerable newUsers) @@ -212,6 +217,8 @@ namespace osu.Game.Overlays { loading.Show(); + loadCancellation?.Cancel(); + if (panels != null) { panels.Expire(); From 6aef05f5d877dfd29b2035cdcadb5f36aa706e3a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Aug 2019 01:16:55 +0300 Subject: [PATCH 10/57] Remove useless function --- osu.Game/Overlays/SocialOverlay.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index ee3adf65b9..97e1c2b5a4 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -73,7 +73,7 @@ namespace osu.Game.Overlays Filter.Tabs.Current.ValueChanged += _ => queueUpdate(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => onDropdownChanged(); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => updateUsers(Users); currentQuery.BindTo(Filter.Search.Current); currentQuery.ValueChanged += query => @@ -202,17 +202,6 @@ namespace osu.Game.Overlays private IEnumerable sortBy(IEnumerable users, Func condition, SortDirection sortDirection) => sortDirection == SortDirection.Ascending ? users.OrderBy(condition) : users.OrderByDescending(condition); - private void onDropdownChanged() - { - if (Users == null) - { - queueUpdate(); - return; - } - - updateUsers(Users); - } - private void clearPanels() { loading.Show(); From b6b4173a8481987f3afc73f90d3cc712e099f000 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Aug 2019 01:27:16 +0300 Subject: [PATCH 11/57] Remove unused using --- osu.Game/Overlays/SocialOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 97e1c2b5a4..bf306f9569 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; -using osu.Framework.Threading; using System; using System.Threading; From 3227dc87fbac1c47fb505c4e66a66ac80a935678 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 6 Sep 2019 22:56:46 +0300 Subject: [PATCH 12/57] Don't use CancelDelayedTasks to avoid cancelling unwanted tasks --- osu.Game/Overlays/SocialOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index bf306f9569..1912a2c3e0 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Overlays.Social; using osu.Game.Users; using System; using System.Threading; +using osu.Framework.Threading; namespace osu.Game.Overlays { @@ -77,12 +78,12 @@ namespace osu.Game.Overlays currentQuery.BindTo(Filter.Search.Current); currentQuery.ValueChanged += query => { - Scheduler.CancelDelayedTasks(); + queryChangedDebounce?.Cancel(); if (string.IsNullOrEmpty(query.NewValue)) queueUpdate(); else - Scheduler.AddDelayed(updateSearch, 500); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); }; } @@ -90,13 +91,15 @@ namespace osu.Game.Overlays private readonly Bindable currentQuery = new Bindable(); + private ScheduledDelegate queryChangedDebounce; + private void queueUpdate() => Scheduler.AddOnce(updateSearch); private CancellationTokenSource loadCancellation; private void updateSearch() { - Scheduler.CancelDelayedTasks(); + queryChangedDebounce?.Cancel(); if (!IsLoaded) return; @@ -169,6 +172,9 @@ namespace osu.Game.Overlays LoadComponentAsync(newPanels, f => { + if (panels != null) + ScrollFlow.Remove(panels); + loading.Hide(); ScrollFlow.Add(panels = newPanels); }, loadCancellation.Token); From 44412f9ddb0407318c7b9603484f806ef717266e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 03:35:56 +0300 Subject: [PATCH 13/57] Fix local sorting calls an online request --- osu.Game/Overlays/SocialOverlay.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 1912a2c3e0..7fd9ef153e 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays Header.Tabs.Current.ValueChanged += _ => queueUpdate(); - Filter.Tabs.Current.ValueChanged += _ => queueUpdate(); + Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => updateUsers(Users); @@ -180,6 +180,17 @@ namespace osu.Game.Overlays }, loadCancellation.Token); } + private void onFilterUpdate() + { + if (Users == null || Filter.Tabs.Current.Value == SocialSortCriteria.Rank) + { + queueUpdate(); + return; + } + + updateUsers(Users); + } + private void updateUsers(IEnumerable newUsers) { var sortDirection = Filter.DisplayStyleControl.Dropdown.Current.Value; From 647433a8d1c21187d86f656571d0abe8b5a0423f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 19 Sep 2019 04:09:14 +0300 Subject: [PATCH 14/57] Don't trigger request if there are no avaliable users --- osu.Game/Overlays/SocialOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 7fd9ef153e..a77172c90c 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -182,7 +182,7 @@ namespace osu.Game.Overlays private void onFilterUpdate() { - if (Users == null || Filter.Tabs.Current.Value == SocialSortCriteria.Rank) + if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank) { queueUpdate(); return; From 944835da234c0c300af3a38a4ae45052237052be Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 28 Nov 2019 12:01:40 +0700 Subject: [PATCH 15/57] Add multiplier score text on mods footer button --- osu.Game/Screens/Select/FooterButtonMods.cs | 44 +++++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 29b1364944..8dc0a2a132 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -7,11 +7,14 @@ using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Screens.Select @@ -24,23 +27,40 @@ namespace osu.Game.Screens.Select set => modDisplay.Current = value; } + protected readonly OsuSpriteText MultiplierText; private readonly FooterModDisplay modDisplay; + private Color4 lowMultiplierColour; + private Color4 highMultiplierColour; public FooterButtonMods() { - Add(new Container + Add(new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, Shear = -SHEAR, - Child = modDisplay = new FooterModDisplay + Children = new Drawable[] { - DisplayUnrankedText = false, - Scale = new Vector2(0.8f) + modDisplay = new FooterModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DisplayUnrankedText = false, + Scale = new Vector2(0.8f) + }, + MultiplierText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + } }, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 70 } }); + + Current.ValueChanged += _ => updateMultiplierText(); } [BackgroundDependencyLoader] @@ -48,10 +68,26 @@ namespace osu.Game.Screens.Select { SelectedColour = colours.Yellow; DeselectedColour = SelectedColour.Opacity(0.5f); + lowMultiplierColour = colours.Red; + highMultiplierColour = colours.Green; Text = @"mods"; Hotkey = Key.F1; } + private void updateMultiplierText() + { + var multiplier = Current.Value.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); + + MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + + if (multiplier > 1.0) + MultiplierText.FadeColour(highMultiplierColour, 200); + else if (multiplier < 1.0) + MultiplierText.FadeColour(lowMultiplierColour, 200); + else + MultiplierText.FadeColour(Color4.White, 200); + } + private class FooterModDisplay : ModDisplay { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; From 6149482b5d81d67778ab66043926dec885802374 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 28 Nov 2019 12:03:11 +0700 Subject: [PATCH 16/57] Add test --- .../UserInterface/TestCaseFooterButtonMods.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs new file mode 100644 index 0000000000..0c437800bc --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs @@ -0,0 +1,70 @@ +// 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 NUnit.Framework; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestCaseFooterButtonMods : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(FooterButtonMods) + }; + + private readonly TestFooterButtonMods footerButtonMods; + + public TestCaseFooterButtonMods() + { + Add(footerButtonMods = new TestFooterButtonMods()); + } + + [Test] + public void TestIncrementMultiplier() + { + AddStep(@"Add Hidden", () => changeMods(new Mod[] { new OsuModHidden() })); + AddAssert(@"Check Hidden multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.06x")); + AddStep(@"Add HardRock", () => changeMods(new Mod[] { new OsuModHidden() })); + AddAssert(@"Check HardRock multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.06x")); + AddStep(@"Add DoubleTime", () => changeMods(new Mod[] { new OsuModDoubleTime() })); + AddAssert(@"Check DoubleTime multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.12x")); + AddStep(@"Add multiple Mods", () => changeMods(new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHidden() })); + AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.26x")); + } + + [Test] + public void TestDecrementMultiplier() + { + AddStep(@"Add Easy", () => changeMods(new Mod[] { new OsuModEasy() })); + AddAssert(@"Check Easy multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"0.50x")); + AddStep(@"Add NoFail", () => changeMods(new Mod[] { new OsuModNoFail() })); + AddAssert(@"Check NoFail multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"0.50x")); + AddStep(@"Add Multiple Mods", () => changeMods(new Mod[] { new OsuModEasy(), new OsuModNoFail() })); + AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"0.25x")); + } + + [Test] + public void TestClearMultiplier() + { + AddStep(@"Add mods", () => changeMods(new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() })); + AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); + AddAssert(@"Check empty multiplier", () => footerButtonMods.MultiplierText.Text.Equals(string.Empty)); + } + + private void changeMods(IReadOnlyList mods) + { + footerButtonMods.Current.Value = mods; + } + + private class TestFooterButtonMods : FooterButtonMods + { + public new OsuSpriteText MultiplierText => base.MultiplierText; + } + } +} From ada8dabf7eac666ad118790110b6ee383bc93d60 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 28 Nov 2019 18:48:43 +0700 Subject: [PATCH 17/57] Add right margin on score multiplier text --- osu.Game/Screens/Select/FooterButtonMods.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 8dc0a2a132..fbeda8f18e 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -54,6 +54,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold), + Margin = new MarginPadding { Right = 10 } } }, AutoSizeAxes = Axes.Both, From af5c5a3000beb54796f9f356d4cd59b804a500a4 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 28 Nov 2019 20:06:15 +0700 Subject: [PATCH 18/57] Apply reviews Also rename the class name to match with the others --- ...onMods.cs => TestSceneFooterButtonMods.cs} | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestCaseFooterButtonMods.cs => TestSceneFooterButtonMods.cs} (81%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs similarity index 81% rename from osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index 0c437800bc..e8d3475a11 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestCaseFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -11,7 +11,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseFooterButtonMods : OsuTestScene + public class TestSceneFooterButtonMods : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly TestFooterButtonMods footerButtonMods; - public TestCaseFooterButtonMods() + public TestSceneFooterButtonMods() { Add(footerButtonMods = new TestFooterButtonMods()); } @@ -29,24 +29,24 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestIncrementMultiplier() { AddStep(@"Add Hidden", () => changeMods(new Mod[] { new OsuModHidden() })); - AddAssert(@"Check Hidden multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.06x")); + AddAssert(@"Check Hidden multiplier", () => footerButtonMods.MultiplierText.Text == @"1.06x"); AddStep(@"Add HardRock", () => changeMods(new Mod[] { new OsuModHidden() })); - AddAssert(@"Check HardRock multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.06x")); + AddAssert(@"Check HardRock multiplier", () => footerButtonMods.MultiplierText.Text == @"1.06x"); AddStep(@"Add DoubleTime", () => changeMods(new Mod[] { new OsuModDoubleTime() })); - AddAssert(@"Check DoubleTime multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.12x")); + AddAssert(@"Check DoubleTime multiplier", () => footerButtonMods.MultiplierText.Text == @"1.12x"); AddStep(@"Add multiple Mods", () => changeMods(new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHidden() })); - AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"1.26x")); + AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text == @"1.26x"); } [Test] public void TestDecrementMultiplier() { AddStep(@"Add Easy", () => changeMods(new Mod[] { new OsuModEasy() })); - AddAssert(@"Check Easy multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"0.50x")); + AddAssert(@"Check Easy multiplier", () => footerButtonMods.MultiplierText.Text == @"0.50x"); AddStep(@"Add NoFail", () => changeMods(new Mod[] { new OsuModNoFail() })); - AddAssert(@"Check NoFail multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"0.50x")); + AddAssert(@"Check NoFail multiplier", () => footerButtonMods.MultiplierText.Text == @"0.50x"); AddStep(@"Add Multiple Mods", () => changeMods(new Mod[] { new OsuModEasy(), new OsuModNoFail() })); - AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text.Equals(@"0.25x")); + AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text == @"0.25x"); } [Test] @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep(@"Add mods", () => changeMods(new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() })); AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); - AddAssert(@"Check empty multiplier", () => footerButtonMods.MultiplierText.Text.Equals(string.Empty)); + AddAssert(@"Check empty multiplier", () => string.IsNullOrEmpty(footerButtonMods.MultiplierText.Text)); } private void changeMods(IReadOnlyList mods) From 0e658790c141bd28eaa7a62d4b41b2f6b0231e5c Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Sun, 15 Dec 2019 22:42:44 +0700 Subject: [PATCH 19/57] Add function to assert multiplier value instead of hardcoded string --- .../TestSceneFooterButtonMods.cs | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index e8d3475a11..6eb621ca3b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; @@ -28,33 +29,46 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestIncrementMultiplier() { - AddStep(@"Add Hidden", () => changeMods(new Mod[] { new OsuModHidden() })); - AddAssert(@"Check Hidden multiplier", () => footerButtonMods.MultiplierText.Text == @"1.06x"); - AddStep(@"Add HardRock", () => changeMods(new Mod[] { new OsuModHidden() })); - AddAssert(@"Check HardRock multiplier", () => footerButtonMods.MultiplierText.Text == @"1.06x"); - AddStep(@"Add DoubleTime", () => changeMods(new Mod[] { new OsuModDoubleTime() })); - AddAssert(@"Check DoubleTime multiplier", () => footerButtonMods.MultiplierText.Text == @"1.12x"); - AddStep(@"Add multiple Mods", () => changeMods(new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHidden() })); - AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text == @"1.26x"); + var hiddenMod = new Mod[] { new OsuModHidden() }; + AddStep(@"Add Hidden", () => changeMods(hiddenMod)); + AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod)); + + var hardRockMod = new Mod[] { new OsuModHardRock() }; + AddStep(@"Add HardRock", () => changeMods(hardRockMod)); + AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod)); + + var doubleTimeMod = new Mod[] { new OsuModDoubleTime() }; + AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod)); + AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod)); + + var mutlipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() }; + AddStep(@"Add multiple Mods", () => changeMods(mutlipleIncrementMods)); + AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(mutlipleIncrementMods)); } [Test] public void TestDecrementMultiplier() { - AddStep(@"Add Easy", () => changeMods(new Mod[] { new OsuModEasy() })); - AddAssert(@"Check Easy multiplier", () => footerButtonMods.MultiplierText.Text == @"0.50x"); - AddStep(@"Add NoFail", () => changeMods(new Mod[] { new OsuModNoFail() })); - AddAssert(@"Check NoFail multiplier", () => footerButtonMods.MultiplierText.Text == @"0.50x"); - AddStep(@"Add Multiple Mods", () => changeMods(new Mod[] { new OsuModEasy(), new OsuModNoFail() })); - AddAssert(@"Check multiple mod multiplier", () => footerButtonMods.MultiplierText.Text == @"0.25x"); + var easyMod = new Mod[] { new OsuModEasy() }; + AddStep(@"Add Easy", () => changeMods(easyMod)); + AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod)); + + var noFailMod = new Mod[] { new OsuModNoFail() }; + AddStep(@"Add NoFail", () => changeMods(noFailMod)); + AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod)); + + var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() }; + AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods)); + AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods)); } [Test] public void TestClearMultiplier() { - AddStep(@"Add mods", () => changeMods(new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() })); + var multipleMods = new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() }; + AddStep(@"Add mods", () => changeMods(multipleMods)); AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); - AddAssert(@"Check empty multiplier", () => string.IsNullOrEmpty(footerButtonMods.MultiplierText.Text)); + AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty())); } private void changeMods(IReadOnlyList mods) @@ -62,6 +76,14 @@ namespace osu.Game.Tests.Visual.UserInterface footerButtonMods.Current.Value = mods; } + private bool assertModsMultiplier(IEnumerable mods) + { + var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); + var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; + + return expectedValue == footerButtonMods.MultiplierText.Text; + } + private class TestFooterButtonMods : FooterButtonMods { public new OsuSpriteText MultiplierText => base.MultiplierText; From bcc19e29f2c2d41621025c9708cf976fce370a55 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Dec 2019 02:56:29 +0900 Subject: [PATCH 20/57] Fix editor crashing after re-ordering objects --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 36 ++++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9485189433..b7508a82f3 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -14,13 +15,42 @@ namespace osu.Game.Rulesets.UI public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); + private readonly Dictionary bindable, double timeAtAdd)> startTimeMap = new Dictionary, double)>(); + public HitObjectContainer() { RelativeSizeAxes = Axes.Both; } - public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); - public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); + public virtual void Add(DrawableHitObject hitObject) + { + // Added first for the comparer to remain ordered during AddInternal + startTimeMap[hitObject] = (hitObject.HitObject.StartTimeBindable.GetBoundCopy(), hitObject.HitObject.StartTime); + startTimeMap[hitObject].bindable.BindValueChanged(_ => onStartTimeChanged(hitObject)); + + AddInternal(hitObject); + } + + public virtual bool Remove(DrawableHitObject hitObject) + { + bool result = RemoveInternal(hitObject); + + // Removed last for the comparer to remain ordered during RemoveInternal + startTimeMap[hitObject].bindable.UnbindAll(); + startTimeMap.Remove(hitObject); + + return result; + } + + private void onStartTimeChanged(DrawableHitObject hitObject) + { + if (!RemoveInternal(hitObject)) + return; + + // Update the stored time, preserving the existing bindable + startTimeMap[hitObject] = (startTimeMap[hitObject].bindable, hitObject.HitObject.StartTime); + AddInternal(hitObject); + } protected override int Compare(Drawable x, Drawable y) { @@ -28,7 +58,7 @@ namespace osu.Game.Rulesets.UI return base.Compare(x, y); // Put earlier hitobjects towards the end of the list, so they handle input first - int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); + int i = startTimeMap[yObj].timeAtAdd.CompareTo(startTimeMap[xObj].timeAtAdd); return i == 0 ? CompareReverseChildID(x, y) : i; } From df8f8ffd0d3fc699b9a0dee2e3aa698231227afe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Dec 2019 12:03:15 +0900 Subject: [PATCH 21/57] Fix potential exception during removal --- osu.Game/Rulesets/UI/HitObjectContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index b7508a82f3..1e33b15184 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -33,13 +33,14 @@ namespace osu.Game.Rulesets.UI public virtual bool Remove(DrawableHitObject hitObject) { - bool result = RemoveInternal(hitObject); + if (!RemoveInternal(hitObject)) + return false; // Removed last for the comparer to remain ordered during RemoveInternal startTimeMap[hitObject].bindable.UnbindAll(); startTimeMap.Remove(hitObject); - return result; + return true; } private void onStartTimeChanged(DrawableHitObject hitObject) From 5664ce3109d9cb40ede788e76b05cb5b503a237a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Dec 2019 18:51:12 +0900 Subject: [PATCH 22/57] Add hitobject container regression test --- .../Gameplay/TestSceneHitObjectContainer.cs | 75 +++++++++++++++++++ osu.Game/Rulesets/UI/HitObjectContainer.cs | 2 + 2 files changed, 77 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs new file mode 100644 index 0000000000..f2bfccb6de --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs @@ -0,0 +1,75 @@ +// 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 NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneHitObjectContainer : OsuTestScene + { + private HitObjectContainer container; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = container = new HitObjectContainer(); + }); + + [Test] + public void TestLateHitObjectIsAddedEarlierInList() + { + DrawableHitObject hitObject = null; + + AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 }))); + + AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 }))); + + AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 0); + } + + [Test] + public void TestEarlyHitObjectIsAddedLaterInList() + { + DrawableHitObject hitObject = null; + + AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 }))); + + AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject()))); + + AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 1); + } + + [Test] + public void TestHitObjectsResortedAfterStartTimeChange() + { + DrawableHitObject firstObject = null; + DrawableHitObject secondObject = null; + + AddStep("setup", () => + { + container.Add(firstObject = new TestDrawableHitObject(new HitObject())); + container.Add(secondObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 })); + }); + + AddStep("move first object after second", () => firstObject.HitObject.StartTime = 2000); + + AddAssert("first object index is 1", () => container.IndexOf(firstObject) == 0); + AddAssert("second object index is 0", () => container.IndexOf(secondObject) == 1); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public TestDrawableHitObject([NotNull] HitObject hitObject) + : base(hitObject) + { + } + } + } +} diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 1e33b15184..dea981c3ad 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.UI return true; } + public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); + private void onStartTimeChanged(DrawableHitObject hitObject) { if (!RemoveInternal(hitObject)) From cb5a35f8f9f23c5b53d456611d3cf6c65f16f86f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Dec 2019 21:08:38 +0900 Subject: [PATCH 23/57] Fix incorrect bindable valuechanged usage --- osu.Game/Screens/Select/FooterButtonMods.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index fbeda8f18e..dde6e90ee3 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -60,8 +60,6 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 70 } }); - - Current.ValueChanged += _ => updateMultiplierText(); } [BackgroundDependencyLoader] @@ -75,6 +73,13 @@ namespace osu.Game.Screens.Select Hotkey = Key.F1; } + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateMultiplierText(), true); + } + private void updateMultiplierText() { var multiplier = Current.Value.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); From 2905a4418d5960f7edadaefedfb81545d4b7b943 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Dec 2019 21:30:57 +0900 Subject: [PATCH 24/57] Fix potential nullref --- osu.Game/Screens/Select/FooterButtonMods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index dde6e90ee3..8419ee0c2a 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Select private void updateMultiplierText() { - var multiplier = Current.Value.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); + double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; From 9ac15ef3a8141d0e583009aa7a604e112f6c1d06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Dec 2019 22:56:00 +0900 Subject: [PATCH 25/57] Don't log discord connection failures --- osu.Desktop/DiscordRichPresence.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index b53ca6161b..8818cef8eb 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -43,8 +43,7 @@ namespace osu.Desktop }; client.OnReady += onReady; - client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error); - client.OnConnectionFailed += (_, e) => Logger.Log($"An connection occurred with Discord RPC Client: {e.Type}", LoggingTarget.Network, LogLevel.Error); + client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u => { From 8d6987c8703d27b4459fb3f30f586a1b1b9ae8a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 18 Dec 2019 23:59:48 +0900 Subject: [PATCH 26/57] Refactor storyboard timeline to reduce GC / fix crashes --- osu.Game/Storyboards/CommandTimeline.cs | 29 +++++---- osu.Game/Storyboards/CommandTimelineGroup.cs | 65 +++++++++++++++----- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index bcf642b4ea..c71806352d 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.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 osu.Framework.Caching; using osu.Framework.Graphics; using System; using System.Collections.Generic; @@ -12,27 +11,35 @@ namespace osu.Game.Storyboards public class CommandTimeline : ICommandTimeline { private readonly List commands = new List(); + public IEnumerable Commands => commands.OrderBy(c => c.StartTime); + public bool HasCommands => commands.Count > 0; - private readonly Cached startTimeBacking = new Cached(); - public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue; + public double StartTime { get; private set; } = double.MaxValue; + public double EndTime { get; private set; } = double.MinValue; - private readonly Cached endTimeBacking = new Cached(); - public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue; - - public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default; - public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default; + public T StartValue { get; private set; } + public T EndValue { get; private set; } public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue) { if (endTime < startTime) return; - commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, }); + commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue }); - startTimeBacking.Invalidate(); - endTimeBacking.Invalidate(); + if (startTime < StartTime) + { + StartValue = startValue; + StartTime = startTime; + } + + if (endTime > EndTime) + { + EndValue = endValue; + EndTime = endTime; + } } public override string ToString() diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 7b6e667d4f..6ce3b617e9 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.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; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -25,28 +26,52 @@ namespace osu.Game.Storyboards public CommandTimeline FlipH = new CommandTimeline(); public CommandTimeline FlipV = new CommandTimeline(); + private readonly ICommandTimeline[] timelines; + + public CommandTimelineGroup() + { + timelines = new ICommandTimeline[] + { + X, + Y, + Scale, + VectorScale, + Rotation, + Colour, + Alpha, + BlendingParameters, + FlipH, + FlipV + }; + } + [JsonIgnore] - public IEnumerable Timelines + public double CommandsStartTime { get { - yield return X; - yield return Y; - yield return Scale; - yield return Rotation; - yield return Colour; - yield return Alpha; - yield return BlendingParameters; - yield return FlipH; - yield return FlipV; + double min = double.MaxValue; + + for (int i = 0; i < timelines.Length; i++) + min = Math.Min(min, timelines[i].StartTime); + + return min; } } [JsonIgnore] - public double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime); + public double CommandsEndTime + { + get + { + double max = double.MinValue; - [JsonIgnore] - public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime); + for (int i = 0; i < timelines.Length; i++) + max = Math.Max(max, timelines[i].EndTime); + + return max; + } + } [JsonIgnore] public double CommandsDuration => CommandsEndTime - CommandsStartTime; @@ -61,7 +86,19 @@ namespace osu.Game.Storyboards public double Duration => EndTime - StartTime; [JsonIgnore] - public bool HasCommands => Timelines.Any(t => t.HasCommands); + public bool HasCommands + { + get + { + for (int i = 0; i < timelines.Length; i++) + { + if (timelines[i].HasCommands) + return true; + } + + return false; + } + } public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { From f38853d2291835e6cbda7cc12a2dcf1e4f9eae5f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Dec 2019 00:52:50 +0900 Subject: [PATCH 27/57] Improve performance of storyboard loading --- osu.Game/Storyboards/StoryboardSprite.cs | 83 ++++++++++++++++++------ 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index abf9f58804..22e1929419 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -7,6 +7,7 @@ using osu.Game.Storyboards.Drawables; using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace osu.Game.Storyboards { @@ -63,50 +64,56 @@ namespace osu.Game.Storyboards public void ApplyTransforms(Drawable drawable, IEnumerable> triggeredGroups = null) { - applyCommands(drawable, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); - applyCommands(drawable, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), + // For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity. + // To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list + // The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially. + + List generated = new List(); + + generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing)); + generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing)); + generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing)); + generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing)); + generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing)); + generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); + generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); if (drawable is IVectorScalable vectorScalable) { - applyCommands(drawable, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value, + generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value, (d, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing)); } if (drawable is IFlippable flippable) { - applyCommands(drawable, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), + generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), false); - applyCommands(drawable, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), + generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), false); } + + foreach (var command in generated.OrderBy(g => g.StartTime)) + command.ApplyTo(drawable); } - private void applyCommands(Drawable drawable, IEnumerable.TypedCommand> commands, DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, - bool alwaysInitialize = true) - where T : struct + private void generateCommands(List resultList, IEnumerable.TypedCommand> commands, + DrawablePropertyInitializer initializeProperty, DrawableTransformer transform, bool alwaysInitialize = true) { - var initialized = false; + bool initialized = false; - foreach (var command in commands.OrderBy(l => l)) + foreach (var command in commands) { + DrawablePropertyInitializer initFunc = null; + if (!initialized) { if (alwaysInitialize || command.StartTime == command.EndTime) - initializeProperty.Invoke(drawable, command.StartValue); + initFunc = initializeProperty; initialized = true; } - using (drawable.BeginAbsoluteSequence(command.StartTime)) - { - transform(drawable, command.StartValue, 0, Easing.None); - transform(drawable, command.EndValue, command.Duration, command.Easing); - } + resultList.Add(new GeneratedCommand(command, initFunc, transform)); } } @@ -127,5 +134,39 @@ namespace osu.Game.Storyboards public override string ToString() => $"{Path}, {Origin}, {InitialPosition}"; + + private interface IGeneratedCommand + { + double StartTime { get; } + + void ApplyTo(Drawable drawable); + } + + private readonly struct GeneratedCommand : IGeneratedCommand + { + public double StartTime => command.StartTime; + + private readonly DrawablePropertyInitializer initializeProperty; + private readonly DrawableTransformer transform; + private readonly CommandTimeline.TypedCommand command; + + public GeneratedCommand([NotNull] CommandTimeline.TypedCommand command, [CanBeNull] DrawablePropertyInitializer initializeProperty, [NotNull] DrawableTransformer transform) + { + this.command = command; + this.initializeProperty = initializeProperty; + this.transform = transform; + } + + public void ApplyTo(Drawable drawable) + { + initializeProperty?.Invoke(drawable, command.StartValue); + + using (drawable.BeginAbsoluteSequence(command.StartTime)) + { + transform(drawable, command.StartValue, 0, Easing.None); + transform(drawable, command.EndValue, command.Duration, command.Easing); + } + } + } } } From 007e2e80c1cd193e9e7a1ded3df6191571bed8a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Dec 2019 02:02:57 +0900 Subject: [PATCH 28/57] Refactor to fix sorting issues --- osu.Game/Overlays/SocialOverlay.cs | 70 +++++++++++++----------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 5b3acf8e46..01dd1ee635 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.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; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; @@ -15,7 +16,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; -using System; using System.Threading; using osu.Framework.Threading; @@ -33,17 +33,18 @@ namespace osu.Game.Overlays protected override SearchableListHeader CreateHeader() => new Header(); protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); - private IEnumerable users; + private User[] users = Array.Empty(); - public IEnumerable Users + public User[] Users { get => users; set { - if (ReferenceEquals(users, value)) + if (users == value) return; - users = value?.ToList(); + users = value ?? Array.Empty(); + recreatePanels(); } } @@ -72,8 +73,8 @@ namespace osu.Game.Overlays Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate(); - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => updateUsers(Users); + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels(); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => recreatePanels(); currentQuery.BindTo(Filter.Search.Current); currentQuery.ValueChanged += query => @@ -115,19 +116,19 @@ namespace osu.Game.Overlays { case SocialTab.Friends: var friendRequest = new GetFriendsRequest(); // TODO filter arguments? - friendRequest.Success += updateUsers; + friendRequest.Success += users => Users = users.ToArray(); API.Queue(getUsersRequest = friendRequest); break; default: var userRequest = new GetUsersRequest(); // TODO filter arguments! - userRequest.Success += res => updateUsers(res.Users.Select(r => r.User)); + userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray(); API.Queue(getUsersRequest = userRequest); break; } } - private void recreatePanels(PanelDisplayStyle displayStyle) + private void recreatePanels() { clearPanels(); @@ -139,17 +140,33 @@ namespace osu.Game.Overlays loadCancellation = new CancellationTokenSource(); + IEnumerable sortedUsers = Users; + + switch (Filter.Tabs.Current.Value) + { + case SocialSortCriteria.Location: + sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName); + break; + + case SocialSortCriteria.Name: + sortedUsers = sortedUsers.OrderBy(u => u.Username); + break; + } + + if (Filter.DisplayStyleControl.Dropdown.Current.Value == SortDirection.Descending) + sortedUsers = sortedUsers.Reverse(); + var newPanels = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(10f), Margin = new MarginPadding { Top = 10 }, - ChildrenEnumerable = Users.Select(u => + ChildrenEnumerable = sortedUsers.Select(u => { SocialPanel panel; - switch (displayStyle) + switch (Filter.DisplayStyleControl.DisplayStyle.Value) { case PanelDisplayStyle.Grid: panel = new SocialGridPanel(u) @@ -188,36 +205,9 @@ namespace osu.Game.Overlays return; } - updateUsers(Users); + recreatePanels(); } - private void updateUsers(IEnumerable newUsers) - { - var sortDirection = Filter.DisplayStyleControl.Dropdown.Current.Value; - - IEnumerable sortedUsers = newUsers; - - if (sortedUsers.Any()) - { - switch (Filter.Tabs.Current.Value) - { - case SocialSortCriteria.Location: - sortedUsers = sortBy(sortedUsers, u => u.Country.FullName, sortDirection); - break; - - case SocialSortCriteria.Name: - sortedUsers = sortBy(sortedUsers, u => u.Username, sortDirection); - break; - } - } - - Users = sortedUsers; - recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); - } - - private IEnumerable sortBy(IEnumerable users, Func condition, SortDirection sortDirection) => - sortDirection == SortDirection.Ascending ? users.OrderBy(condition) : users.OrderByDescending(condition); - private void clearPanels() { loading.Show(); From dd68106d909d9deffb321a39536fbb35454bf05d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 19 Dec 2019 02:21:33 +0900 Subject: [PATCH 29/57] Recreate panels only while loading/loaded --- osu.Game/Overlays/SocialOverlay.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 01dd1ee635..7cc3b6e3ce 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -17,6 +17,7 @@ using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; using System.Threading; +using osu.Framework.Allocation; using osu.Framework.Threading; namespace osu.Game.Overlays @@ -44,7 +45,9 @@ namespace osu.Game.Overlays return; users = value ?? Array.Empty(); - recreatePanels(); + + if (LoadState >= LoadState.Ready) + recreatePanels(); } } @@ -70,7 +73,6 @@ namespace osu.Game.Overlays }; Header.Tabs.Current.ValueChanged += _ => queueUpdate(); - Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels(); @@ -88,6 +90,12 @@ namespace osu.Game.Overlays }; } + [BackgroundDependencyLoader] + private void load() + { + recreatePanels(); + } + private APIRequest getUsersRequest; private readonly Bindable currentQuery = new Bindable(); From a46602f705c2686ad45a58ea2997bb715bb8265e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2019 11:26:22 +0900 Subject: [PATCH 30/57] Move cancellation token construction closer to usage --- osu.Game/Overlays/SocialOverlay.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 7cc3b6e3ce..0c99962def 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -146,8 +146,6 @@ namespace osu.Game.Overlays return; } - loadCancellation = new CancellationTokenSource(); - IEnumerable sortedUsers = Users; switch (Filter.Tabs.Current.Value) @@ -202,7 +200,7 @@ namespace osu.Game.Overlays loading.Hide(); ScrollFlow.Add(panels = newPanels); - }, loadCancellation.Token); + }, (loadCancellation = new CancellationTokenSource()).Token); } private void onFilterUpdate() From b1533ae2a9d32eee264464d1d12ee374c59cd1bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2019 14:58:56 +0900 Subject: [PATCH 31/57] Fix score serialisation failing for unknown mod properties --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c7609e8a0b..c37bab9086 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -89,7 +89,7 @@ namespace osu.Game.Scoring if (mods == null) return null; - return modsJson = JsonConvert.SerializeObject(mods); + return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym })); } set { From 81e842f6b4bab33100c8f79eae63c6ab41155730 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2019 19:48:48 +0900 Subject: [PATCH 32/57] Fix waveform test beatmap accessing zip archive across multiple threads --- .../Editor/TestSceneEditorComposeTimeline.cs | 14 +++----- osu.Game.Tests/WaveformTestBeatmap.cs | 32 +++++++++++-------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index e618256c03..ed6bc5fe0c 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -68,8 +68,11 @@ namespace osu.Game.Tests.Visual.Editor { private readonly Drawable marker; - private readonly IBindable beatmap = new Bindable(); - private IAdjustableClock adjustableClock; + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private IAdjustableClock adjustableClock { get; set; } public AudioVisualiser() { @@ -91,13 +94,6 @@ namespace osu.Game.Tests.Visual.Editor }; } - [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock, IBindable beatmap) - { - this.adjustableClock = adjustableClock; - this.beatmap.BindTo(beatmap); - } - protected override void Update() { base.Update(); diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 0d16a78f75..59f322e24c 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -20,23 +20,21 @@ namespace osu.Game.Tests /// public class WaveformTestBeatmap : WorkingBeatmap { - private readonly ZipArchiveReader reader; - private readonly Stream stream; private readonly ITrackStore trackStore; + private Stream getStream() => TestResources.GetTestBeatmapStream(); + + private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); + public WaveformTestBeatmap(AudioManager audioManager) : base(new BeatmapInfo(), audioManager) { - stream = TestResources.GetTestBeatmapStream(); - reader = new ZipArchiveReader(stream); - trackStore = audioManager.GetTrackStore(reader); + trackStore = audioManager.GetTrackStore(getZipReader()); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - stream?.Dispose(); - reader?.Dispose(); trackStore?.Dispose(); } @@ -50,15 +48,23 @@ namespace osu.Game.Tests protected override Track GetTrack() => trackStore.Get(firstAudioFile); - private string firstAudioFile => reader.Filenames.First(f => f.EndsWith(".mp3")); - - private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))); + private string firstAudioFile + { + get + { + using (var reader = getZipReader()) + return reader.Filenames.First(f => f.EndsWith(".mp3")); + } + } private Beatmap createTestBeatmap() { - using (var beatmapStream = getBeatmapStream()) - using (var beatmapReader = new LineBufferedReader(beatmapStream)) - return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader); + using (var reader = getZipReader()) + { + using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))) + using (var beatmapReader = new LineBufferedReader(beatmapStream)) + return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader); + } } } } From a3154f2f7b3046db8930de15b55636adf6f510d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2019 20:30:58 +0900 Subject: [PATCH 33/57] 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 abb3cc8244..dd11804b90 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e5f34b1c7e..757e0e11fa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c84e617285..0dba92b975 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 3ac0e3ce51cd811cb2cc07afbfd329aae5e853a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2019 23:03:31 +0900 Subject: [PATCH 34/57] Fix iOS project failing to compile --- osu.Game.Rulesets.Catch.Tests.iOS/Application.cs | 2 +- osu.Game.Rulesets.Mania.Tests.iOS/Application.cs | 2 +- osu.Game.Rulesets.Osu.Tests.iOS/Application.cs | 2 +- osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs | 2 +- osu.Game.Tests.iOS/Application.cs | 2 +- osu.iOS/Application.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs index beca477943..f7f07ef938 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs @@ -5,7 +5,7 @@ using UIKit; namespace osu.Game.Rulesets.Catch.Tests.iOS { - public class Application + public static class Application { public static void Main(string[] args) { diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs index 0362402320..c381ea585d 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs @@ -5,7 +5,7 @@ using UIKit; namespace osu.Game.Rulesets.Mania.Tests.iOS { - public class Application + public static class Application { public static void Main(string[] args) { diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs index 3718264a42..b36d0b5728 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs @@ -5,7 +5,7 @@ using UIKit; namespace osu.Game.Rulesets.Osu.Tests.iOS { - public class Application + public static class Application { public static void Main(string[] args) { diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs index 330cb42901..73faf16d9f 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs @@ -5,7 +5,7 @@ using UIKit; namespace osu.Game.Rulesets.Taiko.Tests.iOS { - public class Application + public static class Application { public static void Main(string[] args) { diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs index d96a3e27a4..9533b90131 100644 --- a/osu.Game.Tests.iOS/Application.cs +++ b/osu.Game.Tests.iOS/Application.cs @@ -5,7 +5,7 @@ using UIKit; namespace osu.Game.Tests.iOS { - public class Application + public static class Application { public static void Main(string[] args) { diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index 30e0e15ad1..740937e0e1 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -5,7 +5,7 @@ using UIKit; namespace osu.iOS { - public class Application + public static class Application { public static void Main(string[] args) { From 7d090d6cd90fd14810aa84d0c30d549f198c09a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 19 Dec 2019 17:52:58 +0300 Subject: [PATCH 35/57] Fix key overlay appearing regardless of the setting --- osu.Game/Screens/Play/KeyCounterDisplay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index 1edb95ca46..9c107f0293 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -43,6 +43,11 @@ namespace osu.Game.Screens.Play private void load(OsuConfigManager config) { config.BindWith(OsuSetting.KeyOverlay, configVisibility); + } + + protected override void LoadComplete() + { + base.LoadComplete(); Visible.BindValueChanged(_ => updateVisibility()); configVisibility.BindValueChanged(_ => updateVisibility(), true); From 656c58450305770ec5f76a7742cf3964e74c40aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2019 13:50:47 +0900 Subject: [PATCH 36/57] Update RestoreDefaultValueButton when default value changes --- osu.Game/Overlays/Settings/SettingsItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 9c390c34ec..31fcb7abd8 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -113,6 +113,7 @@ namespace osu.Game.Overlays.Settings bindable = value; bindable.ValueChanged += _ => UpdateState(); bindable.DisabledChanged += _ => UpdateState(); + bindable.DefaultChanged += _ => UpdateState(); UpdateState(); } } From de8154bc7f5af6a970715c35b347d7900915f34b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2019 13:54:13 +0900 Subject: [PATCH 37/57] Update readme with new testflight link source --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2e854c755..753dee548b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If you are not interested in developing the game, you can still consume our [bin **Latest build:** -| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) +| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | - **Linux** users are recommended to self-compile until we have official deployment in place. From 11214628adb34e9cec915706c38b9efae6833d64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2019 13:50:57 +0900 Subject: [PATCH 38/57] Add post-update notification for iOS users --- osu.Game/Updater/UpdateManager.cs | 5 ++++- osu.iOS/OsuGameIOS.cs | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index e256cdbe45..48505a9891 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -11,7 +11,10 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Updater { - public abstract class UpdateManager : CompositeDrawable + /// + /// An update manager which only shows notifications after an update completes. + /// + public class UpdateManager : CompositeDrawable { [Resolved] private OsuConfigManager config { get; set; } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 6cf18df9a6..e5ff4aec95 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -4,11 +4,19 @@ using System; using Foundation; using osu.Game; +using osu.Game.Updater; namespace osu.iOS { public class OsuGameIOS : OsuGame { public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(new UpdateManager()); + } } } From 1802e0ff111559f07dd28eba251498d3e2876b1b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Dec 2019 16:04:05 +0900 Subject: [PATCH 39/57] Fix storyboard incorrectly re-ordering elements --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 67c4105e6d..b1b27278fe 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Extensions; @@ -42,10 +41,6 @@ namespace osu.Game.Beatmaps.Formats { this.storyboard = storyboard; base.ParseStreamInto(stream, storyboard); - - // OrderBy is used to guarantee that the parsing order of elements with equal start times is maintained (stably-sorted) - foreach (StoryboardLayer layer in storyboard.Layers) - layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList(); } protected override void ParseLine(Storyboard storyboard, Section section, string line) From 705cdde148db14e740e51d02662a0152800b5666 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Dec 2019 16:42:45 +0900 Subject: [PATCH 40/57] Fix incorrect test --- osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 66d53d7e7b..96ff6b81e3 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); Assert.IsTrue(sprite.IsDrawable); Assert.AreEqual(Anchor.Centre, sprite.Origin); - Assert.AreEqual("SB/black.jpg", sprite.Path); + Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); var animation = background.Elements.OfType().First(); Assert.NotNull(animation); From 351e826120093ea0fb383f37237b361d4debd741 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Mon, 16 Dec 2019 15:28:18 +0800 Subject: [PATCH 41/57] Upgrade project targets to netcoreapp3.1 --- .../CatchRuleset__Tests_.xml | 4 +-- .../ManiaRuleset__Tests_.xml | 4 +-- .../runConfigurations/OsuRuleset__Tests_.xml | 4 +-- .../TaikoRuleset__Tests_.xml | 4 +-- .../.idea/runConfigurations/Tournament.xml | 4 +-- .../runConfigurations/Tournament__Tests_.xml | 4 +-- .../.idea/runConfigurations/osu_.xml | 4 +-- .../.idea/runConfigurations/osu___Tests_.xml | 4 +-- .vscode/launch.json | 32 +++++++++---------- .vscode/tasks.json | 2 +- README.md | 4 +-- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../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.csproj | 2 +- 18 files changed, 42 insertions(+), 42 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml index 5372b6f28a..a4154623b6 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml @@ -1,6 +1,6 @@ - WinExe - netcoreapp3.0 + netcoreapp3.1 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 8fc4dbfe72..dea6e6c0fb 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 @@ -9,7 +9,7 @@ WinExe - netcoreapp3.0 + netcoreapp3.1 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 fddf176fd0..9d4e016eae 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 @@ -9,7 +9,7 @@ WinExe - netcoreapp3.0 + netcoreapp3.1 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 b5bd384e05..d728d65bfd 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 @@ -9,7 +9,7 @@ WinExe - netcoreapp3.0 + netcoreapp3.1 diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c5998c9cfc..6c799e5e90 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -10,7 +10,7 @@ WinExe - netcoreapp3.0 + netcoreapp3.1 diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index d58a724c27..7ecfd6ef70 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -11,7 +11,7 @@ WinExe - netcoreapp3.0 + netcoreapp3.1 From 0ebdf90dfa9d4ed99b6df992844fe16a85beaf33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Dec 2019 18:07:39 +0900 Subject: [PATCH 42/57] Move methods below ctor --- osu.Game.Tests/WaveformTestBeatmap.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 59f322e24c..b7d7bb1ee1 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -22,10 +22,6 @@ namespace osu.Game.Tests { private readonly ITrackStore trackStore; - private Stream getStream() => TestResources.GetTestBeatmapStream(); - - private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); - public WaveformTestBeatmap(AudioManager audioManager) : base(new BeatmapInfo(), audioManager) { @@ -38,6 +34,10 @@ namespace osu.Game.Tests trackStore?.Dispose(); } + private Stream getStream() => TestResources.GetTestBeatmapStream(); + + private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); + protected override IBeatmap GetBeatmap() => createTestBeatmap(); protected override Texture GetBackground() => null; From 492a91067191708b5e4d1896181249a4c0fcb3ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2019 19:08:05 +0900 Subject: [PATCH 43/57] Update missed launch configurations --- osu.Game.Rulesets.Catch.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Mania.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Osu.Tests/.vscode/launch.json | 4 ++-- osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index 4030d2d9e7..67d27c33eb 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index 779eb4f277..0811c2724c 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index 67338b7bbe..94568e3852 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index 7d929e6bbf..5b02ecfc91 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", From 5d29ff29cbe4464288b299dc2841664d7c970430 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 22 Dec 2019 20:52:00 +0800 Subject: [PATCH 44/57] Animate "Good bye" instead on exit --- osu.Game/Screens/Menu/MainMenu.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 231115d1e1..8437748d9d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -132,6 +133,8 @@ namespace osu.Game.Screens.Menu private void confirmAndExit() { + if (exitConfirmed) return; + exitConfirmed = true; this.Exit(); } @@ -250,6 +253,12 @@ namespace osu.Game.Screens.Menu return true; } + if (dialogOverlay.CurrentDialog is ConfirmExitDialog) + { + exitConfirmed = true; + dialogOverlay.CurrentDialog.Buttons.First().Click(); + } + buttons.State = ButtonSystemState.Exit; this.FadeOut(3000); return base.OnExiting(next); From 1f41acc5b92648a2d3c51afcf775346cf9090404 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 22 Dec 2019 21:20:55 +0800 Subject: [PATCH 45/57] Integrate auto click into exit confirmed check --- osu.Game/Screens/Menu/MainMenu.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 8437748d9d..ee4d98bd89 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -247,14 +247,14 @@ namespace osu.Game.Screens.Menu public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + if (!exitConfirmed && dialogOverlay != null) { - dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); - return true; - } + if (!(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + { + dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); + return true; + } - if (dialogOverlay.CurrentDialog is ConfirmExitDialog) - { exitConfirmed = true; dialogOverlay.CurrentDialog.Buttons.First().Click(); } From 796223d3e0e3f62c640a7c4a64046c781b257d87 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 22 Dec 2019 21:39:25 +0800 Subject: [PATCH 46/57] Invert if --- osu.Game/Screens/Menu/MainMenu.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index ee4d98bd89..b28d572b5c 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -249,14 +249,16 @@ namespace osu.Game.Screens.Menu { if (!exitConfirmed && dialogOverlay != null) { - if (!(dialogOverlay.CurrentDialog is ConfirmExitDialog)) + if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog) + { + exitConfirmed = true; + exitDialog.Buttons.First().Click(); + } + else { dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort())); return true; } - - exitConfirmed = true; - dialogOverlay.CurrentDialog.Buttons.First().Click(); } buttons.State = ButtonSystemState.Exit; From e2b4e3580cc55caaa7a0a0941150ab5464d063f8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 21 Dec 2019 15:48:15 +0100 Subject: [PATCH 47/57] Truncate strings passed to the Discord RPC client to a maximum of 128 bytes --- osu.Desktop/DiscordRichPresence.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index b53ca6161b..45552f1906 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -1,7 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using System.Text; using DiscordRPC; +using DiscordRPC.Helper; using DiscordRPC.Message; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -78,8 +81,8 @@ namespace osu.Desktop if (status.Value is UserStatusOnline && activity.Value != null) { - presence.State = activity.Value.Status; - presence.Details = getDetails(activity.Value); + presence.State = truncate(activity.Value.Status); + presence.Details = truncate(getDetails(activity.Value)); } else { @@ -97,6 +100,8 @@ namespace osu.Desktop client.SetPresence(presence); } + private string truncate(string str) => str.WithinLength(128) ? str : new string (str.TakeWhile((c, i) => Encoding.UTF8.GetByteCount(str.Substring(0, i + 1)) <= 125).ToArray()) + '…'; //the ellipsis char is 3 bytes long in UTF8 + private string getDetails(UserActivity activity) { switch (activity) From 125f4286ae75fec93c83a50c243536782a898aed Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 23 Dec 2019 10:55:44 +0100 Subject: [PATCH 48/57] Move truncate() to a method body --- osu.Desktop/DiscordRichPresence.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 45552f1906..cd5fba533b 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -100,7 +100,13 @@ namespace osu.Desktop client.SetPresence(presence); } - private string truncate(string str) => str.WithinLength(128) ? str : new string (str.TakeWhile((c, i) => Encoding.UTF8.GetByteCount(str.Substring(0, i + 1)) <= 125).ToArray()) + '…'; //the ellipsis char is 3 bytes long in UTF8 + private string truncate(string str) + { + if (str.WithinLength(128)) + return str; + + return new string(str.TakeWhile((c, i) => Encoding.UTF8.GetByteCount(str.Substring(0, i + 1)) <= 125).ToArray()) + '…'; //the ellipsis char is 3 bytes long in UTF8 + } private string getDetails(UserActivity activity) { From c7936e40d5c2b02a37ef7821b5d9698146e68a88 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 19:13:36 +0900 Subject: [PATCH 49/57] Protect against potential nullref --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5dfdeb5ebc..8970f9ac88 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -562,7 +562,7 @@ namespace osu.Game.Screens.Play // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. - GameplayClockContainer.StopUsingBeatmapClock(); + GameplayClockContainer?.StopUsingBeatmapClock(); fadeOut(); return base.OnExiting(next); From 43024122430816e425d69174bf554edfd2be4930 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 19:34:12 +0900 Subject: [PATCH 50/57] Simplify implementation --- osu.Desktop/DiscordRichPresence.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index cd5fba533b..85e4b8db55 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -1,10 +1,9 @@ // 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 System; using System.Text; using DiscordRPC; -using DiscordRPC.Helper; using DiscordRPC.Message; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -100,12 +99,19 @@ namespace osu.Desktop client.SetPresence(presence); } - private string truncate(string str) + private string truncate(ReadOnlySpan str) { - if (str.WithinLength(128)) - return str; + if (Encoding.UTF8.GetByteCount(str) < 128) + return new string(str); - return new string(str.TakeWhile((c, i) => Encoding.UTF8.GetByteCount(str.Substring(0, i + 1)) <= 125).ToArray()) + '…'; //the ellipsis char is 3 bytes long in UTF8 + int ellipsisLength = Encoding.UTF8.GetByteCount(new[] { '…' }); + + do + { + str = str[..^1]; + } while (Encoding.UTF8.GetByteCount(str) + ellipsisLength > 128); + + return new string(str) + '…'; } private string getDetails(UserActivity activity) From b9bc1c954eae7513a1fbb31e90cc71ed43c2aabd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Dec 2019 19:50:35 +0900 Subject: [PATCH 51/57] Disable automatic retry of failed discord RPC connections --- osu.Desktop/DiscordRichPresence.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 8818cef8eb..71b73ec78e 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -43,6 +43,10 @@ namespace osu.Desktop }; client.OnReady += onReady; + + // safety measure for now, until we performance test / improve backoff for failed connections. + client.OnConnectionFailed += (_, __) => client.Deinitialize(); + client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u => From f1f9e1f658cca54a0888629a6cd9e5c761fe3115 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 19:56:05 +0900 Subject: [PATCH 52/57] Don't truncate with exactly 128 bytes --- osu.Desktop/DiscordRichPresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 9d2fad2e1a..ce878edda8 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -100,7 +100,7 @@ namespace osu.Desktop private string truncate(ReadOnlySpan str) { - if (Encoding.UTF8.GetByteCount(str) < 128) + if (Encoding.UTF8.GetByteCount(str) <= 128) return new string(str); int ellipsisLength = Encoding.UTF8.GetByteCount(new[] { '…' }); From 0bfd7579362d55a258163c70ba1a8640b1e11eff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Dec 2019 14:21:16 +0900 Subject: [PATCH 53/57] Make OsuTextBox use BasicTextBox --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 1cac4d76ab..72fa135699 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -12,7 +12,7 @@ using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { - public class OsuTextBox : TextBox + public class OsuTextBox : BasicTextBox { protected override float LeftRightPadding => 10; @@ -41,6 +41,8 @@ namespace osu.Game.Graphics.UserInterface BackgroundCommit = BorderColour = colour.Yellow; } + protected override Color4 SelectionColour => new Color4(249, 90, 255, 255); + protected override void OnFocus(FocusEvent e) { BorderThickness = 3; From e088a856620a584ba4dd8b639e9033b6df7f65f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2019 17:12:12 +0900 Subject: [PATCH 54/57] 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 dd11804b90..d684b73d8d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 757e0e11fa..4c5f3e0f38 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0dba92b975..ccfbee9a62 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From bc75bd34f6222c5fa3ccd5678d745700061e10a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2019 18:23:09 +0900 Subject: [PATCH 55/57] Fix caret width having changed --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 72fa135699..f5b7bc3073 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -16,6 +16,8 @@ namespace osu.Game.Graphics.UserInterface { protected override float LeftRightPadding => 10; + protected override float CaretWidth => 3; + protected override SpriteText CreatePlaceholder() => new OsuSpriteText { Font = OsuFont.GetFont(italics: true), From 36dd0e69980eb39c4df566a4ead1b5773dec737d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Dec 2019 11:14:40 +0900 Subject: [PATCH 56/57] Make ellipsis length into a static --- osu.Desktop/DiscordRichPresence.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index ce878edda8..73c717b97c 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -98,17 +98,17 @@ namespace osu.Desktop client.SetPresence(presence); } + private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); + private string truncate(ReadOnlySpan str) { if (Encoding.UTF8.GetByteCount(str) <= 128) return new string(str); - int ellipsisLength = Encoding.UTF8.GetByteCount(new[] { '…' }); - do { str = str[..^1]; - } while (Encoding.UTF8.GetByteCount(str) + ellipsisLength > 128); + } while (Encoding.UTF8.GetByteCount(str) + ellipsis_length > 128); return new string(str) + '…'; } From 1a7937bcf73f0fe3bc87666df542b6e1c0cd3ba6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Dec 2019 12:04:28 +0900 Subject: [PATCH 57/57] Apply suggested optimisations --- osu.Desktop/DiscordRichPresence.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 73c717b97c..05004cf76e 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -100,17 +100,23 @@ namespace osu.Desktop private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' }); - private string truncate(ReadOnlySpan str) + private string truncate(string str) { if (Encoding.UTF8.GetByteCount(str) <= 128) - return new string(str); + return str; + + ReadOnlyMemory strMem = str.AsMemory(); do { - str = str[..^1]; - } while (Encoding.UTF8.GetByteCount(str) + ellipsis_length > 128); + strMem = strMem[..^1]; + } while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128); - return new string(str) + '…'; + return string.Create(strMem.Length + 1, strMem, (span, mem) => + { + mem.Span.CopyTo(span); + span[^1] = '…'; + }); } private string getDetails(UserActivity activity)