From aed74984c6cd33a64660524b6fcfbe2ee782b775 Mon Sep 17 00:00:00 2001 From: erik Date: Sat, 28 Feb 2026 12:55:58 +0000 Subject: [PATCH] Fix hot reload: prevent duplicate event handlers and timer leaks Unsubscribe all event handlers and stop/dispose timers at the start of Startup() before re-creating objects. On first load the -= calls are no-ops; on hot reload they remove stale handlers that would otherwise compound with each reload. Also adds LoginComplete unsubscription to Shutdown() for completeness. Co-Authored-By: Claude Opus 4.6 --- MosswartMassacre/PluginCore.cs | 37 ++++++++++++++++++ .../bin/Release/MosswartMassacre.dll | Bin 1676288 -> 1676800 bytes 2 files changed, 37 insertions(+) diff --git a/MosswartMassacre/PluginCore.cs b/MosswartMassacre/PluginCore.cs index 8c03124..bbd640a 100644 --- a/MosswartMassacre/PluginCore.cs +++ b/MosswartMassacre/PluginCore.cs @@ -163,6 +163,42 @@ namespace MosswartMassacre var isCharacterLoaded = CoreManager.Current.CharacterFilter.LoginStatus == 3; var needsHotReload = IsHotReload || isCharacterLoaded; + // Clean up old event subscriptions to prevent duplicates on hot reload. + // C# -= with a non-subscribed handler is a no-op, so safe on first load. + if (_chatEventRouter != null) + CoreManager.Current.ChatBoxMessage -= new EventHandler(_chatEventRouter.OnChatText); + CoreManager.Current.ChatBoxMessage -= new EventHandler(ChatEventRouter.AllChatText); + CoreManager.Current.CommandLineText -= OnChatCommand; + CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete; + CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath; + CoreManager.Current.WorldFilter.CreateObject -= OnSpawn; + CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected; + CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn; + if (_inventoryMonitor != null) + { + CoreManager.Current.WorldFilter.CreateObject -= _inventoryMonitor.OnInventoryCreate; + CoreManager.Current.WorldFilter.ReleaseObject -= _inventoryMonitor.OnInventoryRelease; + CoreManager.Current.WorldFilter.ChangeObject -= _inventoryMonitor.OnInventoryChange; + } + if (_gameEventRouter != null) + CoreManager.Current.EchoFilter.ServerDispatch -= _gameEventRouter.OnServerDispatch; + WebSocket.OnServerCommand -= HandleServerCommand; + + // Stop old timers before recreating (prevents timer leaks on hot reload) + _killTracker?.Stop(); + if (vitalsTimer != null) + { + vitalsTimer.Stop(); + vitalsTimer.Dispose(); + vitalsTimer = null; + } + if (commandTimer != null) + { + commandTimer.Stop(); + commandTimer.Dispose(); + commandTimer = null; + } + // Initialize kill tracker (owns the 1-sec stats timer) _killTracker = new KillTracker( this, @@ -280,6 +316,7 @@ namespace MosswartMassacre CoreManager.Current.ChatBoxMessage -= new EventHandler(_chatEventRouter.OnChatText); CoreManager.Current.CommandLineText -= OnChatCommand; CoreManager.Current.ChatBoxMessage -= new EventHandler(ChatEventRouter.AllChatText); + CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete; CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath; CoreManager.Current.WorldFilter.CreateObject -= OnSpawn; CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected; diff --git a/MosswartMassacre/bin/Release/MosswartMassacre.dll b/MosswartMassacre/bin/Release/MosswartMassacre.dll index 6503adcb5f94c30cde7b4fd9cfdd9a212f17044e..5e74667057552ea9e6b44aa31ec001f216c0c4f9 100644 GIT binary patch delta 9479 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 delta 9114 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