From 7918cac9839d8ecc722b159cc58ab8838fc89d79 Mon Sep 17 00:00:00 2001 From: VDH Date: Tue, 16 Sep 2025 11:00:28 +0700 Subject: [PATCH] use event bus --- Assets/Joystick Pack/Scripts/Base/Joystick.cs | 11 +- Assets/Scripts/CECHostPlayer.cs | 8 +- Assets/Scripts/CECUIManager.cs | 16 ++ Assets/Scripts/CECUIManager.cs.meta | 2 + Assets/Scripts/EventBus.cs | 247 ++++++++++++++++++ Assets/Scripts/EventBus.cs.meta | 2 + 6 files changed, 276 insertions(+), 10 deletions(-) create mode 100644 Assets/Scripts/CECUIManager.cs create mode 100644 Assets/Scripts/CECUIManager.cs.meta create mode 100644 Assets/Scripts/EventBus.cs create mode 100644 Assets/Scripts/EventBus.cs.meta diff --git a/Assets/Joystick Pack/Scripts/Base/Joystick.cs b/Assets/Joystick Pack/Scripts/Base/Joystick.cs index 97ede87bdf..6d271d6332 100644 --- a/Assets/Joystick Pack/Scripts/Base/Joystick.cs +++ b/Assets/Joystick Pack/Scripts/Base/Joystick.cs @@ -6,9 +6,6 @@ using UnityEngine.EventSystems; public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler { - public event Action OnPointUp; - public event Action OnPointDown; - public float Horizontal { get { return (snapX) ? SnapFloat(input.x, AxisOptions.Horizontal) : input.x; } } public float Vertical { get { return (snapY) ? SnapFloat(input.y, AxisOptions.Vertical) : input.y; } } public Vector2 Direction { get { return new Vector2(Horizontal, Vertical); } } @@ -64,7 +61,7 @@ public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPoint public virtual void OnPointerDown(PointerEventData eventData) { OnDrag(eventData); - OnPointDown?.Invoke(); + EventBus.Publish(new JoystickPressEvent()); } public void OnDrag(PointerEventData eventData) @@ -139,7 +136,7 @@ public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPoint input = Vector2.zero; handle.anchoredPosition = Vector2.zero; - OnPointUp?.Invoke(); + EventBus.Publish(new JoystickRealeaseEvent()); } protected Vector2 ScreenPointToAnchoredPosition(Vector2 screenPosition) @@ -154,4 +151,6 @@ public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPoint } } -public enum AxisOptions { Both, Horizontal, Vertical } \ No newline at end of file +public enum AxisOptions { Both, Horizontal, Vertical } +public struct JoystickPressEvent { } +public struct JoystickRealeaseEvent { } \ No newline at end of file diff --git a/Assets/Scripts/CECHostPlayer.cs b/Assets/Scripts/CECHostPlayer.cs index 540a8d6110..8f9d2110d0 100644 --- a/Assets/Scripts/CECHostPlayer.cs +++ b/Assets/Scripts/CECHostPlayer.cs @@ -144,7 +144,7 @@ public class CECHostPlayer : MonoBehaviour Vector3 finalMove = (move * playerSpeed) + (playerVelocity.y * Vector3.up); controller.Move(finalMove * Time.deltaTime); } - private void JoystickRelease() + private void JoystickRelease(JoystickRealeaseEvent joystickRealeaseEvent) { _playerStateMachine.ChangeState(_idleState); } @@ -341,11 +341,11 @@ public class CECHostPlayer : MonoBehaviour SetModelHostPlayer(); Debug.LogError("Pos Character = " + pos); joystick = FindAnyObjectByType(); - joystick.OnPointUp += JoystickRelease; - joystick.OnPointDown += JoystickStartDrag; + EventBus.Subscribe(JoystickRelease); + EventBus.Subscribe(JoystickStartDrag); } - private void JoystickStartDrag() + private void JoystickStartDrag(JoystickPressEvent joystickPressEvent) { _playerStateMachine.ChangeState(_moveState); } diff --git a/Assets/Scripts/CECUIManager.cs b/Assets/Scripts/CECUIManager.cs new file mode 100644 index 0000000000..549a8edfbb --- /dev/null +++ b/Assets/Scripts/CECUIManager.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +public class CECUIManager : MonoBehaviour +{ + // Start is called once before the first execution of Update after the MonoBehaviour is created + void Start() + { + + } + + // Update is called once per frame + void Update() + { + + } +} diff --git a/Assets/Scripts/CECUIManager.cs.meta b/Assets/Scripts/CECUIManager.cs.meta new file mode 100644 index 0000000000..b493f02bb2 --- /dev/null +++ b/Assets/Scripts/CECUIManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d21c02a79936b334da12ef5379524df2 \ No newline at end of file diff --git a/Assets/Scripts/EventBus.cs b/Assets/Scripts/EventBus.cs new file mode 100644 index 0000000000..401cca5668 --- /dev/null +++ b/Assets/Scripts/EventBus.cs @@ -0,0 +1,247 @@ +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 +} diff --git a/Assets/Scripts/EventBus.cs.meta b/Assets/Scripts/EventBus.cs.meta new file mode 100644 index 0000000000..bde522192d --- /dev/null +++ b/Assets/Scripts/EventBus.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f24ba7881ac213b4e905d525121d0a95 \ No newline at end of file