| using System.Collections.Generic; | 
| using UnityEngine; | 
| using System; | 
| using Cysharp.Threading.Tasks; | 
| using System.Linq; | 
|   | 
| #if UNITY_EDITOR | 
| using UnityEngine.Profiling; | 
| #endif | 
|   | 
|   | 
| public class GameObjectPoolManager : SingletonMonobehaviour<GameObjectPoolManager> | 
| { | 
| #if UNITY_EDITOR | 
|     private bool m_ShowDebugPanel = false; | 
|     private Vector2 m_ScrollPosition = Vector2.zero; | 
|   | 
|      | 
|     private void OnGUI() | 
|     { | 
|         if (!m_ShowDebugPanel) return; | 
|   | 
|         GUILayout.BeginArea(new Rect(10, 10, 400, 600)); | 
|         GUILayout.BeginVertical("Box"); | 
|         m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUILayout.Width(380), GUILayout.Height(580)); | 
|   | 
|         GUILayout.Label("对象池调试面板", GUILayout.Height(30)); | 
|         LogPoolMemoryUsage(); | 
|         GUILayout.Space(10); | 
|   | 
|         foreach (var poolEntry in m_PoolDict) | 
|         { | 
|             int prefabInstanceId = poolEntry.Key; | 
|             GameObjectPool pool = poolEntry.Value; | 
|   | 
|             GUILayout.BeginVertical("Box"); | 
|             GUILayout.Label($"池名称: {pool.Prefab.name}"); | 
|             GUILayout.Label($"活跃对象数: {pool.m_ActiveHashSet.Count}"); | 
|             GUILayout.Label($"空闲对象数: {pool.m_FreeQueue.Count}"); | 
|             int count = m_PoolUsageFrequency.TryGetValue(prefabInstanceId, out int frequency) ? frequency : 0; | 
|             GUILayout.Label($"使用频率: {count}"); | 
|             GUILayout.EndVertical(); | 
|             GUILayout.Space(5); | 
|         } | 
|   | 
|         GUILayout.EndScrollView(); | 
|         GUILayout.EndVertical(); | 
|         GUILayout.EndArea(); | 
|     } | 
|   | 
|     [UnityEditor.MenuItem("Tools/对象池/显示调试面板")] | 
|     private static void ToggleDebugPanel() | 
|     { | 
|         if (Instance != null) | 
|         { | 
|             Instance.m_ShowDebugPanel = !Instance.m_ShowDebugPanel; | 
|         } | 
|     } | 
|   | 
|     public void LogPoolMemoryUsage() | 
|     { | 
|         long totalMemory = 0; | 
|         long totalFreeMemory = 0; | 
|         var prefabMemoryDict = new Dictionary<string, long>(); | 
|   | 
|         foreach (var poolEntry in m_PoolDict) | 
|         { | 
|             int prefabInstanceId = poolEntry.Key; | 
|             GameObjectPool pool = poolEntry.Value; | 
|             string prefabName = pool.Prefab.name; | 
|             long prefabMemory = 0; | 
|             long freeMemory = 0; | 
|   | 
|             // 计算活跃对象的内存占用 | 
|             foreach (var gameObject in pool.m_ActiveHashSet) | 
|             { | 
|                 if (gameObject != null) | 
|                 { | 
|                     long memory = Profiler.GetRuntimeMemorySizeLong(gameObject); | 
|                     prefabMemory += memory; | 
|                 } | 
|             } | 
|   | 
|             // 计算空闲对象的内存占用 | 
|             foreach (var gameObject in pool.m_FreeQueue) | 
|             { | 
|                 if (gameObject != null) | 
|                 { | 
|                     long memory = Profiler.GetRuntimeMemorySizeLong(gameObject); | 
|                     prefabMemory += memory; | 
|                     freeMemory += memory; | 
|                 } | 
|             } | 
|   | 
|             totalMemory += prefabMemory; | 
|             totalFreeMemory += freeMemory; | 
|             prefabMemoryDict[prefabName] = prefabMemory; | 
|         } | 
|   | 
|         // 按内存占用排序 | 
|         var sortedPrefabs = prefabMemoryDict.OrderByDescending(kv => kv.Value).Take(3).ToList(); | 
|   | 
|         GUILayout.Label($"总内存占用: {totalMemory / 1024} KB", GUILayout.Height(30)); | 
|         GUILayout.Label($"空闲内存占用: {totalFreeMemory / 1024} KB", GUILayout.Height(30)); | 
|         GUILayout.Label("占用最高的前3预制体名:", GUILayout.Height(30)); | 
|         foreach (var prefabInstance in sortedPrefabs) | 
|         { | 
|             GUILayout.BeginHorizontal("Box"); | 
|             GUILayout.Label($"{prefabInstance.Key}({prefabInstance.Value / 1024} KB)", GUILayout.Height(25)); | 
|             GUILayout.EndHorizontal(); | 
|         } | 
|   | 
|     } | 
|   | 
|   | 
| #endif | 
|     // 池统计数据 | 
|     public Dictionary<int, PoolStats> PoolStatistics { get; private set; } = new Dictionary<int, PoolStats>(); | 
|   | 
|     public class PoolStats | 
|     { | 
|         public int TotalObjects { get; set; } | 
|         public int ActiveObjects { get; set; } | 
|         public int FreeObjects { get; set; } | 
|         public int UsageFrequency { get; set; } | 
|     } | 
|   | 
|     // 更新池统计数据 | 
|     private void UpdatePoolStats() | 
|     { | 
|         if (m_PoolDict == null) | 
|             return; | 
|              | 
|         foreach (var poolEntry in m_PoolDict) | 
|         { | 
|             int prefabInstanceId = poolEntry.Key; | 
|             GameObjectPool pool = poolEntry.Value; | 
|   | 
|             if (!PoolStatistics.ContainsKey(prefabInstanceId)) | 
|             { | 
|                 PoolStatistics[prefabInstanceId] = new PoolStats(); | 
|             } | 
|   | 
|             PoolStats stats = PoolStatistics[prefabInstanceId]; | 
|             stats.TotalObjects = pool.m_ActiveHashSet.Count + pool.m_FreeQueue.Count; | 
|             stats.ActiveObjects = pool.m_ActiveHashSet.Count; | 
|             stats.FreeObjects = pool.m_FreeQueue.Count; | 
|             stats.UsageFrequency = m_PoolUsageFrequency.TryGetValue(prefabInstanceId, out int frequency) ? frequency : 0; | 
|         } | 
|     } | 
|   | 
|   | 
|     private Dictionary<int, GameObjectPool> m_PoolDict = null; | 
|   | 
|     // 不需要销毁的对象列表 | 
|     private List<int> dontDestoryGoInstIDList = null; | 
|   | 
|     private Transform m_TargetContainer; | 
|   | 
|     // 定期检查池的时间间隔(秒) | 
|     private float m_PoolCheckInterval = 600f; | 
|   | 
|     // 记录每个池的使用频率 | 
|     private Dictionary<int, int> m_PoolUsageFrequency = new Dictionary<int, int>(); | 
|   | 
|     //需要动态卸载的预制体,例如切换场景时 | 
|     public List<GameObject> needDestroyPrefabList = new List<GameObject>(); | 
|   | 
|     public void Initialize() | 
|     { | 
|         if (m_PoolDict == null) | 
|         { | 
|             m_PoolDict = new Dictionary<int, GameObjectPool>(); | 
|         } | 
|   | 
|         if (!m_TargetContainer) | 
|         { | 
|             m_TargetContainer = new GameObject("Container").transform; | 
|             m_TargetContainer.SetParent(transform); | 
|         } | 
|   | 
|         if (dontDestoryGoInstIDList == null) | 
|         { | 
|             dontDestoryGoInstIDList = new List<int>(); | 
|         } | 
|   | 
|         // 启动定期检查池的协程 | 
|         CheckPoolUsage(); | 
|     } | 
|   | 
|     private async UniTask CheckPoolUsage() | 
|     { | 
|         while (true) | 
|         { | 
|             await UniTask.Delay(TimeSpan.FromSeconds(m_PoolCheckInterval)); | 
|   | 
|             foreach (var poolEntry in m_PoolDict) | 
|             { | 
|                 int prefabInstanceId = poolEntry.Key; | 
|                 GameObjectPool pool = poolEntry.Value; | 
|   | 
|                 // 如果池的使用频率低于阈值,减少池的大小 | 
|                 if (m_PoolUsageFrequency.TryGetValue(prefabInstanceId, out int usageCount) && usageCount < 5) | 
|                 { | 
|                     // 保留至少一个对象,避免频繁创建和销毁 | 
|                     int targetSize = Mathf.Max(1, pool.m_FreeQueue.Count / 2); | 
|                     while (pool.m_FreeQueue.Count > targetSize) | 
|                     { | 
|                         GameObject obj = pool.m_FreeQueue.Dequeue(); | 
|                         if (!dontDestoryGoInstIDList.Contains(obj.GetInstanceID())) | 
|                         { | 
|                             Destroy(obj); | 
|                         } | 
|                     } | 
|                 } | 
|   | 
|                 // 重置使用频率 | 
|                 m_PoolUsageFrequency[prefabInstanceId] = 0; | 
|             } | 
|         } | 
|     } | 
|   | 
|     public void AddDontDestroyGoInstID(int id) | 
|     { | 
|         if (!dontDestoryGoInstIDList.Contains(id)) | 
|         { | 
|             dontDestoryGoInstIDList.Add(id); | 
|         } | 
|     } | 
|   | 
|     public bool ExistPool(GameObject prefab) | 
|     { | 
|         if (prefab == null) | 
|         { | 
|             return false; | 
|         } | 
|   | 
|         if (m_PoolDict.ContainsKey(prefab.GetInstanceID())) | 
|         { | 
|             return true; | 
|         } | 
|   | 
|         return false; | 
|     } | 
|   | 
|     /// <summary> | 
|     /// 传入的为预制体,而非组件,否则会导致GetInstanceID管理混乱,每次实例化都会改变 | 
|     /// </summary> | 
|     /// <param name="prefab"></param> | 
|     /// <returns></returns> | 
|     public GameObjectPool RequestPool(GameObject prefab) | 
|     { | 
|         if (prefab == null) | 
|         { | 
|             return null; | 
|         } | 
|   | 
| #if UNITY_EDITOR | 
|         if (UnityEditor.PrefabUtility.GetPrefabAssetType(prefab) == UnityEditor.PrefabAssetType.NotAPrefab) | 
|         { | 
|             Debug.LogError($"{prefab.name}不是一个预制体!"); | 
|             return null; | 
|         } | 
| #endif | 
|   | 
|   | 
|         int _prefabInstanceId = prefab.GetInstanceID(); | 
|   | 
|         GameObjectPool _pool = null; | 
|         if (m_PoolDict.TryGetValue(_prefabInstanceId, out _pool)) | 
|         { | 
|             return _pool; | 
|         } | 
|   | 
|         _pool = new GameObjectPool(prefab); | 
|         m_PoolDict[_prefabInstanceId] = _pool; | 
|   | 
|         return _pool; | 
|     } | 
|   | 
|   | 
|     public void CacheGameObject(GameObject prefab, int count, bool _prefabActive) | 
|     { | 
|         if (prefab == null) | 
|         { | 
|             return; | 
|         } | 
|   | 
|         RequestPool(prefab).Cache(count, _prefabActive); | 
|     } | 
|   | 
|   | 
|     public GameObject RequestGameObject(GameObject prefab) | 
|     { | 
|         if (prefab == null) | 
|         { | 
|             return null; | 
|         } | 
|   | 
| #if UNITY_EDITOR | 
|         if (UnityEditor.PrefabUtility.GetPrefabAssetType(prefab) == UnityEditor.PrefabAssetType.NotAPrefab) | 
|         { | 
|             Debug.LogError($"{prefab.name}不是一个预制体!"); | 
|             return null; | 
|         } | 
| #endif | 
|         return RequestPool(prefab).Request(); | 
|     } | 
|   | 
|   | 
|     //特定情况下需要动态卸载的,如切换场景时 | 
|     public void UnLoadAll(bool force = false) | 
|     { | 
|         List<int> _removeList = new List<int>(); | 
|         foreach (var _pool in m_PoolDict.Values) | 
|         { | 
|             if (needDestroyPrefabList.Contains(_pool.Prefab)) | 
|             { | 
|                 _pool.Clear(); | 
|                 _removeList.Add(_pool.PrefabInstanceId); | 
|             } | 
|         } | 
|   | 
|         for (int i = 0; i < _removeList.Count; ++i) | 
|         { | 
|             m_PoolDict.Remove(_removeList[i]); | 
|         } | 
|     } | 
|   | 
|     public class GameObjectPool | 
|     { | 
|         private GameObject m_Prefab = null; | 
|         public GameObject Prefab | 
|         { | 
|             get | 
|             { | 
|                 return m_Prefab; | 
|             } | 
|         } | 
|   | 
|         //预制体实例id,用于检索池 | 
|         public int PrefabInstanceId | 
|         { | 
|             get; private set; | 
|         } | 
|   | 
|         public HashSet<GameObject> m_ActiveHashSet = new HashSet<GameObject>(); | 
|         public Queue<GameObject> m_FreeQueue = null; | 
|         private int Pool_FreeList_Warning_Threshold = 10;    //当空闲对象高于此值时,会进行日志输出提醒 | 
|   | 
|         Action<GameObject> releaseCallBack; | 
|   | 
|   | 
|         public string assetBundleName; | 
|         public string assetName; | 
|   | 
|         internal protected GameObjectPool(GameObject prefab) | 
|         { | 
|             m_Prefab = prefab; | 
|             PrefabInstanceId = m_Prefab.GetInstanceID(); | 
|             m_ActiveHashSet = new HashSet<GameObject>(); | 
|             m_FreeQueue = new Queue<GameObject>(); | 
|   | 
|         } | 
|   | 
|         public void AddReleaseListener(Action<GameObject> _callBack) | 
|         { | 
|             releaseCallBack = _callBack; | 
|         } | 
|   | 
|         public void RemoveReleaseListener() | 
|         { | 
|             releaseCallBack = null; | 
|         } | 
|   | 
|         public void Cache(int count, bool _prefabActive) | 
|         { | 
|             GameObject _go; | 
|             for (int i = 0; i < count; ++i) | 
|             { | 
|                 _go = Instantiate(m_Prefab, Constants.Special_Hide_Position, Quaternion.identity); | 
|                 m_FreeQueue.Enqueue(_go); | 
|                 _go.transform.SetParent(Instance.m_TargetContainer); | 
|                 _go.transform.position = Constants.Special_Hide_Position; | 
|   | 
|   | 
| #if UNITY_EDITOR | 
|                 // 检查空闲列表数量是否超过阈值 | 
|                 if (m_FreeQueue.Count > Pool_FreeList_Warning_Threshold) | 
|                 { | 
|                     Debug.LogWarning($"GameObjectPool {m_Prefab.name} 的空闲对象数量 ({m_FreeQueue.Count}) 超过阈值 ({Pool_FreeList_Warning_Threshold}),请检查是否需要清理。"); | 
|                 } | 
| #endif | 
|                 var animator = _go.GetComponent<Animator>(); | 
|                 if (animator != null) | 
|                 { | 
|                     animator.enabled = false; | 
|                 } | 
|                 _go.SetActive(_prefabActive); | 
|             } | 
|         } | 
|   | 
|         public GameObject Request() | 
|         { | 
|             GameObject _go = null; | 
|   | 
|             if (m_FreeQueue.Count > 0) | 
|             { | 
|                 _go = m_FreeQueue.Dequeue(); | 
|   | 
|                 m_ActiveHashSet.Add(_go); | 
|   | 
|                 // 统计池的使用频率 | 
|                 if (Instance.m_PoolUsageFrequency.ContainsKey(m_Prefab.GetInstanceID())) | 
|                 { | 
|                     Instance.m_PoolUsageFrequency[m_Prefab.GetInstanceID()]++; | 
|                 } | 
|                 else | 
|                 { | 
|                     Instance.m_PoolUsageFrequency[m_Prefab.GetInstanceID()] = 1; | 
|                 } | 
|   | 
|             } | 
|   | 
|   | 
|             if (_go == null) | 
|             { | 
|                 _go = Instantiate(m_Prefab, Constants.Special_Hide_Position, Quaternion.identity); | 
|                 m_ActiveHashSet.Add(_go); | 
|   | 
|             } | 
|   | 
|             _go.transform.SetParent(null); | 
|             _go.transform.localScale = Vector3.one; | 
|   | 
|             // 更新池统计数据 | 
|             Instance.UpdatePoolStats(); | 
|   | 
|             return _go; | 
|         } | 
|   | 
|   | 
|         public void Release(GameObject go) | 
|         { | 
|             if (go == null) | 
|             { | 
|                 return; | 
|             } | 
|             m_ActiveHashSet.Remove(go); | 
|   | 
|             m_FreeQueue.Enqueue(go); | 
|             go.transform.SetParent(Instance.m_TargetContainer); | 
|   | 
|             go.transform.position = Constants.Special_Hide_Position; | 
|   | 
|   | 
| #if UNITY_EDITOR | 
|             // 检查空闲列表数量是否超过阈值 | 
|             if (m_FreeQueue.Count > Pool_FreeList_Warning_Threshold) | 
|             { | 
|                 Debug.LogWarning($"GameObjectPool {m_Prefab.name} 的空闲对象数量 ({m_FreeQueue.Count}) 超过阈值 ({Pool_FreeList_Warning_Threshold}),请检查是否需要清理。"); | 
|             } | 
| #endif | 
|   | 
|             if (releaseCallBack != null) | 
|             { | 
|                 releaseCallBack(go); | 
|             } | 
|   | 
|             // 更新池统计数据 | 
|             Instance.UpdatePoolStats(); | 
|         } | 
|   | 
|         public void ReleaseAll() | 
|         { | 
|             //释放 m_ActiveHashSet中 的GameObject,排除 | 
|             foreach (GameObject go in m_ActiveHashSet) | 
|             { | 
|                 if (go != null) | 
|                 { | 
|                     m_FreeQueue.Enqueue(go); | 
|                     go.transform.SetParent(Instance.m_TargetContainer); | 
|                     go.transform.position = Constants.Special_Hide_Position; | 
|   | 
|                     if (releaseCallBack != null) | 
|                     { | 
|                         releaseCallBack(go); | 
|                     } | 
|                 } | 
|                  | 
|             } | 
|             m_ActiveHashSet.Clear(); | 
|   | 
|             // 更新池统计数据 | 
|             Instance.UpdatePoolStats(); | 
|         } | 
|   | 
|         public void Clear() | 
|         { | 
|             foreach (GameObject go in m_ActiveHashSet) | 
|             { | 
|                 if (go != null) | 
|                 { | 
|                     if (!Instance.dontDestoryGoInstIDList.Contains(go.GetInstanceID())) | 
|                     { | 
|                         m_ActiveHashSet.Remove(go); | 
|                         Destroy(go); | 
|                     } | 
|   | 
|                 } | 
|                  | 
|             } | 
|   | 
| #if UNITY_EDITOR | 
|             if (m_ActiveHashSet.Count != 0) | 
|             { | 
|                 Debug.LogWarningFormat("{0} 池清理后, 还有 {1} 个活跃对象. ", m_Prefab.name, m_ActiveHashSet.Count); | 
|             } | 
| #endif | 
|   | 
|             Queue<GameObject> tempQueue = new Queue<GameObject>(); | 
|             while (m_FreeQueue.Count > 0) | 
|             { | 
|                 GameObject obj = m_FreeQueue.Dequeue(); | 
|                 if (Instance.dontDestoryGoInstIDList.Contains(obj.GetInstanceID())) | 
|                     tempQueue.Enqueue(obj); | 
|                 else | 
|                     Destroy(obj); | 
|             } | 
|             m_FreeQueue = tempQueue; | 
|   | 
|   | 
| #if UNITY_EDITOR | 
|             if (m_FreeQueue.Count != 0) | 
|             { | 
|                 Debug.LogWarningFormat("{0} 池清理后, 还有 {1} 个空闲对象. ", m_Prefab.name, m_FreeQueue.Count); | 
|                 // for (int i = 0; i < m_FreeList.Count; ++i) | 
|                 // { | 
|                 //     Debug.LogWarningFormat(" |-- {0}", m_FreeList[i].name); | 
|                 // } | 
|             } | 
| #endif | 
|             if (IsEmpty()) | 
|             { | 
|                 m_Prefab = null; | 
|   | 
|             } | 
|   | 
|             if (!string.IsNullOrEmpty(assetBundleName) && !string.IsNullOrEmpty(assetName)) | 
|             { | 
|                 ResManager.Instance.UnloadAsset(assetBundleName, assetName); | 
|                 // ResManager.Instance.UnloadAssetBundle(assetBundleName, true, false); | 
|             } | 
|         } | 
|   | 
|         public bool IsEmpty() | 
|         { | 
|             return m_ActiveHashSet.Count == 0 && m_FreeQueue.Count == 0; | 
|         } | 
|     } | 
| } |