From b55e5b8da41d8a876be04bb1181a2a83054e44b7 Mon Sep 17 00:00:00 2001 From: Xekep Date: Thu, 26 Feb 2026 20:29:53 +0300 Subject: [PATCH 1/2] Optimize handler dispatch with copy-on-write snapshots Avoid per-invocation list allocations and lock holding in HandlerList.Invoke by using a snapshot array refreshed on register/unregister. --- TShockAPI/HandlerList.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TShockAPI/HandlerList.cs b/TShockAPI/HandlerList.cs index a258d99be..983c2fc59 100644 --- a/TShockAPI/HandlerList.cs +++ b/TShockAPI/HandlerList.cs @@ -37,6 +37,7 @@ public class HandlerItem protected object HandlerLock = new object(); protected List Handlers { get; set; } + private HandlerItem[] HandlerSnapshot { get; set; } = Array.Empty(); public HandlerList() { Handlers = new List(); @@ -59,6 +60,7 @@ public void Register(HandlerItem obj) { Handlers.Add(obj); Handlers = Handlers.OrderBy(h => (int)h.Priority).ToList(); + HandlerSnapshot = Handlers.ToArray(); } } @@ -66,21 +68,21 @@ public void UnRegister(EventHandler handler) { lock (HandlerLock) { - Handlers.RemoveAll(h => h.Handler.Equals(handler)); + if (Handlers.RemoveAll(h => h.Handler.Equals(handler)) > 0) + { + HandlerSnapshot = Handlers.ToArray(); + } } } public void Invoke(object sender, T e) { - List handlers; - lock (HandlerLock) - { - //Copy the list for invoking as to not keep it locked during the invokes - handlers = new List(Handlers); - } + var handlers = HandlerSnapshot; + if (handlers.Length == 0) + return; var hargs = e as HandledEventArgs; - for (int i = 0; i < handlers.Count; i++) + for (int i = 0; i < handlers.Length; i++) { if (hargs == null || !hargs.Handled || (hargs.Handled && handlers[i].GetHandled)) { From 1736b23f2b222ee8e603027ad04d229b0de10753 Mon Sep 17 00:00:00 2001 From: Xekep Date: Fri, 27 Feb 2026 06:00:30 +0300 Subject: [PATCH 2/2] Fix handler snapshot publication visibility --- TShockAPI/HandlerList.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TShockAPI/HandlerList.cs b/TShockAPI/HandlerList.cs index 983c2fc59..3222414e3 100644 --- a/TShockAPI/HandlerList.cs +++ b/TShockAPI/HandlerList.cs @@ -20,6 +20,7 @@ You should have received a copy of the GNU General Public License using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading; namespace TShockAPI { @@ -37,7 +38,7 @@ public class HandlerItem protected object HandlerLock = new object(); protected List Handlers { get; set; } - private HandlerItem[] HandlerSnapshot { get; set; } = Array.Empty(); + private HandlerItem[] _handlerSnapshot = Array.Empty(); public HandlerList() { Handlers = new List(); @@ -60,7 +61,7 @@ public void Register(HandlerItem obj) { Handlers.Add(obj); Handlers = Handlers.OrderBy(h => (int)h.Priority).ToList(); - HandlerSnapshot = Handlers.ToArray(); + Volatile.Write(ref _handlerSnapshot, Handlers.ToArray()); } } @@ -70,14 +71,14 @@ public void UnRegister(EventHandler handler) { if (Handlers.RemoveAll(h => h.Handler.Equals(handler)) > 0) { - HandlerSnapshot = Handlers.ToArray(); + Volatile.Write(ref _handlerSnapshot, Handlers.ToArray()); } } } public void Invoke(object sender, T e) { - var handlers = HandlerSnapshot; + var handlers = Volatile.Read(ref _handlerSnapshot); if (handlers.Length == 0) return;