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;
}