using System; using System.Collections.Generic; using System.Linq; using System.Timers; using UnityEngine; public static class EventBus { private static readonly Dictionary globalListeners = new(); private static readonly Dictionary globalClassListeners = new(); private static readonly Dictionary> channelListeners = new(); private static readonly Dictionary> channelClassListeners = new(); 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(typeof(T))) globalListeners[type] = null; globalListeners[type] = (Action)globalListeners[type] + listener; } public static void Unsubscribe(Action listener) where T : struct { var type = typeof(T); if (globalListeners.ContainsKey(typeof(T))) { globalListeners[type] = (Action)globalListeners[type] - listener; if (globalListeners[type] == null) globalListeners.Remove(type); } } public static void Publish(T eventData) where T : struct { var type = typeof(T); if (globalListeners.TryGetValue(type, out var del) && del is Action action) { DebugLog($"Publish Global Struct Event: {typeof(T).Name}"); action.Invoke(eventData); } } // ===== GLOBAL CLASS EVENTS ===== public static void SubscribeClass(Action listener) where T : class { var type = typeof(T); if (!globalClassListeners.ContainsKey(type)) globalClassListeners[type] = null; globalClassListeners[type] = (Action)globalClassListeners[type] + listener; } public static void UnsubscribeClass(Action listener) where T : class { var type = typeof(T); if (globalClassListeners.ContainsKey(type)) { globalClassListeners[type] = (Action)globalClassListeners[type] - listener; if (globalClassListeners[type] == null) globalClassListeners.Remove(type); } } public static void PublishClass(T eventData) where T : class { var type = typeof(T); if (globalClassListeners.TryGetValue(type, out var del) && del is Action action) { DebugLog($"Publish Global Class Event: {typeof(T).Name}"); action.Invoke(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; channelListeners[channelId][type] = (Action)channelListeners[channelId][type] + listener; } public static void UnsubscribeChannel(int channelId, Action listener) where T : struct { var type = typeof(T); if (channelListeners.ContainsKey(channelId) && channelListeners[channelId].ContainsKey(type)) { channelListeners[channelId][type] = (Action)channelListeners[channelId][type] - listener; if (channelListeners[channelId][type] == null) channelListeners[channelId].Remove(type); if (channelListeners[channelId].Count == 0) channelListeners.Remove(channelId); } } public static void PublishChannel(int channelId, T eventData) where T : struct { var type = typeof(T); if (channelListeners.TryGetValue(channelId, out var listeners) && listeners.TryGetValue(type, out var del) && del is Action action) { DebugLog($"Publish Channel Struct Event: {type.Name} to channel '{channelId}'"); action.Invoke(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; channelClassListeners[channelId][type] = (Action)channelClassListeners[channelId][type] + listener; } public static void UnsubscribeChannelClass(int channelId, Action listener) where T : class { var type = typeof(T); if (channelClassListeners.ContainsKey(channelId) && channelClassListeners[channelId].ContainsKey(type)) { channelClassListeners[channelId][type] = (Action)channelClassListeners[channelId][type] - listener; if (channelClassListeners[channelId][type] == null) channelClassListeners[channelId].Remove(type); if (channelClassListeners[channelId].Count == 0) channelClassListeners.Remove(channelId); } } public static void PublishChannelClass(int channelId, T eventData) where T : class { var type = typeof(T); if (channelClassListeners.TryGetValue(channelId, out var listeners) && listeners.TryGetValue(type, out var del) && del is Action action) { DebugLog($"Publish Channel Class Event: {type.Name} to channel '{channelId}'"); action.Invoke(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(); } #if UNITY_EDITOR public static void LogAllActiveListeners() { foreach (var kv in globalListeners) { var methods = kv.Value?.GetInvocationList(); if (methods != null && methods.Length > 0) { Debug.LogWarning($"[EventBus Leak] {kv.Key.Name} still has {methods.Length} listeners:"); foreach (var m in methods) Debug.LogWarning($" - Target: {m.Target}, Method: {m.Method}"); } } } #endif }