481 lines
18 KiB
C#
481 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Syntriax.Engine.Core;
|
|
|
|
/// <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 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}()";
|
|
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);
|
|
}
|
|
|
|
/// <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>
|
|
{
|
|
// 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 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})";
|
|
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);
|
|
}
|
|
|
|
/// <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>
|
|
{
|
|
// 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 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}({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);
|
|
}
|