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 { if (!globalListeners.ContainsKey(typeof(T))) globalListeners[typeof(T)] = null; globalListeners[typeof(T)] = (Action)globalListeners[typeof(T)] + listener; } public static void Unsubscribe(Action listener) where T : struct { if (globalListeners.ContainsKey(typeof(T))) { globalListeners[typeof(T)] = (Action)globalListeners[typeof(T)] - listener; if (globalListeners[typeof(T)] == null) globalListeners.Remove(typeof(T)); } } public static void Publish(T eventData) where T : struct { if (globalListeners.TryGetValue(typeof(T), 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 { if (!globalClassListeners.ContainsKey(typeof(T))) globalClassListeners[typeof(T)] = null; globalClassListeners[typeof(T)] = (Action)globalClassListeners[typeof(T)] + listener; } public static void UnsubscribeClass(Action listener) where T : class { if (globalClassListeners.ContainsKey(typeof(T))) { globalClassListeners[typeof(T)] = (Action)globalClassListeners[typeof(T)] - listener; if (globalClassListeners[typeof(T)] == null) globalClassListeners.Remove(typeof(T)); } } public static void PublishClass(T eventData) where T : class { if (globalClassListeners.TryGetValue(typeof(T), 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 { if (!channelListeners.ContainsKey(channelId)) channelListeners[channelId] = new Dictionary(); if (!channelListeners[channelId].ContainsKey(typeof(T))) channelListeners[channelId][typeof(T)] = null; channelListeners[channelId][typeof(T)] = (Action)channelListeners[channelId][typeof(T)] + listener; } public static void UnsubscribeChannel(int channelId, Action listener) where T : struct { if (channelListeners.ContainsKey(channelId) && channelListeners[channelId].ContainsKey(typeof(T))) { channelListeners[channelId][typeof(T)] = (Action)channelListeners[channelId][typeof(T)] - listener; if (channelListeners[channelId][typeof(T)] == null) channelListeners[channelId].Remove(typeof(T)); if (channelListeners[channelId].Count == 0) channelListeners.Remove(channelId); } } public static void PublishChannel(int channelId, T eventData) where T : struct { if (channelListeners.TryGetValue(channelId, out var listeners) && listeners.TryGetValue(typeof(T), out var del) && del is Action action) { DebugLog($"Publish Channel Struct Event: {typeof(T).Name} to channel '{channelId}'"); action.Invoke(eventData); } } // ===== CHANNEL CLASS EVENTS ===== public static void SubscribeChannelClass(int channelId, Action listener) where T : class { if (!channelClassListeners.ContainsKey(channelId)) channelClassListeners[channelId] = new Dictionary(); if (!channelClassListeners[channelId].ContainsKey(typeof(T))) channelClassListeners[channelId][typeof(T)] = null; channelClassListeners[channelId][typeof(T)] = (Action)channelClassListeners[channelId][typeof(T)] + listener; } public static void UnsubscribeChannelClass(int channelId, Action listener) where T : class { if (channelClassListeners.ContainsKey(channelId) && channelClassListeners[channelId].ContainsKey(typeof(T))) { channelClassListeners[channelId][typeof(T)] = (Action)channelClassListeners[channelId][typeof(T)] - listener; if (channelClassListeners[channelId][typeof(T)] == null) channelClassListeners[channelId].Remove(typeof(T)); if (channelClassListeners[channelId].Count == 0) channelClassListeners.Remove(channelId); } } public static void PublishChannelClass(int channelId, T eventData) where T : class { if (channelClassListeners.TryGetValue(channelId, out var listeners) && listeners.TryGetValue(typeof(T), out var del) && del is Action action) { DebugLog($"Publish Channel Class Event: {typeof(T).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 }