510 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
		
		
			
		
	
	
			510 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
|  | using System; | |||
|  | using System.Collections.Generic; | |||
|  | using UnityEngine; | |||
|  | 
 | |||
|  | namespace Guru | |||
|  | { | |||
|  | 
 | |||
|  |     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<List<Delegate>> _listenerListQueue = new (); | |||
|  |         private readonly Queue<List<DelegateGameObjectBonding>> _listenerBondingListQueue = new (); | |||
|  |         private readonly Dictionary<int, List<Delegate>> _eventTable = new (); | |||
|  |         private readonly Dictionary<int, List<DelegateGameObjectBonding>> _eventObjTable = new (); | |||
|  |         private readonly Dictionary<int, List<Delegate>> _pendingAddTable = new (); | |||
|  |         private readonly Dictionary<int, List<DelegateGameObjectBonding>> _pendingObjAddTable = new (); | |||
|  |         private readonly Dictionary<int, List<Delegate>> _pendingRemoveTable = new (); | |||
|  |         private readonly Dictionary<int, List<DelegateGameObjectBonding>> _pendingObjRemoveTable = new (); | |||
|  | 
 | |||
|  |         // Using raising event list to prevent infinite loop call or changing delegate list | |||
|  |         private readonly List<int> _raisingEventIds = new (); | |||
|  | 
 | |||
|  |         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<T>(int event_id, Action<T> new_listener, GameObject life_cycle_obj = null) | |||
|  |         { | |||
|  |             DoAddListener(event_id, new_listener, life_cycle_obj); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void AddListener<T1, T2>(int event_id, Action<T1, T2> new_listener, GameObject life_cycle_obj = null) | |||
|  |         { | |||
|  |             DoAddListener(event_id, new_listener, life_cycle_obj); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void AddListener<T1, T2, T3>(int event_id, Action<T1, T2, T3> 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<T>(int event_id, Action<T> listener, GameObject life_cycle_obj = null) | |||
|  |         { | |||
|  |             DoRemoveListener(event_id, listener, life_cycle_obj); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void RemoveListener<T1, T2>(int event_id, Action<T1, T2> listener, GameObject life_cycle_obj = null) | |||
|  |         { | |||
|  |             DoRemoveListener(event_id, listener, life_cycle_obj); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void RemoveListener<T1, T2, T3>(int event_id, Action<T1, T2, T3> 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<T>(int event_id, T arg1) | |||
|  |         { | |||
|  |             DoRaise(event_id, arg1); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void Raise<T1, T2>(int event_id, T1 arg1, T2 arg2) | |||
|  |         { | |||
|  |             DoRaise(event_id, arg1, arg2); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void Raise<T1, T2, T3>(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<Delegate> SpawnDelegateList() | |||
|  |         { | |||
|  |             if(_listenerListQueue.Count > 0) | |||
|  |             { | |||
|  |                 return _listenerListQueue.Dequeue(); | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 return new List<Delegate>(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private void DespawnDelegateList(List<Delegate> list) | |||
|  |         { | |||
|  |             if (list == null) return; | |||
|  | 
 | |||
|  |             list.Clear(); | |||
|  |             _listenerListQueue.Enqueue(list); | |||
|  |         } | |||
|  | 
 | |||
|  |         private List<DelegateGameObjectBonding> SpawnDelegateBondingList() | |||
|  |         { | |||
|  |             if (_listenerBondingListQueue.Count > 0) | |||
|  |             { | |||
|  |                 return _listenerBondingListQueue.Dequeue(); | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 return new List<DelegateGameObjectBonding>(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private void DespawnDelegateBondingList(List<DelegateGameObjectBonding> 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(); | |||
|  |         } | |||
|  |     } | |||
|  | } |