perf(fourkit): server gc, fast-path event dispatch, tps probe

This commit is contained in:
itsRevela
2026-04-24 21:31:06 -05:00
parent 6db68bda06
commit b64b1f17db
11 changed files with 486 additions and 57 deletions
+61 -39
View File
@@ -22,60 +22,80 @@ internal sealed class EventDispatcher
public int CompareTo(RegisteredHandler other) => Priority.CompareTo(other.Priority);
}
private readonly Dictionary<Type, List<RegisteredHandler>> _handlers = new();
private readonly object _lock = new();
// Snapshot-on-write: writers swap _handlers atomically; Fire reads it lock-free.
private volatile Dictionary<Type, RegisteredHandler[]> _handlers = new();
private readonly object _writeLock = new();
// Fired when an event type gains its first handler.
internal Action<Type>? OnSubscriptionChanged;
public void Register(Listener listener)
{
var methods = listener.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
lock (_lock)
List<(Type eventType, RegisteredHandler handler)>? pending = null;
foreach (var method in methods)
{
foreach (var method in methods)
var attr = method.GetCustomAttribute<Event.EventHandlerAttribute>();
if (attr == null)
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
var attr = method.GetCustomAttribute<Event.EventHandlerAttribute>();
if (attr == null)
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} must have exactly 1 parameter, skipping.");
continue;
}
var eventType = parameters[0].ParameterType;
if (!typeof(Event.Event).IsAssignableFrom(eventType))
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} parameter must extend Event, skipping.");
continue;
}
if (!_handlers.TryGetValue(eventType, out var list))
{
list = new List<RegisteredHandler>();
_handlers[eventType] = list;
}
list.Add(new RegisteredHandler(listener, method, attr.Priority, attr.IgnoreCancelled));
_handlers[eventType] = list.OrderBy(h => h.Priority).ToList();
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} must have exactly 1 parameter, skipping.");
continue;
}
var eventType = parameters[0].ParameterType;
if (!typeof(Event.Event).IsAssignableFrom(eventType))
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} parameter must extend Event, skipping.");
continue;
}
pending ??= new List<(Type, RegisteredHandler)>();
pending.Add((eventType, new RegisteredHandler(listener, method, attr.Priority, attr.IgnoreCancelled)));
}
if (pending == null) return;
HashSet<Type> newlySubscribed = new();
lock (_writeLock)
{
var newDict = new Dictionary<Type, RegisteredHandler[]>(_handlers);
foreach (var (eventType, handler) in pending)
{
bool hadAny = newDict.TryGetValue(eventType, out var existing);
existing ??= Array.Empty<RegisteredHandler>();
// OrderBy is stable; Array.Sort is not.
var combined = existing.Append(handler).OrderBy(h => h.Priority).ToArray();
newDict[eventType] = combined;
if (!hadAny) newlySubscribed.Add(eventType);
}
_handlers = newDict;
}
if (OnSubscriptionChanged != null)
{
foreach (var t in newlySubscribed)
OnSubscriptionChanged(t);
}
}
public void Fire(Event.Event evt)
{
List<RegisteredHandler>? handlers;
lock (_lock)
{
if (!_handlers.TryGetValue(evt.GetType(), out handlers))
return;
handlers = new List<RegisteredHandler>(handlers);
}
var snapshot = _handlers;
if (!snapshot.TryGetValue(evt.GetType(), out var handlers))
return;
var cancellable = evt as Cancellable;
foreach (var handler in handlers)
for (int i = 0; i < handlers.Length; i++)
{
ref readonly var handler = ref handlers[i];
if (handler.IgnoreCancelled && cancellable != null && cancellable.isCancelled())
continue;
@@ -89,4 +109,6 @@ internal sealed class EventDispatcher
}
}
}
internal bool IsSubscribed(Type eventType) => _handlers.ContainsKey(eventType);
}