From 913af2a4a4cb01908e9d46507a42892e6adf3741 Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sat, 31 Jan 2026 00:49:00 +0300 Subject: [PATCH] fix: added dynamic index updates during event invocation so there are no missing/duplicate invocations This also makes the events very not ideal for multithreaded applications at the moment. --- Engine.Core/Helpers/Event.cs | 93 +++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/Engine.Core/Helpers/Event.cs b/Engine.Core/Helpers/Event.cs index c008c52..042a29b 100644 --- a/Engine.Core/Helpers/Event.cs +++ b/Engine.Core/Helpers/Event.cs @@ -58,6 +58,9 @@ public class Event public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; + private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen. + private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen. + private readonly List listeners = null!; private readonly List onceListeners = null!; @@ -74,6 +77,9 @@ public class Event if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentCallIndex) + currentCallIndex++; + listeners.Insert(insertIndex, listenerData); } @@ -90,6 +96,9 @@ public class Event if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentOnceCallIndex) + currentOnceCallIndex++; + onceListeners.Insert(insertIndex, listenerData); } @@ -103,6 +112,8 @@ public class Event if (listeners[i].Callback == listener) { listeners.RemoveAt(i); + if (i < currentCallIndex) + currentCallIndex--; return; } } @@ -117,6 +128,8 @@ public class Event if (onceListeners[i].Callback == listener) { onceListeners.RemoveAt(i); + if (i < currentOnceCallIndex) + currentOnceCallIndex--; return; } } @@ -131,23 +144,23 @@ public class Event /// public void Invoke() { - for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Callback.Invoke(); } + for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--) + try { listeners[currentCallIndex].Callback.Invoke(); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()"; - EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}()"; + EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation); } - for (int i = onceListeners.Count - 1; i >= 0; i--) + for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--) { - try { onceListeners[i].Callback.Invoke(); } + try { onceListeners[currentOnceCallIndex].Callback.Invoke(); } catch (Exception exception) { - string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()"; - EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}()"; + EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? this, Logger, exception, methodCallRepresentation); } - onceListeners.RemoveAt(i); + onceListeners.RemoveAt(currentOnceCallIndex); } } @@ -216,6 +229,9 @@ public class Event where TSender : class public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; + private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen. + private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen. + private readonly List listeners = null!; private readonly List onceListeners = null!; @@ -232,6 +248,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentCallIndex) + currentCallIndex++; + listeners.Insert(insertIndex, listenerData); } @@ -248,6 +267,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentOnceCallIndex) + currentOnceCallIndex++; + onceListeners.Insert(insertIndex, listenerData); } @@ -261,6 +283,8 @@ public class Event where TSender : class if (listeners[i].Callback == listener) { listeners.RemoveAt(i); + if (i < currentCallIndex) + currentCallIndex--; return; } } @@ -275,6 +299,8 @@ public class Event where TSender : class if (onceListeners[i].Callback == listener) { onceListeners.RemoveAt(i); + if (i < currentOnceCallIndex) + currentOnceCallIndex--; return; } } @@ -290,23 +316,23 @@ public class Event where TSender : class /// The caller that's triggering this event. public void Invoke(TSender sender) { - for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Callback.Invoke(sender); } + for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--) + try { listeners[currentCallIndex].Callback.Invoke(sender); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})"; - EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender})"; + EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); } - for (int i = onceListeners.Count - 1; i >= 0; i--) + for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--) { - try { onceListeners[i].Callback.Invoke(sender); } + try { onceListeners[currentOnceCallIndex].Callback.Invoke(sender); } catch (Exception exception) { - string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})"; - EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender})"; + EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); } - onceListeners.RemoveAt(i); + onceListeners.RemoveAt(currentOnceCallIndex); } } @@ -382,6 +408,9 @@ public class Event where TSender : class public ILogger Logger { get; set => field = value ?? ILogger.Shared; } = ILogger.Shared; + private int currentOnceCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen. + private int currentCallIndex = -1; // These are for the purpose of if a listener is added/removed during invocation the index is dynamically updated so no missing/duplicate invocations happen. + private readonly List listeners = null!; private readonly List onceListeners = null!; @@ -398,6 +427,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentCallIndex) + currentCallIndex++; + listeners.Insert(insertIndex, listenerData); } @@ -414,6 +446,9 @@ public class Event where TSender : class if (insertIndex < 0) insertIndex = ~insertIndex; + if (insertIndex < currentOnceCallIndex) + currentOnceCallIndex++; + onceListeners.Insert(insertIndex, listenerData); } @@ -427,6 +462,8 @@ public class Event where TSender : class if (listeners[i].Callback == listener) { listeners.RemoveAt(i); + if (i < currentCallIndex) + currentCallIndex--; return; } } @@ -441,6 +478,8 @@ public class Event where TSender : class if (onceListeners[i].Callback == listener) { onceListeners.RemoveAt(i); + if (i < currentOnceCallIndex) + currentOnceCallIndex--; return; } } @@ -457,23 +496,23 @@ public class Event where TSender : class /// The arguments provided for this event. public void Invoke(TSender sender, TArguments args) { - for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Callback.Invoke(sender, args); } + for (currentCallIndex = listeners.Count - 1; currentCallIndex >= 0; currentCallIndex--) + try { listeners[currentCallIndex].Callback.Invoke(sender, args); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})"; - EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{listeners[currentCallIndex].Callback.Method.DeclaringType?.FullName}.{listeners[currentCallIndex].Callback.Method.Name}({sender}, {args})"; + EventHelpers.LogInvocationException(listeners[currentCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); } - for (int i = onceListeners.Count - 1; i >= 0; i--) + for (currentOnceCallIndex = onceListeners.Count - 1; currentOnceCallIndex >= 0; currentOnceCallIndex--) { - try { onceListeners[i].Callback.Invoke(sender, args); } + try { onceListeners[currentOnceCallIndex].Callback.Invoke(sender, args); } catch (Exception exception) { - string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})"; - EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); + string methodCallRepresentation = $"{onceListeners[currentOnceCallIndex].Callback.Method.DeclaringType?.FullName}.{onceListeners[currentOnceCallIndex].Callback.Method.Name}({sender}, {args})"; + EventHelpers.LogInvocationException(onceListeners[currentOnceCallIndex].Callback.Target ?? sender, Logger, exception, methodCallRepresentation); } - onceListeners.RemoveAt(i); + onceListeners.RemoveAt(currentOnceCallIndex); } }