Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add priority for event handlers to Exiled Events #423

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,7 @@ _site/
JSON/

# Mac DS_Store
.DS_Store
.DS_Store

# Code Rush
/EXILED/.cr/*
129 changes: 107 additions & 22 deletions EXILED/Exiled.Events/Features/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Exiled.Events.Features
using Exiled.API.Features;
using Exiled.Events.EventArgs.Interfaces;
using MEC;
using PluginAPI.Roles;

/// <summary>
/// The custom <see cref="EventHandler"/> delegate, with empty parameters.
Expand All @@ -31,8 +32,20 @@ namespace Exiled.Events.Features
/// </summary>
public class Event : IExiledEvent
{
private record Registration(CustomEventHandler handler, int priority);

private record AsyncRegistration(CustomAsyncEventHandler handler, int priority);

private static readonly List<Event> EventsValue = new();

private static readonly IComparer<Registration> RegisterComparable = Comparer<Registration>.Create((x, y) => y.priority - x.priority);

private static readonly IComparer<AsyncRegistration> AsyncRegisterComparable = Comparer<AsyncRegistration>.Create((x, y) => y.priority - x.priority);

private readonly List<Registration> innerEvent = new();

private readonly List<AsyncRegistration> innerAsyncEvent = new();

private bool patched;

/// <summary>
Expand All @@ -43,10 +56,6 @@ public Event()
EventsValue.Add(this);
}

private event CustomEventHandler InnerEvent;

private event CustomAsyncEventHandler InnerAsyncEvent;

/// <summary>
/// Gets a <see cref="IReadOnlyList{T}"/> of <see cref="Event{T}"/> which contains all the <see cref="Event{T}"/> instances.
/// </summary>
Expand Down Expand Up @@ -105,6 +114,14 @@ public Event()
/// </summary>
/// <param name="handler">The handler to add.</param>
public void Subscribe(CustomEventHandler handler)
=> Subscribe(handler, 0);

/// <summary>
/// Subscribes a target <see cref="CustomEventHandler"/> to the inner event if the conditional is true.
/// </summary>
/// <param name="handler">The handler to add.</param>
/// <param name="priority">The highest priority is the first called, the lowest the last.</param>
public void Subscribe(CustomEventHandler handler, int priority)
{
Log.Assert(Events.Instance is not null, $"{nameof(Events.Instance)} is null, please ensure you have exiled_events enabled!");

Expand All @@ -114,14 +131,33 @@ public void Subscribe(CustomEventHandler handler)
patched = true;
}

InnerEvent += handler;
Registration registration = new Registration(handler, priority);
int index = innerEvent.BinarySearch(registration, RegisterComparable);
if (index < 0)
{
innerEvent.Insert(~index, registration);
}
else
{
while (index < innerEvent.Count && innerEvent[index].priority == priority)
index++;
innerEvent.Insert(index, registration);
}
}

/// <summary>
/// Subscribes a target <see cref="CustomAsyncEventHandler"/> to the inner event if the conditional is true.
/// </summary>
/// <param name="handler">The handler to add.</param>
public void Subscribe(CustomAsyncEventHandler handler)
=> Subscribe(handler, 0);

/// <summary>
/// Subscribes a target <see cref="CustomAsyncEventHandler"/> to the inner event if the conditional is true.
/// </summary>
/// <param name="handler">The handler to add.</param>
/// <param name="priority">The highest priority is the first called, the lowest the last.</param>
public void Subscribe(CustomAsyncEventHandler handler, int priority)
{
Log.Assert(Events.Instance is not null, $"{nameof(Events.Instance)} is null, please ensure you have exiled_events enabled!");

Expand All @@ -131,7 +167,18 @@ public void Subscribe(CustomAsyncEventHandler handler)
patched = true;
}

InnerAsyncEvent += handler;
AsyncRegistration registration = new AsyncRegistration(handler, 0);
int index = innerAsyncEvent.BinarySearch(registration, AsyncRegisterComparable);
if (index < 0)
{
innerAsyncEvent.Insert(~index, registration);
}
else
{
while (index < innerAsyncEvent.Count && innerAsyncEvent[index].priority == priority)
index++;
innerAsyncEvent.Insert(index, registration);
}
}

/// <summary>
Expand All @@ -140,7 +187,9 @@ public void Subscribe(CustomAsyncEventHandler handler)
/// <param name="handler">The handler to add.</param>
public void Unsubscribe(CustomEventHandler handler)
{
InnerEvent -= handler;
int index = innerEvent.FindIndex(p => p.handler == handler);
if (index != -1)
innerEvent.RemoveAt(index);
}

/// <summary>
Expand All @@ -149,52 +198,88 @@ public void Unsubscribe(CustomEventHandler handler)
/// <param name="handler">The handler to add.</param>
public void Unsubscribe(CustomAsyncEventHandler handler)
{
InnerAsyncEvent -= handler;
int index = innerAsyncEvent.FindIndex(p => p.handler == handler);
if (index != -1)
innerAsyncEvent.RemoveAt(index);
}

/// <summary>
/// Executes all <see cref="CustomEventHandler"/> listeners safely.
/// </summary>
public void InvokeSafely()
{
InvokeNormal();
InvokeAsync();
BlendedInvoke();
}

/// <inheritdoc cref="InvokeSafely"/>
internal void InvokeNormal()
internal void BlendedInvoke()
{
if (InnerEvent is null)
return;
Registration[] innerEvent = this.innerEvent.ToArray();
AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray();
int count = innerEvent.Length + innerAsyncEvent.Length;
int eventIndex = 0, asyncEventIndex = 0;

for (int i = 0; i < count; i++)
{
if (eventIndex < innerEvent.Length && (asyncEventIndex >= innerAsyncEvent.Length || innerEvent[eventIndex].priority >= innerAsyncEvent[asyncEventIndex].priority))
{
try
{
innerEvent[eventIndex].handler();
}
catch (Exception ex)
{
Log.Error($"Method \"{innerEvent[eventIndex].handler.Method.Name}\" of the class \"{innerEvent[eventIndex].handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}");
}

eventIndex++;
}
else
{
try
{
Timing.RunCoroutine(innerAsyncEvent[asyncEventIndex].handler());
}
catch (Exception ex)
{
Log.Error($"Method \"{innerAsyncEvent[asyncEventIndex].handler.Method.Name}\" of the class \"{innerAsyncEvent[asyncEventIndex].handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}");
}

asyncEventIndex++;
}
}
}

foreach (CustomEventHandler handler in InnerEvent.GetInvocationList().Cast<CustomEventHandler>())
/// <inheritdoc cref="InvokeSafely"/>
internal void InvokeNormal()
{
Registration[] innerEvent = this.innerEvent.ToArray();
foreach (Registration registration in innerEvent)
{
try
{
handler();
registration.handler();
}
catch (Exception ex)
{
Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}");
Log.Error($"Method \"{registration.handler.Method.Name}\" of the class \"{registration.handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}");
}
}
}

/// <inheritdoc cref="InvokeSafely"/>
internal void InvokeAsync()
{
if (InnerAsyncEvent is null)
return;

foreach (CustomAsyncEventHandler handler in InnerAsyncEvent.GetInvocationList().Cast<CustomAsyncEventHandler>())
AsyncRegistration[] innerAsyncEvent = this.innerAsyncEvent.ToArray();
foreach (AsyncRegistration registration in innerAsyncEvent)
{
try
{
Timing.RunCoroutine(handler());
Timing.RunCoroutine(registration.handler());
}
catch (Exception ex)
{
Log.Error($"Method \"{handler.Method.Name}\" of the class \"{handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}");
Log.Error($"Method \"{registration.handler.Method.Name}\" of the class \"{registration.handler.Method.ReflectedType.FullName}\" caused an exception when handling the event \"{GetType().FullName}\"\n{ex}");
}
}
}
Expand Down
Loading