507 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			507 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
 | 
						|
using Engine.Core.Debug;
 | 
						|
 | 
						|
namespace Engine.Core;
 | 
						|
 | 
						|
// TODO!: every reverse loop has a chance to have more than 1 unsubscription,
 | 
						|
// for (int i = listeners.Count - 1; i >= 0; i--) 
 | 
						|
// can be replaced with 
 | 
						|
// for (int i = listeners.Count - 1; i >= 0; i = Math.Min(i - 1, listeners.Count - 1))
 | 
						|
// but this would causes possible double calls on already called callbacks, find a better method.
 | 
						|
 | 
						|
/// <summary>
 | 
						|
/// Represents a simple event with no parameters.
 | 
						|
/// <para>Example usage:</para> 
 | 
						|
/// <code>
 | 
						|
/// 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!");
 | 
						|
///     }
 | 
						|
/// }
 | 
						|
/// </code>
 | 
						|
/// The output of the example code above would be: 
 | 
						|
/// <code>
 | 
						|
/// Event occurred!
 | 
						|
/// Event called once!
 | 
						|
/// Event occurred!
 | 
						|
/// Event occurred!
 | 
						|
/// Event occurred!
 | 
						|
/// ...
 | 
						|
/// </code>
 | 
						|
/// </summary>
 | 
						|
public class Event
 | 
						|
{
 | 
						|
    // We use Ascending order because draw calls are running from last to first
 | 
						|
    private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
 | 
						|
 | 
						|
    private ILogger _logger = ILogger.Shared;
 | 
						|
    public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
 | 
						|
 | 
						|
    private readonly List<ListenerData> listeners = null!;
 | 
						|
    private readonly List<ListenerData> onceListeners = null!;
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Subscribes the callback to be invoked whenever the event is triggered.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback to be called when the event is triggered.</param>
 | 
						|
    /// <param name="priority">Priority of the callback.</param>
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback to be called the next time the event is triggered.</param>
 | 
						|
    /// <param name="priority">Priority of the callback.</param>
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
 | 
						|
    public void RemoveListener(EventHandler listener)
 | 
						|
    {
 | 
						|
        for (int i = listeners.Count - 1; i >= 0; i--)
 | 
						|
            if (listeners[i].Callback == listener)
 | 
						|
            {
 | 
						|
                listeners.RemoveAt(i);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
 | 
						|
    public void RemoveOneTimeListener(EventHandler listener)
 | 
						|
    {
 | 
						|
        for (int i = 0; i < onceListeners.Count; i++)
 | 
						|
            if (onceListeners[i].Callback == listener)
 | 
						|
            {
 | 
						|
                onceListeners.RemoveAt(i);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    public void Clear() { listeners.Clear(); onceListeners.Clear(); }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Triggers the event.
 | 
						|
    /// </summary>
 | 
						|
    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}()";
 | 
						|
                EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
 | 
						|
            }
 | 
						|
 | 
						|
        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}()";
 | 
						|
                EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
 | 
						|
            }
 | 
						|
            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);
 | 
						|
}
 | 
						|
 | 
						|
/// <summary>
 | 
						|
/// Represents an event with only sender parameters.
 | 
						|
/// <para>Example usage:</para> 
 | 
						|
/// <code>
 | 
						|
/// 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!");
 | 
						|
///     }
 | 
						|
/// }
 | 
						|
/// </code>
 | 
						|
/// The output of the example code above would be: 
 | 
						|
/// <code>
 | 
						|
/// [Id]'s event occurred!
 | 
						|
/// [Id]'s event called once!
 | 
						|
/// [Id]'s event occurred!
 | 
						|
/// [Id]'s event occurred!
 | 
						|
/// [Id]'s event occurred!
 | 
						|
/// ...
 | 
						|
/// </code>
 | 
						|
/// 
 | 
						|
/// </summary>
 | 
						|
/// <typeparam name="TSender">Sender type</typeparam>
 | 
						|
