using System; using System.Collections.Generic; using UnityEngine; namespace DofLibrary { public class GameEventMgr { private static GameEventMgr _instance; public static GameEventMgr Instance { get { if (_instance == null) { _instance = new GameEventMgr(); } return _instance; } } public static void Init(bool is_debug_mode = false) { if (_instance == null) { _instance = new GameEventMgr(); } _isDebugMode = is_debug_mode; if (_isDebugMode) { Debug.Log($"[GameEventMgr]Init: Running in debug mode"); } } public static bool _isDebugMode = false; struct DelegateGameObjectBonding { public GameObject gameObject; public Delegate listener; public DelegateGameObjectBonding(GameObject game_object, Delegate del) { gameObject = game_object; listener = del; } } private readonly Queue> _listenerListQueue = new Queue>(); private readonly Queue> _listenerBondingListQueue = new Queue>(); private readonly Dictionary> _eventTable = new Dictionary>(); private readonly Dictionary> _eventObjTable = new Dictionary>(); private readonly Dictionary> _pendingAddTable = new Dictionary>(); private readonly Dictionary> _pendingObjAddTable = new Dictionary>(); private readonly Dictionary> _pendingRemoveTable = new Dictionary>(); private readonly Dictionary> _pendingObjRemoveTable = new Dictionary>(); // Using raising event list to prevent infinite loop call or changing delegate list private readonly List _raisingEventIds = new List(); private GameEventMgr() { } public void AddListener(int event_id, Action new_listener, GameObject life_cycle_obj = null) { DoAddListener(event_id, new_listener, life_cycle_obj); } public void AddListener(int event_id, Action new_listener, GameObject life_cycle_obj = null) { DoAddListener(event_id, new_listener, life_cycle_obj); } public void AddListener(int event_id, Action new_listener, GameObject life_cycle_obj = null) { DoAddListener(event_id, new_listener, life_cycle_obj); } public void AddListener(int event_id, Action new_listener, GameObject life_cycle_obj = null) { DoAddListener(event_id, new_listener, life_cycle_obj); } public void RemoveListener(int event_id, Action listener, GameObject life_cycle_obj = null) { DoRemoveListener(event_id, listener, life_cycle_obj); } public void RemoveListener(int event_id, Action listener, GameObject life_cycle_obj = null) { DoRemoveListener(event_id, listener, life_cycle_obj); } public void RemoveListener(int event_id, Action listener, GameObject life_cycle_obj = null) { DoRemoveListener(event_id, listener, life_cycle_obj); } public void RemoveListener(int event_id, Action listener, GameObject life_cycle_obj = null) { DoRemoveListener(event_id, listener, life_cycle_obj); } public void Raise(int event_id) { DoRaise(event_id); } public void Raise(int event_id, T arg1) { DoRaise(event_id, arg1); } public void Raise(int event_id, T1 arg1, T2 arg2) { DoRaise(event_id, arg1, arg2); } public void Raise(int event_id, T1 arg1, T2 arg2, T3 arg3) { DoRaise(event_id, arg1, arg2, arg3); } public void ClearNullGameObjectListeners() { var etor = _eventObjTable.GetEnumerator(); while (etor.MoveNext()) { var listeners = etor.Current.Value; for (int i = 0; i < listeners.Count; i++) { if (listeners[i].gameObject == null) { listeners.RemoveAt(i--); } } } } private List SpawnDelegateList() { if (_listenerListQueue.Count > 0) { return _listenerListQueue.Dequeue(); } else { return new List(); } } private void DespawnDelegateList(List list) { if (list == null) return; list.Clear(); _listenerListQueue.Enqueue(list); } private List SpawnDelegateBondingList() { if (_listenerBondingListQueue.Count > 0) { return _listenerBondingListQueue.Dequeue(); } else { return new List(); } } private void DespawnDelegateBondingList(List list) { if (list == null) return; list.Clear(); _listenerBondingListQueue.Enqueue(list); } private bool DoAddListener(int event_id, Delegate new_listener, GameObject life_cycle_obj = null) { if (new_listener == null) { Debug.LogError($"[GameEventMgr]DoAddListener: Can't add empty listener for event {event_id}!"); return false; } bool result = true; // If event is raising, add it into pending list if (_raisingEventIds.Contains(event_id)) { if (life_cycle_obj != null) { if (!_pendingObjAddTable.TryGetValue(event_id, out var pending_listeners)) { pending_listeners = SpawnDelegateBondingList(); _pendingObjAddTable[event_id] = pending_listeners; } pending_listeners.Add(new DelegateGameObjectBonding(life_cycle_obj, new_listener)); } else { if (!_pendingAddTable.TryGetValue(event_id, out var pending_listeners)) { pending_listeners = SpawnDelegateList(); _pendingAddTable[event_id] = pending_listeners; } pending_listeners.Add(new_listener); } } else { if (life_cycle_obj != null) { if (!_eventObjTable.TryGetValue(event_id, out var listener_bondings)) { listener_bondings = SpawnDelegateBondingList(); _eventObjTable[event_id] = listener_bondings; } if (listener_bondings.Count == 0 || listener_bondings[0].listener.GetType() == new_listener.GetType()) { var bonding = new DelegateGameObjectBonding(life_cycle_obj, new_listener); if (_isDebugMode && listener_bondings.Contains(bonding)) { Debug.LogError($"[GameEventMgr]DoAddListener: Already in the list, will not add it again [{new_listener.Target}.{new_listener.Method?.Name}]"); } else { listener_bondings.Add(bonding); } } else if (listener_bondings[0].listener.GetType() != new_listener.GetType()) { Debug.LogError($"[GameEventMgr]DoAddListener: Attempting to add listener with inconsistent signature for event {event_id}. Current listeners type({listener_bondings[0].listener.GetType().Name}) != added type({new_listener.GetType().Name})"); result = false; } } else { if (!_eventTable.TryGetValue(event_id, out var listeners)) { listeners = SpawnDelegateList(); _eventTable[event_id] = listeners; } if (listeners.Count == 0 || listeners[0].GetType() == new_listener.GetType()) { if (_isDebugMode && listeners.Contains(new_listener)) { Debug.LogError($"[GameEventMgr]DoAddListener: Already in the list, will not add it again [{new_listener.Target}.{new_listener.Method?.Name}]"); } else { listeners.Add(new_listener); } } else if (listeners[0].GetType() != new_listener.GetType()) { Debug.LogError($"[GameEventMgr]DoAddListener: Attempting to add listener with inconsistent signature for event {event_id}. Current listeners type({listeners[0].GetType().Name}) != added type({new_listener.GetType().Name})"); result = false; } } } return result; } private bool DoRemoveListener(int event_id, Delegate listener, GameObject life_cycle_obj = null) { if (listener == null) { Debug.LogError($"[GameEventMgr]DoRemoveListener: Can't remove empty listener for event {event_id}!"); return false; } bool result = false; // If event is raising, add it into pending list if (_raisingEventIds.Contains(event_id)) { if (life_cycle_obj != null) { if (!_pendingObjRemoveTable.TryGetValue(event_id, out var pending_listeners)) { pending_listeners = SpawnDelegateBondingList(); _pendingObjRemoveTable[event_id] = pending_listeners; } pending_listeners.Add(new DelegateGameObjectBonding(life_cycle_obj, listener)); } else { if (!_pendingRemoveTable.TryGetValue(event_id, out var pending_listeners)) { pending_listeners = SpawnDelegateList(); _pendingRemoveTable[event_id] = pending_listeners; } pending_listeners.Add(listener); } result = true; } else { if (life_cycle_obj != null) { if (_eventObjTable.TryGetValue(event_id, out var listeners)) { if (listeners.Count > 0) { listeners.Remove(new DelegateGameObjectBonding(life_cycle_obj, listener)); result = true; } if (listeners.Count == 0) { DespawnDelegateBondingList(listeners); _eventObjTable.Remove(event_id); } } } else { if (_eventTable.TryGetValue(event_id, out var listeners)) { if (listeners.Count > 0) { listeners.Remove(listener); result = true; } if (listeners.Count == 0) { DespawnDelegateList(listeners); _eventTable.Remove(event_id); } } } } return result; } private void LogException(Exception ex, string event_tag, string method_name) { string msg = ex.Message; string stack_trace = ex.StackTrace; if (ex.InnerException != null) { msg = ex.InnerException.Message; stack_trace = ex.InnerException.StackTrace; } Debug.LogError($"[GameEventMgr]{method_name}: Event({event_tag}) {msg}\nStack Trace: {stack_trace}"); } private bool DoRaise(int event_id, params object[] args) { if (_raisingEventIds.Contains(event_id)) { Debug.LogError($"[GameEventMgr]DoRaise: Can't raise event({event_id}) again inside the same event raising process!"); return false; } bool result = false; _raisingEventIds.Add(event_id); if (_eventObjTable.TryGetValue(event_id, out var bond_listeners)) { for (int i = 0; i < bond_listeners.Count; i++) { var bond_listener = bond_listeners[i]; if (bond_listener.gameObject != null) { try { bond_listener.listener.Method.Invoke(bond_listener.listener.Target, args); result = true; } catch (Exception ex) { bond_listeners.RemoveAt(i--); LogException(ex, $"{event_id}_Obj[{bond_listener.gameObject.name}]", "DoRaise"); } } else { bond_listeners.RemoveAt(i--); Debug.Log($"[GameEventMgr]DoRaise: Remove null GameObject listener ({event_id})!"); continue; } } } if (_eventTable.TryGetValue(event_id, out var listeners)) { for (int i = 0; i < listeners.Count; i++) { var listener = listeners[i]; if (listener != null) { try { listener.Method.Invoke(listener.Target, args); result = true; } catch (Exception ex) { listeners.RemoveAt(i--); LogException(ex, event_id.ToString(), "DoRaise"); } } } } _raisingEventIds.Remove(event_id); if (_pendingObjAddTable.TryGetValue(event_id, out var add_bond_listeners)) { if (add_bond_listeners != null && add_bond_listeners.Count > 0) { bond_listeners.AddRange(add_bond_listeners); DespawnDelegateBondingList(add_bond_listeners); } _pendingObjAddTable.Remove(event_id); } if (_pendingObjRemoveTable.TryGetValue(event_id, out var remove_bond_listeners)) { if (remove_bond_listeners != null && remove_bond_listeners.Count > 0) { for (int i = 0; i < remove_bond_listeners.Count; i++) { bond_listeners.Remove(remove_bond_listeners[i]); } DespawnDelegateBondingList(remove_bond_listeners); } _pendingObjRemoveTable.Remove(event_id); } if (bond_listeners?.Count == 0) { DespawnDelegateBondingList(bond_listeners); _eventObjTable.Remove(event_id); } if (_pendingAddTable.TryGetValue(event_id, out var add_listeners)) { if (add_listeners != null && add_listeners.Count > 0) { listeners.AddRange(add_listeners); DespawnDelegateList(add_listeners); } _pendingAddTable.Remove(event_id); } if (_pendingRemoveTable.TryGetValue(event_id, out var remove_listeners)) { if (remove_listeners != null && remove_listeners.Count > 0) { for (int i = 0; i < remove_listeners.Count; i++) { listeners.Remove(remove_listeners[i]); } DespawnDelegateList(remove_listeners); } _pendingRemoveTable.Remove(event_id); } if (listeners?.Count == 0) { DespawnDelegateList(listeners); _eventTable.Remove(event_id); } return result; } public void Clear() { _eventTable.Clear(); _eventObjTable.Clear(); _pendingAddTable.Clear(); _pendingObjAddTable.Clear(); _pendingRemoveTable.Clear(); _pendingObjRemoveTable.Clear(); _raisingEventIds.Clear(); } } }