mirror of
https://github.com/itsRevela/MinecraftConsoles.git
synced 2026-07-18 02:10:37 +00:00
perf(fourkit): server gc, fast-path event dispatch, tps probe
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,68 @@ using Minecraft.Server.FourKit.Plugin;
|
||||
/// </summary>
|
||||
public static class FourKit
|
||||
{
|
||||
private static readonly EventDispatcher _dispatcher = new();
|
||||
private static readonly EventDispatcher _dispatcher;
|
||||
private static readonly Dictionary<string, Player> _players = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly Dictionary<int, Player> _playersByEntityId = new();
|
||||
private static readonly object _playerLock = new();
|
||||
|
||||
// Must match HandlerKind in FourKitNatives.h.
|
||||
private enum HandlerKind
|
||||
{
|
||||
ChunkLoad = 0,
|
||||
ChunkUnload = 1,
|
||||
PlayerMove = 2,
|
||||
}
|
||||
|
||||
private static uint _handlerMask;
|
||||
private static readonly object _handlerMaskLock = new();
|
||||
|
||||
static FourKit()
|
||||
{
|
||||
_dispatcher = new EventDispatcher();
|
||||
_dispatcher.OnSubscriptionChanged = OnEventSubscribed;
|
||||
}
|
||||
|
||||
private static HandlerKind? MapEventTypeToHandlerKind(Type eventType)
|
||||
{
|
||||
if (eventType == typeof(Event.World.ChunkLoadEvent)) return HandlerKind.ChunkLoad;
|
||||
if (eventType == typeof(Event.World.ChunkUnloadEvent)) return HandlerKind.ChunkUnload;
|
||||
if (eventType == typeof(Event.Player.PlayerMoveEvent)) return HandlerKind.PlayerMove;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void OnEventSubscribed(Type eventType)
|
||||
{
|
||||
var kind = MapEventTypeToHandlerKind(eventType);
|
||||
if (kind == null) return;
|
||||
|
||||
lock (_handlerMaskLock)
|
||||
{
|
||||
uint newMask = _handlerMask | (1u << (int)kind.Value);
|
||||
if (newMask == _handlerMask) return;
|
||||
_handlerMask = newMask;
|
||||
NativeBridge.SetHandlerMask?.Invoke(_handlerMask);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ResyncHandlerMask()
|
||||
{
|
||||
lock (_handlerMaskLock)
|
||||
{
|
||||
NativeBridge.SetHandlerMask?.Invoke(_handlerMask);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current server tick count. Increments once per server tick
|
||||
/// (~20 per second under nominal load). Useful for measuring TPS by
|
||||
/// sampling the delta against wall clock time.
|
||||
/// </summary>
|
||||
public static int getServerTick()
|
||||
{
|
||||
return NativeBridge.GetServerTickCount?.Invoke() ?? 0;
|
||||
}
|
||||
|
||||
internal const int MAX_CHAT_LENGTH = 123;
|
||||
|
||||
private static readonly Dictionary<int, World> _worldsByDimId = new();
|
||||
|
||||
@@ -160,4 +160,32 @@ public static partial class FourKitHost
|
||||
ServerLog.Error("fourkit", $"SetWorldEntityCallbacks error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void SetSubscriptionCallbacks(IntPtr setHandlerMask)
|
||||
{
|
||||
try
|
||||
{
|
||||
NativeBridge.SetSubscriptionCallbacks(setHandlerMask);
|
||||
// Flush the mask accumulated during plugin onEnable.
|
||||
FourKit.ResyncHandlerMask();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerLog.Error("fourkit", $"SetSubscriptionCallbacks error: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void SetServerCallbacks(IntPtr getServerTickCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
NativeBridge.SetServerCallbacks(getServerTickCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ServerLog.Error("fourkit", $"SetServerCallbacks error: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@
|
||||
<AssemblyName>Minecraft.Server.FourKit</AssemblyName>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<BaseOutputPath>bin</BaseOutputPath>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -236,6 +236,15 @@ internal static class NativeBridge
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeSetBiomeIdDelegate(int dimId, int x, int z, int biomeId);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate void NativeSetHandlerMaskDelegate(uint mask);
|
||||
internal static NativeSetHandlerMaskDelegate? SetHandlerMask;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
internal delegate int NativeGetServerTickCountDelegate();
|
||||
internal static NativeGetServerTickCountDelegate? GetServerTickCount;
|
||||
|
||||
internal static NativeDamageDelegate? DamagePlayer;
|
||||
internal static NativeSetHealthDelegate? SetPlayerHealth;
|
||||
internal static NativeTeleportDelegate? TeleportPlayer;
|
||||
@@ -439,4 +448,14 @@ internal static class NativeBridge
|
||||
GetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeGetBiomeIdDelegate>(getBiomeId);
|
||||
SetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeSetBiomeIdDelegate>(setBiomeId);
|
||||
}
|
||||
|
||||
internal static void SetSubscriptionCallbacks(IntPtr setHandlerMask)
|
||||
{
|
||||
SetHandlerMask = Marshal.GetDelegateForFunctionPointer<NativeSetHandlerMaskDelegate>(setHandlerMask);
|
||||
}
|
||||
|
||||
internal static void SetServerCallbacks(IntPtr getServerTickCount)
|
||||
{
|
||||
GetServerTickCount = Marshal.GetDelegateForFunctionPointer<NativeGetServerTickCountDelegate>(getServerTickCount);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user