From fa53bd96a0b308a52ce5aa43f8a58bfab894786b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 7 Feb 2020 23:14:46 +0300 Subject: [PATCH 01/24] Merge dependency --- osu.Game/Users/User.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 5d0ffd5a67..c573fdd089 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -203,6 +203,21 @@ namespace osu.Game.Users public int ID; } + [JsonProperty("monthly_playcounts")] + public UserHistoryCount[] MonthlyPlaycounts; + + [JsonProperty("replays_watched_counts")] + public UserHistoryCount[] ReplaysWatchedCounts; + + public class UserHistoryCount + { + [JsonProperty("start_date")] + public DateTime Date; + + [JsonProperty("count")] + public long Count; + } + public override string ToString() => Username; /// From 84b7dfb3d6d0031873ba4b964ca5bdf4a4aa9294 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 7 Feb 2020 23:26:35 +0300 Subject: [PATCH 02/24] Implement UserGraph component An abstraction for RankGraph --- .../Profile/Header/Components/RankGraph.cs | 281 +++--------------- osu.Game/Overlays/Profile/UserGraph.cs | 234 +++++++++++++++ 2 files changed, 270 insertions(+), 245 deletions(-) create mode 100644 osu.Game/Overlays/Profile/UserGraph.cs diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index ffc060b3f1..917b086f04 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -4,307 +4,98 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Users; -using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { - public class RankGraph : Container, IHasCustomTooltip + public class RankGraph : UserGraph { - private const float secondary_textsize = 13; - private const float padding = 10; - private const float fade_duration = 150; private const int ranked_days = 88; - private readonly RankChartLineGraph graph; - private readonly OsuSpriteText placeholder; - - private KeyValuePair[] ranks; - private int dayIndex; public readonly Bindable Statistics = new Bindable(); + private readonly OsuSpriteText placeholder; + public RankGraph() { - Padding = new MarginPadding { Vertical = padding }; - Children = new Drawable[] + Add(placeholder = new OsuSpriteText { - placeholder = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "No recent plays", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) - }, - graph = new RankChartLineGraph - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Y = -secondary_textsize, - Alpha = 0, - } - }; + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) + }); - graph.OnBallMove += i => dayIndex = i; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - graph.LineColour = colours.Yellow; + Graph.Alpha = 0; } protected override void LoadComplete() { base.LoadComplete(); - Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true); } private void updateStatistics(UserStatistics statistics) { - placeholder.FadeIn(fade_duration, Easing.Out); + placeholder.FadeIn(FADE_DURATION, Easing.Out); if (statistics?.Ranks.Global == null) { - graph.FadeOut(fade_duration, Easing.Out); - ranks = null; + Graph.FadeOut(FADE_DURATION, Easing.Out); + Data = null; return; } int[] userRanks = statistics.RankHistory?.Data ?? new[] { statistics.Ranks.Global.Value }; - ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + Data = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); - if (ranks.Length > 1) + if (Data.Length > 1) { - placeholder.FadeOut(fade_duration, Easing.Out); + placeholder.FadeOut(FADE_DURATION, Easing.Out); - graph.DefaultValueCount = ranks.Length; - graph.Values = ranks.Select(x => -MathF.Log(x.Value)); + Graph.DefaultValueCount = Data.Length; + Graph.Values = Data.Select(x => -MathF.Log(x.Value)); } - graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + Graph.FadeTo(Data.Length > 1 ? 1 : 0, FADE_DURATION, Easing.Out); } - protected override bool OnHover(HoverEvent e) + protected override object GetTooltipContent() { - if (ranks?.Length > 1) - { - graph.UpdateBallPosition(e.MousePosition.X); - graph.ShowBar(); - } + if (Statistics.Value?.Ranks.Global == null) + return null; - return base.OnHover(e); + var days = ranked_days - Data[DataIndex].Key + 1; + + return new TooltipDisplayContent + { + Rank = $"#{Data[DataIndex].Value:#,##0}", + Time = days == 0 ? "now" : $"{days} days ago" + }; } - protected override bool OnMouseMove(MouseMoveEvent e) + protected override UserGraphTooltip GetTooltip() => new RankGraphTooltip(); + + private class RankGraphTooltip : UserGraphTooltip { - if (ranks?.Length > 1) - graph.UpdateBallPosition(e.MousePosition.X); - - return base.OnMouseMove(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (ranks?.Length > 1) - { - graph.HideBar(); - } - - base.OnHoverLost(e); - } - - private class RankChartLineGraph : LineGraph - { - private readonly CircularContainer movingBall; - private readonly Container bar; - private readonly Box ballBg; - private readonly Box line; - - public Action OnBallMove; - - public RankChartLineGraph() - { - Add(bar = new Container - { - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Alpha = 0, - RelativePositionAxes = Axes.Both, - Children = new Drawable[] - { - line = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = 1.5f, - }, - movingBall = new CircularContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Size = new Vector2(18), - Masking = true, - BorderThickness = 4, - RelativePositionAxes = Axes.Y, - Child = ballBg = new Box { RelativeSizeAxes = Axes.Both } - } - } - }); - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) - { - ballBg.Colour = colourProvider.Background5; - movingBall.BorderColour = line.Colour = colours.Yellow; - } - - public void UpdateBallPosition(float mouseXPosition) - { - const int duration = 200; - int index = calculateIndex(mouseXPosition); - Vector2 position = calculateBallPosition(index); - movingBall.MoveToY(position.Y, duration, Easing.OutQuint); - bar.MoveToX(position.X, duration, Easing.OutQuint); - OnBallMove.Invoke(index); - } - - public void ShowBar() => bar.FadeIn(fade_duration); - - public void HideBar() => bar.FadeOut(fade_duration); - - private int calculateIndex(float mouseXPosition) => (int)MathF.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); - - private Vector2 calculateBallPosition(int index) - { - float y = GetYPosition(Values.ElementAt(index)); - return new Vector2(index / (float)(DefaultValueCount - 1), y); - } - } - - public object TooltipContent - { - get - { - if (Statistics.Value?.Ranks.Global == null) - return null; - - var days = ranked_days - ranks[dayIndex].Key + 1; - - return new TooltipDisplayContent - { - Rank = $"#{ranks[dayIndex].Value:#,##0}", - Time = days == 0 ? "now" : $"{days} days ago" - }; - } - } - - public ITooltip GetCustomTooltip() => new RankGraphTooltip(); - - private class RankGraphTooltip : VisibilityContainer, ITooltip - { - private readonly OsuSpriteText globalRankingText, timeText; - private readonly Box background; - public RankGraphTooltip() + : base(@"Global Ranking") { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 10; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "Global Ranking " - }, - globalRankingText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - } - } - }, - timeText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - } - } - } - }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - // Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231) - // If above is fixed, this should use OverlayColourProvider - background.Colour = colours.Gray1; - } - - public bool SetContent(object content) + public override bool SetContent(object content) { if (!(content is TooltipDisplayContent info)) return false; - globalRankingText.Text = info.Rank; - timeText.Text = info.Time; + Counter.Text = info.Rank; + BottomText.Text = info.Time; return true; } - - private bool instantMove = true; - - public void Move(Vector2 pos) - { - if (instantMove) - { - Position = pos; - instantMove = false; - } - else - this.MoveTo(pos, 200, Easing.OutQuint); - } - - protected override void PopIn() - { - instantMove |= !IsPresent; - this.FadeIn(200, Easing.OutQuint); - } - - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } private class TooltipDisplayContent diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs new file mode 100644 index 0000000000..d0816fd4c6 --- /dev/null +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -0,0 +1,234 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Overlays.Profile +{ + public abstract class UserGraph : Container, IHasCustomTooltip + { + protected const float FADE_DURATION = 150; + + protected readonly RankChartLineGraph Graph; + protected KeyValuePair[] Data; + protected int DataIndex; + + protected UserGraph() + { + Add(Graph = new RankChartLineGraph + { + RelativeSizeAxes = Axes.Both, + }); + + Graph.OnBallMove += i => DataIndex = i; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Graph.LineColour = colours.Yellow; + } + + protected override bool OnHover(HoverEvent e) + { + if (Data?.Length > 1) + { + Graph.UpdateBallPosition(e.MousePosition.X); + Graph.ShowBar(); + } + + return base.OnHover(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (Data?.Length > 1) + Graph.UpdateBallPosition(e.MousePosition.X); + + return base.OnMouseMove(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (Data?.Length > 1) + Graph.HideBar(); + + base.OnHoverLost(e); + } + + public ITooltip GetCustomTooltip() => GetTooltip(); + + public object TooltipContent => GetTooltipContent(); + + protected abstract UserGraphTooltip GetTooltip(); + + protected abstract object GetTooltipContent(); + + protected class RankChartLineGraph : LineGraph + { + private readonly CircularContainer movingBall; + private readonly Container bar; + private readonly Box ballBg; + private readonly Box line; + + public Action OnBallMove; + + public RankChartLineGraph() + { + Add(bar = new Container + { + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Alpha = 0, + RelativePositionAxes = Axes.Both, + Children = new Drawable[] + { + line = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 1.5f, + }, + movingBall = new CircularContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Size = new Vector2(18), + Masking = true, + BorderThickness = 4, + RelativePositionAxes = Axes.Y, + Child = ballBg = new Box { RelativeSizeAxes = Axes.Both } + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + ballBg.Colour = colourProvider.Background5; + movingBall.BorderColour = line.Colour = colours.Yellow; + } + + public void UpdateBallPosition(float mouseXPosition) + { + const int duration = 200; + int index = calculateIndex(mouseXPosition); + Vector2 position = calculateBallPosition(index); + movingBall.MoveToY(position.Y, duration, Easing.OutQuint); + bar.MoveToX(position.X, duration, Easing.OutQuint); + OnBallMove.Invoke(index); + } + + public void ShowBar() => bar.FadeIn(FADE_DURATION); + + public void HideBar() => bar.FadeOut(FADE_DURATION); + + private int calculateIndex(float mouseXPosition) => (int)MathF.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + + private Vector2 calculateBallPosition(int index) + { + float y = GetYPosition(Values.ElementAt(index)); + return new Vector2(index / (float)(DefaultValueCount - 1), y); + } + } + + protected abstract class UserGraphTooltip : VisibilityContainer, ITooltip + { + protected readonly OsuSpriteText Counter, BottomText; + private readonly Box background; + + protected UserGraphTooltip(string topText) + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = $"{topText} " + }, + Counter = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + BottomText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + // Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231) + // If above is fixed, this should use OverlayColourProvider + background.Colour = colours.Gray1; + } + + public abstract bool SetContent(object content); + + private bool instantMove = true; + + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() + { + instantMove |= !IsPresent; + this.FadeIn(200, Easing.OutQuint); + } + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } + } +} From b325725c4533c93e4fb7ebac7b864df82cf49db5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 8 Feb 2020 00:10:17 +0300 Subject: [PATCH 03/24] Implement UserHistoryGraph component --- .../Online/TestSceneUserHistoryGraph.cs | 112 ++++++++++++++++++ .../Profile/Header/Components/RankGraph.cs | 2 - .../Sections/Historical/UserHistoryGraph.cs | 90 ++++++++++++++ osu.Game/Overlays/Profile/UserGraph.cs | 1 + 4 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs new file mode 100644 index 0000000000..88bb002fc2 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -0,0 +1,112 @@ +// 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 osu.Game.Overlays.Profile.Sections.Historical; +using osu.Game.Overlays.Profile; +using osu.Framework.Graphics; +using static osu.Game.Users.User; +using osu.Game.Overlays; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneUserHistoryGraph : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UserHistoryGraph), + typeof(UserGraph<,>), + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + public TestSceneUserHistoryGraph() + { + UserHistoryGraph graph; + + Add(graph = new UserHistoryGraph("Counter Name") + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + var values = new[] + { + new UserHistoryCount + { + Date = new DateTime(2000, 1, 1), + Count = 10, + }, + new UserHistoryCount + { + Date = new DateTime(2000, 2, 1), + Count = 20, + }, + new UserHistoryCount + { + Date = new DateTime(2000, 3, 1), + Count = 100, + }, + new UserHistoryCount + { + Date = new DateTime(2000, 4, 1), + Count = 15, + }, + new UserHistoryCount + { + Date = new DateTime(2000, 5, 1), + Count = 30, + } + }; + + var moreValues = new[] + { + new UserHistoryCount + { + Date = new DateTime(2010, 5, 1), + Count = 1000, + }, + new UserHistoryCount + { + Date = new DateTime(2010, 6, 1), + Count = 20, + }, + new UserHistoryCount + { + Date = new DateTime(2010, 7, 1), + Count = 20000, + }, + new UserHistoryCount + { + Date = new DateTime(2010, 8, 1), + Count = 30, + }, + new UserHistoryCount + { + Date = new DateTime(2010, 9, 1), + Count = 50, + }, + new UserHistoryCount + { + Date = new DateTime(2010, 10, 1), + Count = 2000, + }, + new UserHistoryCount + { + Date = new DateTime(2010, 11, 1), + Count = 2100, + } + }; + + AddStep("Set fake values", () => graph.Values = values); + AddStep("Set more values", () => graph.Values = moreValues); + AddStep("Set null values", () => graph.Values = null); + AddStep("Set empty values", () => graph.Values = Array.Empty()); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 917b086f04..2a571e46d1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -29,8 +29,6 @@ namespace osu.Game.Overlays.Profile.Header.Components Text = "No recent plays", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) }); - - Graph.Alpha = 0; } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs new file mode 100644 index 0000000000..5129ce872f --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -0,0 +1,90 @@ +// 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.Graphics; +using static osu.Game.Users.User; + +namespace osu.Game.Overlays.Profile.Sections.Historical +{ + public class UserHistoryGraph : UserGraph + { + private UserHistoryCount[] values; + + public UserHistoryCount[] Values + { + get => values; + set + { + values = value; + updateValues(value); + } + } + + private readonly string tooltipCounterName; + + public UserHistoryGraph(string tooltipCounterName) + { + this.tooltipCounterName = tooltipCounterName; + } + + private void updateValues(UserHistoryCount[] values) + { + if (values == null || !values.Any()) + { + Graph.FadeOut(FADE_DURATION, Easing.Out); + Data = null; + return; + } + + Data = values.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); + + if (values.Length > 1) + { + Graph.DefaultValueCount = Data.Length; + Graph.Values = Data.Select(x => (float)x.Value); + Graph.FadeIn(FADE_DURATION, Easing.Out); + } + } + + protected override object GetTooltipContent() + { + if (!Data?.Any() ?? true) + return null; + + return new TooltipDisplayContent + { + Count = Data[DataIndex].Value.ToString("N0"), + Date = Data[DataIndex].Key.ToString("MMMM yyyy") + }; + } + + protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(tooltipCounterName); + + private class HistoryGraphTooltip : UserGraphTooltip + { + public HistoryGraphTooltip(string topText) + : base(topText) + { + } + + public override bool SetContent(object content) + { + if (!(content is TooltipDisplayContent info)) + return false; + + Counter.Text = info.Count; + BottomText.Text = info.Date; + return true; + } + } + + private class TooltipDisplayContent + { + public string Count; + public string Date; + } + } +} diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index d0816fd4c6..13ea347032 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Profile Add(Graph = new RankChartLineGraph { RelativeSizeAxes = Axes.Both, + Alpha = 0 }); Graph.OnBallMove += i => DataIndex = i; From 5a6a77b609dcf4e9b10fd9c6975b5a8ac964d1f0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 8 Feb 2020 00:13:26 +0300 Subject: [PATCH 04/24] Fix usings order --- osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index 88bb002fc2..bf77e5d60a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Overlays.Profile; using osu.Framework.Graphics; -using static osu.Game.Users.User; using osu.Game.Overlays; using osu.Framework.Allocation; +using static osu.Game.Users.User; namespace osu.Game.Tests.Visual.Online { From 9e5da60614f150af5077da96db092134fa7c136a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 Feb 2020 00:28:38 +0300 Subject: [PATCH 05/24] Rename RankChartLineGraph to UserLineGraph --- osu.Game/Overlays/Profile/UserGraph.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 13ea347032..f3d4f824f7 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -21,13 +21,13 @@ namespace osu.Game.Overlays.Profile { protected const float FADE_DURATION = 150; - protected readonly RankChartLineGraph Graph; + protected readonly UserLineGraph Graph; protected KeyValuePair[] Data; protected int DataIndex; protected UserGraph() { - Add(Graph = new RankChartLineGraph + Add(Graph = new UserLineGraph { RelativeSizeAxes = Axes.Both, Alpha = 0 @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Profile protected abstract object GetTooltipContent(); - protected class RankChartLineGraph : LineGraph + protected class UserLineGraph : LineGraph { private readonly CircularContainer movingBall; private readonly Container bar; @@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Profile public Action OnBallMove; - public RankChartLineGraph() + public UserLineGraph() { Add(bar = new Container { From e2ecef732cfafa97619e2918572c06b6f5a536ed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 9 Feb 2020 00:36:41 +0300 Subject: [PATCH 06/24] Make TooltipCounterName an abstract property --- .../Visual/Online/TestSceneUserHistoryGraph.cs | 14 ++++++++++++-- .../Profile/Header/Components/RankGraph.cs | 5 +---- .../Sections/Historical/UserHistoryGraph.cs | 18 ++---------------- osu.Game/Overlays/Profile/UserGraph.cs | 6 ++++-- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index bf77e5d60a..164d719a00 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -25,9 +25,9 @@ namespace osu.Game.Tests.Visual.Online public TestSceneUserHistoryGraph() { - UserHistoryGraph graph; + TestGraph graph; - Add(graph = new UserHistoryGraph("Counter Name") + Add(graph = new TestGraph { RelativeSizeAxes = Axes.X, Height = 200, @@ -108,5 +108,15 @@ namespace osu.Game.Tests.Visual.Online AddStep("Set null values", () => graph.Values = null); AddStep("Set empty values", () => graph.Values = Array.Empty()); } + + private class TestGraph : UserHistoryGraph + { + protected override UserGraphTooltip GetTooltip() => new TestTooltip(); + + private class TestTooltip : HistoryGraphTooltip + { + protected override string TooltipCounterName => "Test Counter"; + } + } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 2a571e46d1..b62864364b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -80,10 +80,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private class RankGraphTooltip : UserGraphTooltip { - public RankGraphTooltip() - : base(@"Global Ranking") - { - } + protected override string TooltipCounterName => @"Global Ranking"; public override bool SetContent(object content) { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 5129ce872f..2e389d3ac6 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -9,7 +9,7 @@ using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class UserHistoryGraph : UserGraph + public abstract class UserHistoryGraph : UserGraph { private UserHistoryCount[] values; @@ -23,13 +23,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } - private readonly string tooltipCounterName; - - public UserHistoryGraph(string tooltipCounterName) - { - this.tooltipCounterName = tooltipCounterName; - } - private void updateValues(UserHistoryCount[] values) { if (values == null || !values.Any()) @@ -61,15 +54,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }; } - protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(tooltipCounterName); - - private class HistoryGraphTooltip : UserGraphTooltip + protected abstract class HistoryGraphTooltip : UserGraphTooltip { - public HistoryGraphTooltip(string topText) - : base(topText) - { - } - public override bool SetContent(object content) { if (!(content is TooltipDisplayContent info)) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index f3d4f824f7..122a6ded36 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -153,7 +153,9 @@ namespace osu.Game.Overlays.Profile protected readonly OsuSpriteText Counter, BottomText; private readonly Box background; - protected UserGraphTooltip(string topText) + protected abstract string TooltipCounterName { get; } + + protected UserGraphTooltip() { AutoSizeAxes = Axes.Both; Masking = true; @@ -181,7 +183,7 @@ namespace osu.Game.Overlays.Profile new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = $"{topText} " + Text = $"{TooltipCounterName} " }, Counter = new OsuSpriteText { From 2b0bdd1db5deee1efcae3d7388aae3c4a03e2d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Feb 2020 19:15:37 +0100 Subject: [PATCH 07/24] Refactor tooltip construction --- .../Profile/Header/Components/RankGraph.cs | 9 +++------ .../Sections/Historical/UserHistoryGraph.cs | 9 +++------ osu.Game/Overlays/Profile/UserGraph.cs | 20 ++++++++++++++----- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index b62864364b..097b68f3aa 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -62,16 +62,13 @@ namespace osu.Game.Overlays.Profile.Header.Components Graph.FadeTo(Data.Length > 1 ? 1 : 0, FADE_DURATION, Easing.Out); } - protected override object GetTooltipContent() + protected override object GetTooltipContent(int index, int rank) { - if (Statistics.Value?.Ranks.Global == null) - return null; - - var days = ranked_days - Data[DataIndex].Key + 1; + var days = ranked_days - index + 1; return new TooltipDisplayContent { - Rank = $"#{Data[DataIndex].Value:#,##0}", + Rank = $"#{rank:#,##0}", Time = days == 0 ? "now" : $"{days} days ago" }; } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 2e389d3ac6..d37454d607 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -42,15 +42,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } - protected override object GetTooltipContent() + protected override object GetTooltipContent(DateTime date, long playCount) { - if (!Data?.Any() ?? true) - return null; - return new TooltipDisplayContent { - Count = Data[DataIndex].Value.ToString("N0"), - Date = Data[DataIndex].Key.ToString("MMMM yyyy") + Count = playCount.ToString("N0"), + Date = date.ToString("MMMM yyyy") }; } diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 122a6ded36..f1f5b50cd3 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile protected readonly UserLineGraph Graph; protected KeyValuePair[] Data; - protected int DataIndex; + private int dataIndex; protected UserGraph() { @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Profile Alpha = 0 }); - Graph.OnBallMove += i => DataIndex = i; + Graph.OnBallMove += i => dataIndex = i; } [BackgroundDependencyLoader] @@ -71,11 +71,21 @@ namespace osu.Game.Overlays.Profile public ITooltip GetCustomTooltip() => GetTooltip(); - public object TooltipContent => GetTooltipContent(); - protected abstract UserGraphTooltip GetTooltip(); - protected abstract object GetTooltipContent(); + public object TooltipContent + { + get + { + if (Data == null || Data.Length == 0) + return null; + + var (key, value) = Data[dataIndex]; + return GetTooltipContent(key, value); + } + } + + protected abstract object GetTooltipContent(TKey key, TValue value); protected class UserLineGraph : LineGraph { From 9edddbaf4691ee04f2b8af0a9263dc673c1072ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Feb 2020 20:19:20 +0100 Subject: [PATCH 08/24] Encapsulate base graph further --- .../Profile/Header/Components/RankGraph.cs | 24 ++++---- .../Sections/Historical/UserHistoryGraph.cs | 32 +++------- osu.Game/Overlays/Profile/UserGraph.cs | 61 ++++++++++++++----- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 097b68f3aa..77edfe2746 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -39,27 +39,29 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateStatistics(UserStatistics statistics) { - placeholder.FadeIn(FADE_DURATION, Easing.Out); + int[] userRanks = statistics?.RankHistory?.Data; - if (statistics?.Ranks.Global == null) + if (userRanks == null) { - Graph.FadeOut(FADE_DURATION, Easing.Out); Data = null; return; } - int[] userRanks = statistics.RankHistory?.Data ?? new[] { statistics.Ranks.Global.Value }; Data = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + } - if (Data.Length > 1) - { - placeholder.FadeOut(FADE_DURATION, Easing.Out); + protected override float GetDataPointHeight(int rank) => -MathF.Log(rank); - Graph.DefaultValueCount = Data.Length; - Graph.Values = Data.Select(x => -MathF.Log(x.Value)); - } + protected override void ShowGraph() + { + base.ShowGraph(); + placeholder.FadeOut(FADE_DURATION, Easing.Out); + } - Graph.FadeTo(Data.Length > 1 ? 1 : 0, FADE_DURATION, Easing.Out); + protected override void HideGraph() + { + base.HideGraph(); + placeholder.FadeIn(FADE_DURATION, Easing.Out); } protected override object GetTooltipContent(int index, int rank) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index d37454d607..ccc286d423 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -4,43 +4,27 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics; using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { public abstract class UserHistoryGraph : UserGraph { - private UserHistoryCount[] values; - public UserHistoryCount[] Values { - get => values; set { - values = value; - updateValues(value); + if (value == null) + { + Data = null; + return; + } + + Data = value.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } } - private void updateValues(UserHistoryCount[] values) - { - if (values == null || !values.Any()) - { - Graph.FadeOut(FADE_DURATION, Easing.Out); - Data = null; - return; - } - - Data = values.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); - - if (values.Length > 1) - { - Graph.DefaultValueCount = Data.Length; - Graph.Values = Data.Select(x => (float)x.Value); - Graph.FadeIn(FADE_DURATION, Easing.Out); - } - } + protected override float GetDataPointHeight(long playCount) => playCount; protected override object GetTooltipContent(DateTime date, long playCount) { diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index f1f5b50cd3..86e405c8f0 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -21,54 +21,83 @@ namespace osu.Game.Overlays.Profile { protected const float FADE_DURATION = 150; - protected readonly UserLineGraph Graph; - protected KeyValuePair[] Data; + private readonly UserLineGraph graph; + private KeyValuePair[] data; private int dataIndex; protected UserGraph() { - Add(Graph = new UserLineGraph + data = Array.Empty>(); + + Add(graph = new UserLineGraph { RelativeSizeAxes = Axes.Both, Alpha = 0 }); - Graph.OnBallMove += i => dataIndex = i; + graph.OnBallMove += i => dataIndex = i; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - Graph.LineColour = colours.Yellow; + graph.LineColour = colours.Yellow; } protected override bool OnHover(HoverEvent e) { - if (Data?.Length > 1) - { - Graph.UpdateBallPosition(e.MousePosition.X); - Graph.ShowBar(); - } + if (data.Length <= 1) + return base.OnHover(e); + + graph.UpdateBallPosition(e.MousePosition.X); + graph.ShowBar(); return base.OnHover(e); } protected override bool OnMouseMove(MouseMoveEvent e) { - if (Data?.Length > 1) - Graph.UpdateBallPosition(e.MousePosition.X); + if (data.Length > 1) + graph.UpdateBallPosition(e.MousePosition.X); return base.OnMouseMove(e); } protected override void OnHoverLost(HoverLostEvent e) { - if (Data?.Length > 1) - Graph.HideBar(); + if (data.Length > 1) + graph.HideBar(); base.OnHoverLost(e); } + protected KeyValuePair[] Data + { + set + { + value ??= Array.Empty>(); + data = value; + redrawGraph(); + } + } + + private void redrawGraph() + { + if (data.Length == 0) + { + HideGraph(); + return; + } + + graph.DefaultValueCount = data.Length; + graph.Values = data.Select(pair => GetDataPointHeight(pair.Value)).ToArray(); + ShowGraph(); + } + + protected abstract float GetDataPointHeight(TValue value); + protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out); + protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out); + public ITooltip GetCustomTooltip() => GetTooltip(); protected abstract UserGraphTooltip GetTooltip(); @@ -77,10 +106,10 @@ namespace osu.Game.Overlays.Profile { get { - if (Data == null || Data.Length == 0) + if (data.Length == 0) return null; - var (key, value) = Data[dataIndex]; + var (key, value) = data[dataIndex]; return GetTooltipContent(key, value); } } From 60a1dad67d8f51ce21e5bd40cc64792043e6a18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Feb 2020 20:35:31 +0100 Subject: [PATCH 09/24] Explicitly handle hover event --- osu.Game/Overlays/Profile/UserGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 86e405c8f0..de3d91f088 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Profile graph.UpdateBallPosition(e.MousePosition.X); graph.ShowBar(); - return base.OnHover(e); + return true; } protected override bool OnMouseMove(MouseMoveEvent e) From d3937acfe9f8ed0d7ca91fbbeae293761ccddcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Mar 2020 20:11:14 +0100 Subject: [PATCH 10/24] Fix rank graph tooltip display --- osu.Game/Overlays/Profile/Header/Components/RankGraph.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 77edfe2746..39fa0ca251 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics; @@ -70,8 +71,8 @@ namespace osu.Game.Overlays.Profile.Header.Components return new TooltipDisplayContent { - Rank = $"#{rank:#,##0}", - Time = days == 0 ? "now" : $"{days} days ago" + Rank = $"#{rank:N0}", + Time = days == 0 ? "now" : $"{"day".ToQuantity(days)} ago" }; } From b1de47a6afb6089c31f047b95414b7b11c3a1537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 5 Mar 2020 20:34:33 +0100 Subject: [PATCH 11/24] Adjust graph sizings to match web --- osu.Game/Overlays/Profile/UserGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index de3d91f088..48671b8a70 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -141,13 +141,13 @@ namespace osu.Game.Overlays.Profile Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, - Width = 1.5f, + Width = 2, }, movingBall = new CircularContainer { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Size = new Vector2(18), + Size = new Vector2(20), Masking = true, BorderThickness = 4, RelativePositionAxes = Axes.Y, From b77bd08925461bcbd8b24129570c8cdae17c6fc0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Mar 2020 19:20:06 +0300 Subject: [PATCH 12/24] Simplify null values handling --- .../Profile/Sections/Historical/UserHistoryGraph.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index ccc286d423..6de1b8e0f0 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -12,16 +12,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public UserHistoryCount[] Values { - set - { - if (value == null) - { - Data = null; - return; - } - - Data = value.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); - } + set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } protected override float GetDataPointHeight(long playCount) => playCount; From bea2b7094879d36165f179ae130c482b64bf3b02 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Mar 2020 19:22:03 +0300 Subject: [PATCH 13/24] Adjust OnHover syntax --- osu.Game/Overlays/Profile/UserGraph.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 48671b8a70..e2db79024c 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -46,13 +46,15 @@ namespace osu.Game.Overlays.Profile protected override bool OnHover(HoverEvent e) { - if (data.Length <= 1) - return base.OnHover(e); + if (data.Length > 1) + { + graph.UpdateBallPosition(e.MousePosition.X); + graph.ShowBar(); - graph.UpdateBallPosition(e.MousePosition.X); - graph.ShowBar(); + return true; + } - return true; + return base.OnHover(e); } protected override bool OnMouseMove(MouseMoveEvent e) From 432c52bf276747248acc91e8e155a36294ed6a10 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Mar 2020 19:26:15 +0300 Subject: [PATCH 14/24] Simplify test scene --- .../Online/TestSceneUserHistoryGraph.cs | 72 ++++--------------- 1 file changed, 12 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index 164d719a00..26f6ac199b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -37,70 +37,22 @@ namespace osu.Game.Tests.Visual.Online var values = new[] { - new UserHistoryCount - { - Date = new DateTime(2000, 1, 1), - Count = 10, - }, - new UserHistoryCount - { - Date = new DateTime(2000, 2, 1), - Count = 20, - }, - new UserHistoryCount - { - Date = new DateTime(2000, 3, 1), - Count = 100, - }, - new UserHistoryCount - { - Date = new DateTime(2000, 4, 1), - Count = 15, - }, - new UserHistoryCount - { - Date = new DateTime(2000, 5, 1), - Count = 30, - } + new UserHistoryCount { Date = new DateTime(2000, 1, 1), Count = 10 }, + new UserHistoryCount { Date = new DateTime(2000, 2, 1), Count = 20 }, + new UserHistoryCount { Date = new DateTime(2000, 3, 1), Count = 100 }, + new UserHistoryCount { Date = new DateTime(2000, 4, 1), Count = 15 }, + new UserHistoryCount { Date = new DateTime(2000, 5, 1), Count = 30 } }; var moreValues = new[] { - new UserHistoryCount - { - Date = new DateTime(2010, 5, 1), - Count = 1000, - }, - new UserHistoryCount - { - Date = new DateTime(2010, 6, 1), - Count = 20, - }, - new UserHistoryCount - { - Date = new DateTime(2010, 7, 1), - Count = 20000, - }, - new UserHistoryCount - { - Date = new DateTime(2010, 8, 1), - Count = 30, - }, - new UserHistoryCount - { - Date = new DateTime(2010, 9, 1), - Count = 50, - }, - new UserHistoryCount - { - Date = new DateTime(2010, 10, 1), - Count = 2000, - }, - new UserHistoryCount - { - Date = new DateTime(2010, 11, 1), - Count = 2100, - } + new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 }, + new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 }, + new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 }, + new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 }, + new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 }, + new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 }, + new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 } }; AddStep("Set fake values", () => graph.Values = values); From 06855c09c741bba8dd1bc0696c5bfccd08273621 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Mar 2020 19:42:35 +0300 Subject: [PATCH 15/24] Make data nullable --- .../Overlays/Profile/Header/Components/RankGraph.cs | 8 +------- osu.Game/Overlays/Profile/UserGraph.cs | 12 +++++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 39fa0ca251..13fbaa7f85 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -42,13 +42,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { int[] userRanks = statistics?.RankHistory?.Data; - if (userRanks == null) - { - Data = null; - return; - } - - Data = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + Data = userRanks?.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); } protected override float GetDataPointHeight(int rank) => -MathF.Log(rank); diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index e2db79024c..64f988b0c1 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Profile protected UserGraph() { - data = Array.Empty>(); - Add(graph = new UserLineGraph { RelativeSizeAxes = Axes.Both, @@ -46,7 +44,7 @@ namespace osu.Game.Overlays.Profile protected override bool OnHover(HoverEvent e) { - if (data.Length > 1) + if (data?.Length > 1) { graph.UpdateBallPosition(e.MousePosition.X); graph.ShowBar(); @@ -59,7 +57,7 @@ namespace osu.Game.Overlays.Profile protected override bool OnMouseMove(MouseMoveEvent e) { - if (data.Length > 1) + if (data?.Length > 1) graph.UpdateBallPosition(e.MousePosition.X); return base.OnMouseMove(e); @@ -67,7 +65,7 @@ namespace osu.Game.Overlays.Profile protected override void OnHoverLost(HoverLostEvent e) { - if (data.Length > 1) + if (data?.Length > 1) graph.HideBar(); base.OnHoverLost(e); @@ -85,7 +83,7 @@ namespace osu.Game.Overlays.Profile private void redrawGraph() { - if (data.Length == 0) + if (!data?.Any() ?? true) { HideGraph(); return; @@ -108,7 +106,7 @@ namespace osu.Game.Overlays.Profile { get { - if (data.Length == 0) + if (!data?.Any() ?? true) return null; var (key, value) = data[dataIndex]; From d6adc06f6e0f4334d571e4f3e806bbabf19f8157 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 9 Mar 2020 20:13:59 +0300 Subject: [PATCH 16/24] Add xmldoc --- osu.Game/Overlays/Profile/UserGraph.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 64f988b0c1..07346a3e45 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -17,6 +17,11 @@ using osuTK; namespace osu.Game.Overlays.Profile { + /// + /// Graph which is used in to present changes in user statistics over time. + /// + /// Type of data to be used for X-axis of the graph. + /// Type of data to be used for Y-axis of the graph. public abstract class UserGraph : Container, IHasCustomTooltip { protected const float FADE_DURATION = 150; @@ -192,6 +197,9 @@ namespace osu.Game.Overlays.Profile protected readonly OsuSpriteText Counter, BottomText; private readonly Box background; + /// + /// Text which will be shown near the . + /// protected abstract string TooltipCounterName { get; } protected UserGraphTooltip() From f6461dc5f8381d3f62523e155f6f5e7646abb788 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Mar 2020 00:19:28 +0300 Subject: [PATCH 17/24] Add more consistency to data null checks --- osu.Game/Overlays/Profile/UserGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 07346a3e45..cea4600523 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Profile private void redrawGraph() { - if (!data?.Any() ?? true) + if (!(data?.Length > 1)) { HideGraph(); return; @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Profile { get { - if (!data?.Any() ?? true) + if (!(data?.Length > 1)) return null; var (key, value) = data[dataIndex]; From 2f441baeacc8e9421d4914a7d5a69eba9f87793d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Mar 2020 00:50:12 +0300 Subject: [PATCH 18/24] Make UserHistoryGraph non-abstract --- .../Visual/Online/TestSceneUserHistoryGraph.cs | 15 +++------------ .../Profile/Header/Components/RankGraph.cs | 5 ++++- .../Sections/Historical/UserHistoryGraph.cs | 16 ++++++++++++++-- osu.Game/Overlays/Profile/UserGraph.cs | 10 +++------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index 26f6ac199b..83607bea6a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -25,14 +25,15 @@ namespace osu.Game.Tests.Visual.Online public TestSceneUserHistoryGraph() { - TestGraph graph; + UserHistoryGraph graph; - Add(graph = new TestGraph + Add(graph = new UserHistoryGraph { RelativeSizeAxes = Axes.X, Height = 200, Anchor = Anchor.Centre, Origin = Anchor.Centre, + TooltipCounterName = "Test" }); var values = new[] @@ -60,15 +61,5 @@ namespace osu.Game.Tests.Visual.Online AddStep("Set null values", () => graph.Values = null); AddStep("Set empty values", () => graph.Values = Array.Empty()); } - - private class TestGraph : UserHistoryGraph - { - protected override UserGraphTooltip GetTooltip() => new TestTooltip(); - - private class TestTooltip : HistoryGraphTooltip - { - protected override string TooltipCounterName => "Test Counter"; - } - } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 13fbaa7f85..73ae91e345 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -74,7 +74,10 @@ namespace osu.Game.Overlays.Profile.Header.Components private class RankGraphTooltip : UserGraphTooltip { - protected override string TooltipCounterName => @"Global Ranking"; + public RankGraphTooltip() + : base(@"Global Ranking") + { + } public override bool SetContent(object content) { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 6de1b8e0f0..5f6f6cc3e4 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -8,15 +8,22 @@ using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { - public abstract class UserHistoryGraph : UserGraph + public class UserHistoryGraph : UserGraph { public UserHistoryCount[] Values { set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } + /// + /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the + /// + public string TooltipCounterName { get; set; } = @"Plays"; + protected override float GetDataPointHeight(long playCount) => playCount; + protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(TooltipCounterName); + protected override object GetTooltipContent(DateTime date, long playCount) { return new TooltipDisplayContent @@ -26,8 +33,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }; } - protected abstract class HistoryGraphTooltip : UserGraphTooltip + protected class HistoryGraphTooltip : UserGraphTooltip { + public HistoryGraphTooltip(string tooltipCounterName) + : base(tooltipCounterName) + { + } + public override bool SetContent(object content) { if (!(content is TooltipDisplayContent info)) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index cea4600523..c19844960b 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -197,12 +197,7 @@ namespace osu.Game.Overlays.Profile protected readonly OsuSpriteText Counter, BottomText; private readonly Box background; - /// - /// Text which will be shown near the . - /// - protected abstract string TooltipCounterName { get; } - - protected UserGraphTooltip() + protected UserGraphTooltip(string tooltipCounterName) { AutoSizeAxes = Axes.Both; Masking = true; @@ -225,12 +220,13 @@ namespace osu.Game.Overlays.Profile { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), Children = new Drawable[] { new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = $"{TooltipCounterName} " + Text = tooltipCounterName }, Counter = new OsuSpriteText { From d2b4856d134d5e6792b40aca2dd1e49b51d2d97f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 10 Mar 2020 01:02:09 +0300 Subject: [PATCH 19/24] Add more xmldoc --- osu.Game/Overlays/Profile/UserGraph.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index c19844960b..aee464dbf9 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -76,6 +76,9 @@ namespace osu.Game.Overlays.Profile base.OnHoverLost(e); } + /// + /// Set of values which will be used to create a graph. + /// protected KeyValuePair[] Data { set @@ -99,7 +102,13 @@ namespace osu.Game.Overlays.Profile ShowGraph(); } + /// + /// Function used to convert point to it's Y-axis position on the graph. + /// + /// Value to convert. + /// protected abstract float GetDataPointHeight(TValue value); + protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out); protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out); From 299ea236121d0fd47fc56aa2508dcac3290bc843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Mar 2020 14:26:49 +0100 Subject: [PATCH 20/24] Clean up xmldocs --- .../Overlays/Profile/Sections/Historical/UserHistoryGraph.cs | 2 +- osu.Game/Overlays/Profile/UserGraph.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 5f6f6cc3e4..5009c13512 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } /// - /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the + /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the . /// public string TooltipCounterName { get; set; } = @"Plays"; diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index aee464dbf9..651e9ba8b3 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -106,7 +106,6 @@ namespace osu.Game.Overlays.Profile /// Function used to convert point to it's Y-axis position on the graph. /// /// Value to convert. - /// protected abstract float GetDataPointHeight(TValue value); protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out); From ce4761747639e4830bdc415af6513a91bd1f839d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Mar 2020 14:28:23 +0100 Subject: [PATCH 21/24] Trim unnecessary raw string prefixes --- osu.Game/Overlays/Profile/Header/Components/RankGraph.cs | 2 +- .../Overlays/Profile/Sections/Historical/UserHistoryGraph.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 73ae91e345..7d094b3be1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private class RankGraphTooltip : UserGraphTooltip { public RankGraphTooltip() - : base(@"Global Ranking") + : base("Global Ranking") { } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 5009c13512..b690c2051c 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical /// /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the . /// - public string TooltipCounterName { get; set; } = @"Plays"; + public string TooltipCounterName { get; set; } = "Plays"; protected override float GetDataPointHeight(long playCount) => playCount; From d167e0c8b9ca107eef6e1bdffd20ece0562932ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Mar 2020 14:35:04 +0100 Subject: [PATCH 22/24] Mark properties as [CanBeNull] --- .../Overlays/Profile/Sections/Historical/UserHistoryGraph.cs | 2 ++ osu.Game/Overlays/Profile/UserGraph.cs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index b690c2051c..b1e8c8f0ca 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using static osu.Game.Users.User; namespace osu.Game.Overlays.Profile.Sections.Historical { public class UserHistoryGraph : UserGraph { + [CanBeNull] public UserHistoryCount[] Values { set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 651e9ba8b3..5ef42bc3fa 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -79,11 +80,11 @@ namespace osu.Game.Overlays.Profile /// /// Set of values which will be used to create a graph. /// + [CanBeNull] protected KeyValuePair[] Data { set { - value ??= Array.Empty>(); data = value; redrawGraph(); } From af7d6d0a4e4f6b62d042c2a4a301dbf1011db3a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Mar 2020 14:45:32 +0100 Subject: [PATCH 23/24] Invert data length checks for consistency --- osu.Game/Overlays/Profile/UserGraph.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index 5ef42bc3fa..95f8e0c923 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -92,15 +92,15 @@ namespace osu.Game.Overlays.Profile private void redrawGraph() { - if (!(data?.Length > 1)) + if (data?.Length > 1) { - HideGraph(); + graph.DefaultValueCount = data.Length; + graph.Values = data.Select(pair => GetDataPointHeight(pair.Value)).ToArray(); + ShowGraph(); return; } - graph.DefaultValueCount = data.Length; - graph.Values = data.Select(pair => GetDataPointHeight(pair.Value)).ToArray(); - ShowGraph(); + HideGraph(); } /// @@ -120,11 +120,13 @@ namespace osu.Game.Overlays.Profile { get { - if (!(data?.Length > 1)) - return null; + if (data?.Length > 1) + { + var (key, value) = data[dataIndex]; + return GetTooltipContent(key, value); + } - var (key, value) = data[dataIndex]; - return GetTooltipContent(key, value); + return null; } } From 11cf04eed171f3dac856929f917febc62b8d9636 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Nov 2020 13:39:42 +0900 Subject: [PATCH 24/24] Fix frames potentially getting added to spectator replay in wrong format The way spectator currently works, the `Spectator` screen is responsible for adding new frames to the replay, even when it has a child (`SpectatorPlayer`) present. There was a possibility that a new play had already started, and on returning to the Spectator screen (to initialise the new play) there would be a brief period where the Player instance is still reading from the replay, the `userBeganPlaying` call had not yet finished initialising the new target replay, and `userSentFrames` is run (asynchronously), writing frames to the previous replay using the incorrect ruleset instance). To make this work, it doesn't `Schedule` frame addition to the replay (making things a bit unsafe). Changing this itself isn't such a simple one to do, so I instead opted to fix this via locking. Closes https://github.com/ppy/osu/issues/10777. --- osu.Game/Screens/Play/Spectator.cs | 80 ++++++++++++++++++------------ 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 0f593db277..6f51771c12 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -61,7 +62,9 @@ namespace osu.Game.Screens.Play [Resolved] private RulesetStore rulesets { get; set; } - private Replay replay; + private Score score; + + private readonly object scoreLock = new object(); private Container beatmapPanelContainer; @@ -198,23 +201,32 @@ namespace osu.Game.Screens.Play private void userSentFrames(int userId, FrameDataBundle data) { + // this is not scheduled as it handles propagation of frames even when in a child screen (at which point we are not alive). + // probably not the safest way to handle this. + if (userId != targetUser.Id) return; - // this should never happen as the server sends the user's state on watching, - // but is here as a safety measure. - if (replay == null) - return; - - foreach (var frame in data.Frames) + lock (scoreLock) { - IConvertibleReplayFrame convertibleFrame = rulesetInstance.CreateConvertibleReplayFrame(); - convertibleFrame.FromLegacy(frame, beatmap.Value.Beatmap); + // this should never happen as the server sends the user's state on watching, + // but is here as a safety measure. + if (score == null) + return; - var convertedFrame = (ReplayFrame)convertibleFrame; - convertedFrame.Time = frame.Time; + // rulesetInstance should be guaranteed to be in sync with the score via scoreLock. + Debug.Assert(rulesetInstance != null && rulesetInstance.RulesetInfo.Equals(score.ScoreInfo.Ruleset)); - replay.Frames.Add(convertedFrame); + foreach (var frame in data.Frames) + { + IConvertibleReplayFrame convertibleFrame = rulesetInstance.CreateConvertibleReplayFrame(); + convertibleFrame.FromLegacy(frame, beatmap.Value.Beatmap); + + var convertedFrame = (ReplayFrame)convertibleFrame; + convertedFrame.Time = frame.Time; + + score.Replay.Frames.Add(convertedFrame); + } } } @@ -247,10 +259,13 @@ namespace osu.Game.Screens.Play if (userId != targetUser.Id) return; - if (replay != null) + lock (scoreLock) { - replay.HasReceivedAllFrames = true; - replay = null; + if (score != null) + { + score.Replay.HasReceivedAllFrames = true; + score = null; + } } Schedule(clearDisplay); @@ -283,27 +298,28 @@ namespace osu.Game.Screens.Play return; } - replay ??= new Replay { HasReceivedAllFrames = false }; - - var scoreInfo = new ScoreInfo + lock (scoreLock) { - Beatmap = resolvedBeatmap, - User = targetUser, - Mods = state.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), - Ruleset = resolvedRuleset.RulesetInfo, - }; + score = new Score + { + ScoreInfo = new ScoreInfo + { + Beatmap = resolvedBeatmap, + User = targetUser, + Mods = state.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), + Ruleset = resolvedRuleset.RulesetInfo, + }, + Replay = new Replay { HasReceivedAllFrames = false }, + }; - ruleset.Value = resolvedRuleset.RulesetInfo; - rulesetInstance = resolvedRuleset; + ruleset.Value = resolvedRuleset.RulesetInfo; + rulesetInstance = resolvedRuleset; - beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); - watchButton.Enabled.Value = true; + beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); + watchButton.Enabled.Value = true; - this.Push(new SpectatorPlayerLoader(new Score - { - ScoreInfo = scoreInfo, - Replay = replay, - })); + this.Push(new SpectatorPlayerLoader(score)); + } } private void showBeatmapPanel(SpectatorState state)