using System; using System.Collections.Generic; namespace AcDream.Core.Net.Messages; /// /// Central router for inbound 0xF7B0 GameEvent envelopes. /// /// /// Each handled gets a registered delegate. /// Unhandled types are counted for diagnostics so it's easy to see which /// events the server is actually emitting vs which ones we theoretically /// support — a useful number when iterating on chat / inventory / combat. /// /// /// /// Handlers are invoked synchronously on the thread that called /// — normally the render thread since /// 's decode path runs there in the current /// architecture. Handlers must not block. /// /// public sealed class GameEventDispatcher { public delegate void EventHandler(GameEventEnvelope envelope); private readonly Dictionary _handlers = new(); private readonly Dictionary _unhandledCounts = new(); /// /// Register a handler for a GameEvent sub-opcode. Replaces any /// existing handler for that opcode. /// public void Register(GameEventType type, EventHandler handler) { ArgumentNullException.ThrowIfNull(handler); _handlers[type] = handler; } /// /// Remove the registered handler for a sub-opcode. /// public void Unregister(GameEventType type) => _handlers.Remove(type); /// /// Route an envelope to its handler, or log as unhandled. Exceptions /// inside handlers are swallowed to keep the decode loop alive — a /// malformed event from the server should not crash the client. /// public void Dispatch(GameEventEnvelope envelope) { if (_handlers.TryGetValue(envelope.EventType, out var handler)) { try { handler(envelope); } catch (Exception ex) { // The decode thread must survive handler failures. Log via // Console so it surfaces in live-play logs without needing // a Serilog sink here. Console.Error.WriteLine( $"[GameEvent] handler for 0x{(uint)envelope.EventType:X4} threw: {ex.Message}"); } } else { _unhandledCounts.TryGetValue(envelope.EventType, out int n); _unhandledCounts[envelope.EventType] = n + 1; } } /// Number of events of the given type we've seen with no handler. public int GetUnhandledCount(GameEventType type) => _unhandledCounts.TryGetValue(type, out var n) ? n : 0; /// /// Snapshot of every event type we've seen without a handler, keyed /// by type → count. Useful for "which server events are firing that /// we don't parse?" diagnostic overlays. /// public IReadOnlyDictionary UnhandledCounts => _unhandledCounts; /// Reset the unhandled-counts bag (e.g. after a log-off). public void ResetUnhandledCounts() => _unhandledCounts.Clear(); /// How many distinct sub-opcodes have a handler registered. public int RegisteredHandlerCount => _handlers.Count; }