From bcf37127a9d597d993067dd4115d1c441e3e7765 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Apr 2026 16:05:44 +0900 Subject: [PATCH] Safeguard rating distribution graph against null InputManager (#37304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I don't know how to reproduce this: ``` [runtime] 2026-04-15 05:43:26 [verbose]: 📺 OsuScreenStack#478(depth:4) exit from ScreenQueue#281 [runtime] 2026-04-15 05:43:26 [verbose]: 📺 OsuScreenStack#478(depth:4) resume to ScreenIntro#687 [runtime] 2026-04-15 05:43:26 [verbose]: 📺 BackgroundScreenStack#692(depth:1) exit from MatchmakingBackgroundScreen#393 [runtime] 2026-04-15 05:43:26 [verbose]: 📺 BackgroundScreenStack#692(depth:1) resume to BackgroundScreenDefault#210 [runtime] 2026-04-15 05:43:26 [verbose]: 📺 OsuScreenStack#478(depth:3) exit from ScreenIntro#687 [runtime] 2026-04-15 05:43:26 [verbose]: 📺 OsuScreenStack#478(depth:3) resume to MainMenu#505 [runtime] 2026-04-15 05:43:26 [verbose]: 🌅 Global background change queued [runtime] 2026-04-15 05:43:26 [verbose]: ButtonSystem's state changed from EnteringMode to TopLevel [runtime] 2026-04-15 05:43:26 [debug]: Focus changed from nothing to DialogOverlay. [runtime] 2026-04-15 05:43:26 [error]: An unhandled error has occurred. [runtime] 2026-04-15 05:43:26 [error]: System.NullReferenceException: Object reference not set to an instance of an object. [runtime] 2026-04-15 05:43:26 [error]: at osu.Game.Screens.OnlinePlay.Matchmaking.Queue.RatingDistributionGraph.get_TooltipContent() in /home/smgi/Repos/osu/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/RatingDistributionGraph.cs:line 434 [runtime] 2026-04-15 05:43:26 [error]: at osu.Framework.Graphics.Cursor.IHasCustomTooltip`1.osu.Framework.Graphics.Cursor.IHasCustomTooltip.get_TooltipContent() [runtime] 2026-04-15 05:43:26 [error]: at osu.Framework.Graphics.Cursor.TooltipContainer.hasValidTooltip(ITooltipContentProvider target) [runtime] 2026-04-15 05:43:26 [error]: at osu.Framework.Graphics.Cursor.TooltipContainer.Update() [runtime] 2026-04-15 05:43:26 [error]: at osu.Framework.Graphics.Drawable.UpdateSubTree() ``` As unplausible as it may seem, the only thing that can be null here is `GetContainingInputManager()`. The point of this exercise is to simply remove it by relying on `OnMouseMove` events instead. --- .../Queue/RatingDistributionGraph.cs | 128 +++++++++--------- 1 file changed, 61 insertions(+), 67 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/RatingDistributionGraph.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/RatingDistributionGraph.cs index 7457f4e3eb..aba47e9591 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/RatingDistributionGraph.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/RatingDistributionGraph.cs @@ -417,87 +417,72 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue protected override bool OnMouseMove(MouseMoveEvent e) { - var content = TooltipContent; + float minDistToCursor = float.MaxValue; + Vector2 closestPointToCursor = Vector2.Zero; + Color4 closestColourToCursor = Color4.White; + int closestRatingToCursor = 0; + string closestValueToCursor = string.Empty; - hoverMarker.Position = gridContainer.ToLocalSpace(content.Position); - hoverMarkerFill.Colour = content.Colour; - - return true; - } - - public ITooltip GetCustomTooltip() => new RatingDistributionGraphTooltip(); - - public RatingDistributionGraphTooltipData TooltipContent - { - get + if (userRating != null) { - Vector2 mousePos = GetContainingInputManager()!.CurrentState.Mouse.Position; + Vector2 userRatingPos1 = userRatingContainer.ToScreenSpace(pointOnGraph(userRating.Value, 0) * userRatingContainer.DrawSize); + Vector2 userRatingPos2 = userRatingContainer.ToScreenSpace(pointOnGraph(userRating.Value, yRange.max) * userRatingContainer.DrawSize); - float minDistToCursor = float.MaxValue; - Vector2 closestPointToCursor = Vector2.Zero; - Color4 closestColourToCursor = Color4.White; - int closestRatingToCursor = 0; - string closestValueToCursor = string.Empty; + minDistToCursor = Vector2.Distance(e.ScreenSpaceMousePosition, userRatingPos1); + closestPointToCursor = userRatingPos1; + closestColourToCursor = colours.Green; + closestRatingToCursor = userRating.Value; + closestValueToCursor = $"Your rating ({userRating})"; - if (userRating != null) + float d = Vector2.Distance(e.ScreenSpaceMousePosition, userRatingPos2); + + if (d < minDistToCursor) { - Vector2 userRatingPos1 = userRatingContainer.ToScreenSpace(pointOnGraph(userRating.Value, 0) * userRatingContainer.DrawSize); - Vector2 userRatingPos2 = userRatingContainer.ToScreenSpace(pointOnGraph(userRating.Value, yRange.max) * userRatingContainer.DrawSize); - - minDistToCursor = Vector2.Distance(mousePos, userRatingPos1); - closestPointToCursor = userRatingPos1; - closestColourToCursor = colours.Green; - closestRatingToCursor = userRating.Value; - closestValueToCursor = $"Your rating ({userRating})"; - - float d = Vector2.Distance(mousePos, userRatingPos2); - - if (d < minDistToCursor) - { - minDistToCursor = d; - closestPointToCursor = userRatingPos2; - } + minDistToCursor = d; + closestPointToCursor = userRatingPos2; } + } - for (int i = 0; i < data.Length; i++) + for (int i = 0; i < data.Length; i++) + { + Vector2 pos = barsContainer.ToScreenSpace(pointOnGraph(data[i].x, data[i].y) * barsContainer.DrawSize); + float d = Vector2.Distance(e.ScreenSpaceMousePosition, pos); + + if (d < minDistToCursor) { - Vector2 pos = barsContainer.ToScreenSpace(pointOnGraph(data[i].x, data[i].y) * barsContainer.DrawSize); - float d = Vector2.Distance(mousePos, pos); - - if (d < minDistToCursor) - { - minDistToCursor = d; - closestPointToCursor = pos; - closestColourToCursor = colourProvider.Colour0; - closestRatingToCursor = data[i].x; - closestValueToCursor = $"Players: {data[i].y}"; - } + minDistToCursor = d; + closestPointToCursor = pos; + closestColourToCursor = colourProvider.Colour0; + closestRatingToCursor = data[i].x; + closestValueToCursor = $"Players: {data[i].y}"; } + } - int currentCount = 0; - int totalCount = data.Sum(p => p.y); + int currentCount = 0; + int totalCount = data.Sum(p => p.y); - for (int i = 0; i < cumulativePath.Vertices.Count; i++) + for (int i = 0; i < cumulativePath.Vertices.Count; i++) + { + currentCount += data[i].y; + + Vector2 pos = cumulativePath.ToScreenSpace(cumulativePath.Vertices[i] + new Vector2(2)); + float d = Vector2.Distance(e.ScreenSpaceMousePosition, pos); + + if (d < minDistToCursor) { - currentCount += data[i].y; - - Vector2 pos = cumulativePath.ToScreenSpace(cumulativePath.Vertices[i] + new Vector2(2)); - float d = Vector2.Distance(mousePos, pos); - - if (d < minDistToCursor) - { - minDistToCursor = d; - closestPointToCursor = pos; - closestColourToCursor = colours.Yellow; - closestRatingToCursor = data[i].x; - closestValueToCursor = $"Cumulative: {(float)currentCount / totalCount:P1}"; - } + minDistToCursor = d; + closestPointToCursor = pos; + closestColourToCursor = colours.Yellow; + closestRatingToCursor = data[i].x; + closestValueToCursor = $"Cumulative: {(float)currentCount / totalCount:P1}"; } + } - if (float.IsNaN(minDistToCursor) || minDistToCursor == float.MaxValue) - return new RatingDistributionGraphTooltipData(); - - return new RatingDistributionGraphTooltipData + if (minDistToCursor == float.MaxValue) + TooltipContent = new RatingDistributionGraphTooltipData(); + else + { + TooltipContent = new RatingDistributionGraphTooltipData { Colour = closestColourToCursor, Position = closestPointToCursor, @@ -505,8 +490,17 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue Value = closestValueToCursor, }; } + + hoverMarker.Position = gridContainer.ToLocalSpace(TooltipContent.Position); + hoverMarkerFill.Colour = TooltipContent.Colour; + + return true; } + public ITooltip GetCustomTooltip() => new RatingDistributionGraphTooltip(); + + public RatingDistributionGraphTooltipData TooltipContent { get; private set; } = new RatingDistributionGraphTooltipData(); + /// /// A simple vertical line that always remains 1px in size. ///