From bc1c76d746d01281ecaa3c6937e1990fd1dcdcfc Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sun, 6 Jul 2025 22:22:57 +0300 Subject: [PATCH] feat: added priorities to events --- Engine.Core/Helpers/Event.cs | 186 +++++++++++++++++++++++++++++------ 1 file changed, 156 insertions(+), 30 deletions(-) diff --git a/Engine.Core/Helpers/Event.cs b/Engine.Core/Helpers/Event.cs index 7f818e5..c8221f4 100644 --- a/Engine.Core/Helpers/Event.cs +++ b/Engine.Core/Helpers/Event.cs @@ -45,30 +45,71 @@ namespace Syntriax.Engine.Core; /// public class Event { - private readonly List listeners = null!; - private readonly List onceListeners = null!; + // We use Ascending order because draw calls are running from last to first + private static readonly Comparer SortByAscendingPriority = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority)); + + private readonly List listeners = null!; + private readonly List onceListeners = null!; /// /// Subscribes the callback to be invoked whenever the event is triggered. /// /// The callback to be called when the event is triggered. - public void AddListener(EventHandler listener) => listeners.Add(listener); + /// Priority of the callback. + public void AddListener(EventHandler listener, int priority = 0) + { + ListenerData listenerData = new(listener, priority); + + int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority); + if (insertIndex < 0) + insertIndex = ~insertIndex; + + listeners.Insert(insertIndex, listenerData); + } + /// /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once. /// /// The callback to be called the next time the event is triggered. - public void AddOneTimeListener(EventHandler listener) => onceListeners.Add(listener); + /// Priority of the callback. + public void AddOneTimeListener(EventHandler listener, int priority = 0) + { + ListenerData listenerData = new(listener, priority); + + int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority); + if (insertIndex < 0) + insertIndex = ~insertIndex; + + onceListeners.Insert(insertIndex, listenerData); + } /// /// Unsubscribes the callback that was previously registered by . /// /// The callback that was previously registered by - public void RemoveListener(EventHandler listener) => listeners.Remove(listener); + public void RemoveListener(EventHandler listener) + { + for (int i = listeners.Count - 1; i >= 0; i--) + if (listeners[i].Callback == listener) + { + listeners.RemoveAt(i); + return; + } + } + /// /// Unsubscribes the callback that was previously registered by . /// /// The callback that was previously registered by - public void RemoveOneTimeListener(EventHandler listener) => onceListeners.Remove(listener); + public void RemoveOneTimeListener(EventHandler listener) + { + for (int i = 0; i < onceListeners.Count; i++) + if (onceListeners[i].Callback == listener) + { + onceListeners.RemoveAt(i); + return; + } + } /// /// Unsubscribes all listeners that was previously registered by either or . @@ -81,19 +122,19 @@ public class Event public void Invoke() { for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Invoke(); } + try { listeners[i].Callback.Invoke(); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}()"; + string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()"; Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); } for (int i = onceListeners.Count - 1; i >= 0; i--) { - try { onceListeners[i].Invoke(); } + try { onceListeners[i].Callback.Invoke(); } catch (Exception exception) { - string methodCallRepresentation = $"{onceListeners[i].Method.DeclaringType?.FullName}.{onceListeners[i].Method.Name}()"; + string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()"; Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); } onceListeners.RemoveAt(i); @@ -113,6 +154,7 @@ public class Event } public delegate void EventHandler(); + private record struct ListenerData(EventHandler Callback, int Priority); } /// @@ -159,30 +201,71 @@ public class Event /// Sender type public class Event { - private readonly List listeners = null!; - private readonly List onceListeners = null!; + // We use Ascending order because draw calls are running from last to first + private static readonly Comparer SortByAscendingPriority = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority)); + + private readonly List listeners = null!; + private readonly List onceListeners = null!; /// /// Subscribes the callback to be invoked whenever the event is triggered. /// /// The callback to be called when the event is triggered. - public void AddListener(EventHandler listener) => listeners.Add(listener); + /// Priority of the callback. + public void AddListener(EventHandler listener, int priority = 0) + { + ListenerData listenerData = new(listener, priority); + + int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority); + if (insertIndex < 0) + insertIndex = ~insertIndex; + + listeners.Insert(insertIndex, listenerData); + } + /// /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once. /// /// The callback to be called the next time the event is triggered. - public void AddOneTimeListener(EventHandler listener) => onceListeners.Add(listener); + /// Priority of the callback. + public void AddOneTimeListener(EventHandler listener, int priority = 0) + { + ListenerData listenerData = new(listener, priority); + + int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority); + if (insertIndex < 0) + insertIndex = ~insertIndex; + + onceListeners.Insert(insertIndex, listenerData); + } /// /// Unsubscribes the callback that was previously registered by . /// /// The callback that was previously registered by - public void RemoveListener(EventHandler listener) => listeners.Remove(listener); + public void RemoveListener(EventHandler listener) + { + for (int i = listeners.Count - 1; i >= 0; i--) + if (listeners[i].Callback == listener) + { + listeners.RemoveAt(i); + return; + } + } + /// /// Unsubscribes the callback that was previously registered by . /// /// The callback that was previously registered by - public void RemoveOneTimeListener(EventHandler listener) => onceListeners.Remove(listener); + public void RemoveOneTimeListener(EventHandler listener) + { + for (int i = 0; i < onceListeners.Count; i++) + if (onceListeners[i].Callback == listener) + { + onceListeners.RemoveAt(i); + return; + } + } /// /// Unsubscribes all listeners that was previously registered by either or . @@ -196,19 +279,19 @@ public class Event public void Invoke(TSender sender) { for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Invoke(sender); } + try { listeners[i].Callback.Invoke(sender); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}({sender})"; + string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})"; Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); } for (int i = onceListeners.Count - 1; i >= 0; i--) { - try { onceListeners[i].Invoke(sender); } + try { onceListeners[i].Callback.Invoke(sender); } catch (Exception exception) { - string methodCallRepresentation = $"{onceListeners[i].Method.DeclaringType?.FullName}.{onceListeners[i].Method.Name}({sender})"; + string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})"; Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); } onceListeners.RemoveAt(i); @@ -228,6 +311,7 @@ public class Event } public delegate void EventHandler(TSender sender); + private record struct ListenerData(EventHandler Callback, int Priority); } /// @@ -281,30 +365,71 @@ public class Event /// Sender type public class Event { - private readonly List listeners = null!; - private readonly List onceListeners = null!; + // We use Ascending order because draw calls are running from last to first + private static readonly Comparer SortByAscendingPriority = Comparer.Create((x, y) => x.Priority.CompareTo(y.Priority)); + + private readonly List listeners = null!; + private readonly List onceListeners = null!; /// /// Subscribes the callback to be invoked whenever the event is triggered. /// /// The callback to be called when the event is triggered. - public void AddListener(EventHandler listener) => listeners.Add(listener); + /// Priority of the callback. + public void AddListener(EventHandler listener, int priority = 0) + { + ListenerData listenerData = new(listener, priority); + + int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority); + if (insertIndex < 0) + insertIndex = ~insertIndex; + + listeners.Insert(insertIndex, listenerData); + } + /// /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once. /// /// The callback to be called the next time the event is triggered. - public void AddOneTimeListener(EventHandler listener) => onceListeners.Add(listener); + /// Priority of the callback. + public void AddOneTimeListener(EventHandler listener, int priority = 0) + { + ListenerData listenerData = new(listener, priority); + + int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority); + if (insertIndex < 0) + insertIndex = ~insertIndex; + + onceListeners.Insert(insertIndex, listenerData); + } /// /// Unsubscribes the callback that was previously registered by . /// /// The callback that was previously registered by - public void RemoveListener(EventHandler listener) => listeners.Remove(listener); + public void RemoveListener(EventHandler listener) + { + for (int i = listeners.Count - 1; i >= 0; i--) + if (listeners[i].Callback == listener) + { + listeners.RemoveAt(i); + return; + } + } + /// /// Unsubscribes the callback that was previously registered by . /// /// The callback that was previously registered by - public void RemoveOneTimeListener(EventHandler listener) => onceListeners.Remove(listener); + public void RemoveOneTimeListener(EventHandler listener) + { + for (int i = 0; i < onceListeners.Count; i++) + if (onceListeners[i].Callback == listener) + { + onceListeners.RemoveAt(i); + return; + } + } /// /// Unsubscribes all listeners that was previously registered by either or . @@ -319,19 +444,19 @@ public class Event public void Invoke(TSender sender, TArguments args) { for (int i = listeners.Count - 1; i >= 0; i--) - try { listeners[i].Invoke(sender, args); } + try { listeners[i].Callback.Invoke(sender, args); } catch (Exception exception) { - string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}({string.Join(", ", sender, args)})"; + string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({string.Join(", ", sender, args)})"; Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); } for (int i = onceListeners.Count - 1; i >= 0; i--) { - try { onceListeners[i].Invoke(sender, args); } + try { onceListeners[i].Callback.Invoke(sender, args); } catch (Exception exception) { - string methodCallRepresentation = $"{onceListeners[i].Method.DeclaringType?.FullName}.{onceListeners[i].Method.Name}({string.Join(", ", sender, args)})"; + string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({string.Join(", ", sender, args)})"; Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); } onceListeners.RemoveAt(i); @@ -351,4 +476,5 @@ public class Event } public delegate void EventHandler(TSender sender, TArguments args); + private record struct ListenerData(EventHandler Callback, int Priority); }