using System; using System.Collections.Generic; namespace Syntriax.Engine.Core; /// /// Represents a simple event with no parameters. /// Example usage: /// /// public class MyBehaviour : Behaviour, IUpdate /// { /// public readonly Event MyEvent = new(); /// /// public MyBehaviour() /// { /// MyEvent.AddListener(OnEventTriggered); /// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime); /// } /// /// public void Update() /// { /// MyEvent.Invoke(); /// } /// /// private void OnEventTriggered() /// { /// Console.WriteLine($"Event occurred!"); /// } /// /// private static void OnEventTriggeredOneTime() /// { /// Console.WriteLine($"Event called once!"); /// } /// } /// /// The output of the example code above would be: /// /// Event occurred! /// Event called once! /// Event occurred! /// Event occurred! /// Event occurred! /// ... /// /// public class Event { // 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. /// 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. /// 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) { 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) { 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 . /// public void Clear() { listeners.Clear(); onceListeners.Clear(); } /// /// Triggers the event. /// public void Invoke() { for (int i = listeners.Count - 1; i >= 0; i--) try { listeners[i].Callback.Invoke(); } catch (Exception exception) { 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].Callback.Invoke(); } catch (Exception exception) { 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); } } public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2) { listeners = new(initialListenerCount); onceListeners = new(initialOnceListenerCount); } public Event() { listeners = new(4); onceListeners = new(2); } public delegate void EventHandler(); private record struct ListenerData(EventHandler Callback, int Priority); } /// /// Represents an event with only sender parameters. /// Example usage: /// /// public class MyBehaviour : Behaviour, IUpdate /// { /// public readonly Event<MyBehaviour> MyEvent = new(); /// /// public MyBehaviour() /// { /// MyEvent.AddListener(OnEventTriggered); /// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime); /// } /// /// public void Update() /// { /// MyEvent.Invoke(this); /// } /// /// private void OnEventTriggered(MyBehaviour sender) /// { /// Console.WriteLine($"{sender.Id}'s event occurred!"); /// } /// /// private static void OnEventTriggeredOneTime(MyBehaviour sender) /// { /// Console.WriteLine($"{sender.Id}'s event called once!"); /// } /// } /// /// The output of the example code above would be: /// /// [Id]'s event occurred! /// [Id]'s event called once! /// [Id]'s event occurred! /// [Id]'s event occurred! /// [Id]'s event occurred! /// ... /// /// /// /// Sender type public class Event { // 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. /// 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. /// 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) { 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) { 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 . /// public void Clear() { listeners.Clear(); onceListeners.Clear(); } /// /// Triggers the event. /// /// 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); } catch (Exception exception) { 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].Callback.Invoke(sender); } catch (Exception exception) { 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); } } public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2) { listeners = new(initialListenerCount); onceListeners = new(initialOnceListenerCount); } public Event() { listeners = new(4); onceListeners = new(2); } public delegate void EventHandler(TSender sender); private record struct ListenerData(EventHandler Callback, int Priority); } /// /// Represents an event with sender and argument parameters. /// Example usage: /// /// public class MyBehaviour : Behaviour, IUpdate /// { /// public readonly Event<MyBehaviour, MyArguments> MyEvent = new(); /// /// private int myInt = 0; /// private bool myBool = false; /// /// public MyBehaviour() /// { /// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime); /// MyEvent.AddListener(OnEventTriggered); /// } /// /// public void Update() /// { /// MyEvent.Invoke(this, new MyArguments(myInt, myBool)); /// myInt++; /// myBool = !myBool; /// } /// /// private void OnEventTriggered(MyBehaviour sender, MyArguments args) /// { /// Console.WriteLine($"{sender.Id}'s event occurred with MyInt: {args.MyInt} and MyBool {args.MyBool}!"); /// } /// /// private static void OnEventTriggeredOneTime(MyBehaviour sender, MyArguments args) /// { /// Console.WriteLine($"{sender.Id}'s event called once with MyInt: {args.MyInt} and MyBool {args.MyBool}!"); /// } /// /// public readonly record struct MyArguments(int MyInt, bool MyBool); /// } /// /// The output of the example code above would be: /// /// [Id]'s event occurred with MyInt: 0 and MyBool False! /// [Id]'s event called once with MyInt: 0 and MyBool False! /// [Id]'s event occurred with MyInt: 1 and MyBool True! /// [Id]'s event occurred with MyInt: 2 and MyBool False! /// [Id]'s event occurred with MyInt: 3 and MyBool True! /// ... /// /// /// /// Sender type public class Event { // 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. /// 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. /// 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) { 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) { 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 . /// public void Clear() { listeners.Clear(); onceListeners.Clear(); } /// /// Triggers the event. /// /// The caller that's triggering this event. /// 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); } catch (Exception exception) { 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].Callback.Invoke(sender, args); } catch (Exception exception) { 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); } } public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2) { listeners = new(initialListenerCount); onceListeners = new(initialOnceListenerCount); } public Event() { listeners = new(4); onceListeners = new(2); } public delegate void EventHandler(TSender sender, TArguments args); private record struct ListenerData(EventHandler Callback, int Priority); }