using System; using System.Collections.Generic; using System.Linq; using System.Timers; public static class EventBus { // Cho phép null ở value để có thể "clear" slot private static readonly Dictionary globalListeners = new Dictionary(); private static readonly Dictionary globalClassListeners = new Dictionary(); private static readonly Dictionary> channelListeners = new Dictionary>(); private static readonly Dictionary> channelClassListeners = new Dictionary>(); // Cho phép null khi chưa bật auto-cleanup private static Timer? cleanupTimer; public static bool DebugEnabled = false; private static void DebugLog(string message) { if (DebugEnabled) Console.WriteLine($"[EventBus] {message}"); } // ===== GLOBAL STRUCT EVENTS ===== public static void Subscribe(Action listener) where T : struct { var type = typeof(T); if (!globalListeners.ContainsKey(type)) globalListeners[type] = null; var current = globalListeners[type] as Action; current += listener; globalListeners[type] = current; } public static void Unsubscribe(Action listener) where T : struct { var type = typeof(T); if (!globalListeners.ContainsKey(type)) return; var current = globalListeners[type] as Action; current -= listener; if (current == null) globalListeners.Remove(type); else globalListeners[type] = current; } public static void Publish(T eventData) where T : struct { var type = typeof(T); if (globalListeners.TryGetValue(type, out var del)) { var action = del as Action; if (action != null) { DebugLog($"Publish Global Struct Event: {type.Name}"); action(eventData); } } } // ===== GLOBAL CLASS EVENTS ===== public static void SubscribeClass(Action listener) where T : class { var type = typeof(T); if (!globalClassListeners.ContainsKey(type)) globalClassListeners[type] = null; var current = globalClassListeners[type] as Action; current += listener; globalClassListeners[type] = current; } public static void UnsubscribeClass(Action listener) where T : class { var type = typeof(T); if (!globalClassListeners.ContainsKey(type)) return; var current = globalClassListeners[type] as Action; current -= listener; if (current == null) globalClassListeners.Remove(type); else globalClassListeners[type] = current; } public static void PublishClass(T eventData) where T : class { var type = typeof(T); if (globalClassListeners.TryGetValue(type, out var del)) { var action = del as Action; if (action != null) { DebugLog($"Publish Global Class Event: {type.Name}"); action(eventData); } } } // ===== CHANNEL STRUCT EVENTS ===== public static void SubscribeChannel(int channelId, Action listener) where T : struct { var type = typeof(T); if (!channelListeners.ContainsKey(channelId)) channelListeners[channelId] = new Dictionary(); if (!channelListeners[channelId].ContainsKey(type)) channelListeners[channelId][type] = null; var current = channelListeners[channelId][type] as Action; current += listener; channelListeners[channelId][type] = current; } public static void UnsubscribeChannel(int channelId, Action listener) where T : struct { var type = typeof(T); if (!channelListeners.ContainsKey(channelId) || !channelListeners[channelId].ContainsKey(type)) return; var current = channelListeners[channelId][type] as Action; current -= listener; if (current == null) { channelListeners[channelId].Remove(type); if (channelListeners[channelId].Count == 0) channelListeners.Remove(channelId); } else { channelListeners[channelId][type] = current; } } public static void PublishChannel(int channelId, T eventData) where T : struct { var type = typeof(T); if (channelListeners.TryGetValue(channelId, out var dict)) { Delegate? del; if (dict.TryGetValue(type, out del)) { var action = del as Action; if (action != null) { DebugLog($"Publish Channel Struct Event: {type.Name} to channel '{channelId}'"); action(eventData); } } } } // ===== CHANNEL CLASS EVENTS ===== public static void SubscribeChannelClass(int channelId, Action listener) where T : class { var type = typeof(T); if (!channelClassListeners.ContainsKey(channelId)) channelClassListeners[channelId] = new Dictionary(); if (!channelClassListeners[channelId].ContainsKey(type)) channelClassListeners[channelId][type] = null; var current = channelClassListeners[channelId][type] as Action; current += listener; channelClassListeners[channelId][type] = current; } public static void UnsubscribeChannelClass(int channelId, Action listener) where T : class { var type = typeof(T); if (!channelClassListeners.ContainsKey(channelId) || !channelClassListeners[channelId].ContainsKey(type)) return; var current = channelClassListeners[channelId][type] as Action; current -= listener; if (current == null) { channelClassListeners[channelId].Remove(type); if (channelClassListeners[channelId].Count == 0) channelClassListeners.Remove(channelId); } else { channelClassListeners[channelId][type] = current; } } public static void PublishChannelClass(int channelId, T eventData) where T : class { var type = typeof(T); if (channelClassListeners.TryGetValue(channelId, out var dict)) { Delegate? del; if (dict.TryGetValue(type, out del)) { var action = del as Action; if (action != null) { DebugLog($"Publish Channel Class Event: {type.Name} to channel '{channelId}'"); action(eventData); } } } } // ===== ONE-TIME SUBSCRIBE ===== public static void SubscribeOnce(Action listener) where T : struct { Action? wrapper = null; wrapper = data => { listener(data); Unsubscribe(wrapper); }; Subscribe(wrapper); } public static void SubscribeOnceClass(Action listener) where T : class { Action? wrapper = null; wrapper = data => { listener(data); UnsubscribeClass(wrapper); }; SubscribeClass(wrapper); } public static void SubscribeOnceChannel(int channelId, Action listener) where T : struct { Action? wrapper = null; wrapper = data => { listener(data); UnsubscribeChannel(channelId, wrapper); }; SubscribeChannel(channelId, wrapper); } public static void SubscribeOnceChannelClass(int channelId, Action listener) where T : class { Action? wrapper = null; wrapper = data => { listener(data); UnsubscribeChannelClass(channelId, wrapper); }; SubscribeChannelClass(channelId, wrapper); } // ===== CHANNEL UTILITIES ===== public static bool ChannelExists(int channelId) { return channelListeners.ContainsKey(channelId) || channelClassListeners.ContainsKey(channelId); } public static void EnableAutoCleanup(float delaySeconds = 5f) { cleanupTimer = new Timer(delaySeconds * 1000); cleanupTimer.Elapsed += (sender, e) => { var emptyStructChannels = channelListeners .Where(pair => pair.Value.Values.All(d => d == null)) .Select(pair => pair.Key) .ToList(); foreach (var id in emptyStructChannels) { channelListeners.Remove(id); DebugLog($"Removed empty struct channel: {id}"); } var emptyClassChannels = channelClassListeners .Where(pair => pair.Value.Values.All(d => d == null)) .Select(pair => pair.Key) .ToList(); foreach (var id in emptyClassChannels) { channelClassListeners.Remove(id); DebugLog($"Removed empty class channel: {id}"); } }; cleanupTimer.AutoReset = true; cleanupTimer.Start(); } public static void UnsubscribeAllChannels() { channelListeners.Clear(); channelClassListeners.Clear(); DebugLog("Unsubscribed all events from all channels"); } public static void UnsubscribeAllInChannel(int channelId) { if (channelListeners.ContainsKey(channelId)) { channelListeners[channelId].Clear(); channelListeners.Remove(channelId); DebugLog($"Unsubscribed all struct events in channel {channelId}"); } if (channelClassListeners.ContainsKey(channelId)) { channelClassListeners[channelId].Clear(); channelClassListeners.Remove(channelId); DebugLog($"Unsubscribed all class events in channel {channelId}"); } } }