From f4411e05a34dc696e2f15b72ca91dcaa39ce4abd Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 28 Jul 2026 20:00:55 +0200 Subject: [PATCH] fix: SpeedRunHud never detached from the autoload's signal every level load left another handler attached to a freed HUD. SpeedRunManager outlives the scene, so it kept calling them and each one wrote to a disposed Label - thousands of ObjectDisposedException per frame, growing with each load. Latent all along: StartTimer was only ever called from StartNewGame, so after a Continue the timer never ticked and the dead handlers never ran. Calling StartTimer on Continue (needed for splits and best times) made it visible. Also stop the timer when leaving to the menu - StopTimer existed and had no callers, so the run kept ticking in the main menu. --- scripts/UI/GameOverScreen.cs | 1 + scripts/UI/PauseMenu.cs | 2 ++ scripts/UI/SpeedRunHud.cs | 20 +++++++++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/UI/GameOverScreen.cs b/scripts/UI/GameOverScreen.cs index 7f82ba4..1a98455 100644 --- a/scripts/UI/GameOverScreen.cs +++ b/scripts/UI/GameOverScreen.cs @@ -39,6 +39,7 @@ public partial class GameOverScreen : Control private void OnMainMenuClicked() { GameStateStore.Instance?.ResetAll(); + SpeedRunManager.Instance?.StopTimer(); GetTree().ChangeSceneToPacked(MainMenuScene); } diff --git a/scripts/UI/PauseMenu.cs b/scripts/UI/PauseMenu.cs index 9457e06..e8bb83a 100644 --- a/scripts/UI/PauseMenu.cs +++ b/scripts/UI/PauseMenu.cs @@ -84,6 +84,8 @@ public partial class PauseMenu : Control { GameManager.ResumeGame(); GameStateStore.Instance?.ResetSession(); + // The run is over once you leave to the menu; otherwise the timer keeps ticking there. + SpeedRunManager.Instance?.StopTimer(); GetTree().ChangeSceneToPacked(MainMenuScene); } diff --git a/scripts/UI/SpeedRunHud.cs b/scripts/UI/SpeedRunHud.cs index fcb3ea2..0a695dd 100644 --- a/scripts/UI/SpeedRunHud.cs +++ b/scripts/UI/SpeedRunHud.cs @@ -1,5 +1,4 @@ using Godot; -using Mr.BrickAdventures; using Mr.BrickAdventures.Autoloads; namespace Mr.BrickAdventures.scripts.UI; @@ -11,11 +10,26 @@ public partial class SpeedRunHud : Control public override void _Ready() { - SpeedRunManager.Instance.TimeUpdated += OnTimerUpdated; + if (SpeedRunManager.Instance != null) + SpeedRunManager.Instance.TimeUpdated += OnTimerUpdated; + } + + /// + /// Detaches from the autoload's signal. Without this, every level load left another handler + /// attached to a freed HUD: SpeedRunManager outlives the scene, so it kept calling them and each + /// one touched a disposed Label. It stayed hidden only because the timer was never started on + /// Continue, so the signal never fired outside a fresh run. + /// + public override void _ExitTree() + { + if (SpeedRunManager.Instance != null) + SpeedRunManager.Instance.TimeUpdated -= OnTimerUpdated; } private void OnTimerUpdated(double totalTime, double levelTime) { + if (_timerLabel == null || !IsInstanceValid(_timerLabel)) return; + _timerLabel.Text = SpeedRunManager.FormatTime(totalTime); } -} \ No newline at end of file +}