public class Event<TSender> where TSender : class
 | 
						|
{
 | 
						|
    // We use Ascending order because draw calls are running from last to first
 | 
						|
    private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
 | 
						|
 | 
						|
    private ILogger _logger = ILogger.Shared;
 | 
						|
    public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
 | 
						|
 | 
						|
    private readonly List<ListenerData> listeners = null!;
 | 
						|
    private readonly List<ListenerData> onceListeners = null!;
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Subscribes the callback to be invoked whenever the event is triggered.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback to be called when the event is triggered.</param>
 | 
						|
    /// <param name="priority">Priority of the callback.</param>
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback to be called the next time the event is triggered.</param>
 | 
						|
    /// <param name="priority">Priority of the callback.</param>
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
 | 
						|
    public void RemoveListener(EventHandler listener)
 | 
						|
    {
 | 
						|
        for (int i = listeners.Count - 1; i >= 0; i--)
 | 
						|
            if (listeners[i].Callback == listener)
 | 
						|
            {
 | 
						|
                listeners.RemoveAt(i);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
 | 
						|
    public void RemoveOneTimeListener(EventHandler listener)
 | 
						|
    {
 | 
						|
        for (int i = 0; i < onceListeners.Count; i++)
 | 
						|
            if (onceListeners[i].Callback == listener)
 | 
						|
            {
 | 
						|
                onceListeners.RemoveAt(i);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    public void Clear() { listeners.Clear(); onceListeners.Clear(); }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Triggers the event.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="sender">The caller that's triggering this event.</param>
 | 
						|
    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})";
 | 
						|
                EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
 | 
						|
            }
 | 
						|
 | 
						|
        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})";
 | 
						|
                EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
 | 
						|
            }
 | 
						|
            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);
 | 
						|
}
 | 
						|
 | 
						|
/// <summary>
 | 
						|
/// Represents an event with sender and argument parameters.
 | 
						|
/// <para>Example usage:</para> 
 | 
						|
/// <code>
 | 
						|
/// 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);
 | 
						|
/// }
 | 
						|
/// </code>
 | 
						|
/// The output of the example code above would be: 
 | 
						|
/// <code>
 | 
						|
/// [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!
 | 
						|
/// ...
 | 
						|
/// </code>
 | 
						|
/// 
 | 
						|
/// </summary>
 | 
						|
/// <typeparam name="TSender">Sender type</typeparam>
 | 
						|
public class Event<TSender, TArguments> where TSender : class
 | 
						|
{
 | 
						|
    // We use Ascending order because draw calls are running from last to first
 | 
						|
    private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
 | 
						|
 | 
						|
    private ILogger _logger = ILogger.Shared;
 | 
						|
    public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
 | 
						|
 | 
						|
    private readonly List<ListenerData> listeners = null!;
 | 
						|
    private readonly List<ListenerData> onceListeners = null!;
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Subscribes the callback to be invoked whenever the event is triggered.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback to be called when the event is triggered.</param>
 | 
						|
    /// <param name="priority">Priority of the callback.</param>
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback to be called the next time the event is triggered.</param>
 | 
						|
    /// <param name="priority">Priority of the callback.</param>
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
 | 
						|
    public void RemoveListener(EventHandler listener)
 | 
						|
    {
 | 
						|
        for (int i = listeners.Count - 1; i >= 0; i--)
 | 
						|
            if (listeners[i].Callback == listener)
 | 
						|
            {
 | 
						|
                listeners.RemoveAt(i);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
 | 
						|
    public void RemoveOneTimeListener(EventHandler listener)
 | 
						|
    {
 | 
						|
        for (int i = 0; i < onceListeners.Count; i++)
 | 
						|
            if (onceListeners[i].Callback == listener)
 | 
						|
            {
 | 
						|
                onceListeners.RemoveAt(i);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
 | 
						|
    /// </summary>
 | 
						|
    public void Clear() { listeners.Clear(); onceListeners.Clear(); }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Triggers the event.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="sender">The caller that's triggering this event.</param>
 | 
						|
    /// <param name="args">The arguments provided for this event.</param>
 | 
						|
    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}({sender}, {args})";
 | 
						|
                EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
 | 
						|
            }
 | 
						|
 | 
						|
        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}({sender}, {args})";
 | 
						|
                EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
 | 
						|
            }
 | 
						|
            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);
 | 
						|
}
 | 
						|
 | 
						|
internal static class EventHelpers
 | 
						|
{
 | 
						|
    public static void LogInvocationException(object sender, ILogger logger, Exception exception, string methodCallRepresentation)
 | 
						|
    {
 | 
						|
        logger.LogException(sender, exception);
 | 
						|
        logger.LogError(sender, $"Unexpected exception on invocation of method {methodCallRepresentation}");
 | 
						|
    }
 | 
						|
}
 |