using System; using System.Collections.Generic; namespace AcDream.UI.Abstractions; /// /// Real implementation — single-handler-per-type /// dispatch keyed by typeof(T). Replaces /// in live sessions; persists for tests and /// non-live UI scenarios where no command flow is wanted. /// /// /// Threading. Both and /// run on the render thread today (panels render on the render thread, and /// host wiring happens at startup). The internal handler dictionary is /// not synchronized — register all handlers during host setup before the /// panel host starts rendering. /// /// /// /// Phase I.3 of the chat/UI consolidation plan /// (~/.claude/plans/ticklish-conjuring-cake.md): primary client of /// the bus is the handler wired by GameWindow /// against WorldSession.SendTalk/SendTell/SendChannel + the local /// ChatLog echo. /// /// public sealed class LiveCommandBus : ICommandBus { private readonly Dictionary _handlers = new(); /// /// Register a single handler for commands of type . /// Throws if a handler is already /// registered for this type — single-handler-per-type is intentional so /// command routing is unambiguous. /// public void Register(Action handler) where T : notnull { ArgumentNullException.ThrowIfNull(handler); if (_handlers.ContainsKey(typeof(T))) throw new InvalidOperationException( $"A handler for command type {typeof(T).FullName} is already registered."); _handlers[typeof(T)] = handler; } /// public void Publish(T command) where T : notnull { ArgumentNullException.ThrowIfNull(command); if (_handlers.TryGetValue(typeof(T), out var handler)) { ((Action)handler).Invoke(command); } else { // Soft-warn: command published with no registered handler. // Don't throw — the host may publish optional commands a non- // live build doesn't wire (e.g. inventory pre-Phase I.7). Console.WriteLine( $"[LiveCommandBus] no handler registered for {typeof(T).FullName}; dropping."); } } }