Main/Core/ResModule/GameObjectPoolManager.cs
@@ -1,20 +1,170 @@
using System.Collections.Generic;
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 Dictionary<int, DebugItem> m_DebugInstIDDict = null;
    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()
    {
@@ -34,12 +184,40 @@
            dontDestoryGoInstIDList = new List<int>();
        }
#if UNITY_EDITOR
        if (m_DebugInstIDDict == null)
        // 启动定期检查池的协程
        CheckPoolUsage();
    }
    private async UniTask CheckPoolUsage()
    {
        while (true)
        {
            m_DebugInstIDDict = new Dictionary<int, DebugItem>();
            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;
            }
        }
#endif
    }
    public void AddDontDestroyGoInstID(int id)
@@ -65,12 +243,26 @@
        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();
@@ -105,20 +297,28 @@
            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 (GAMgr.Instance.needDestroyPrefabList.Contains(_pool.Prefab))
            // {
            if (needDestroyPrefabList.Contains(_pool.Prefab))
            {
                _pool.Clear();
                _removeList.Add(_pool.PrefabInstanceId);
            // }
            }
        }
        for (int i = 0; i < _removeList.Count; ++i)
@@ -137,20 +337,19 @@
                return m_Prefab;
            }
        }
        //预制体实例id,用于检索池
        public int PrefabInstanceId
        {
            get; private set;
        }
        public List<GameObject> m_ActiveList = null;
        private List<GameObject> m_FreeList = null;
        public HashSet<GameObject> m_ActiveHashSet = new HashSet<GameObject>();
        public Queue<GameObject> m_FreeQueue = null;
        private int Pool_FreeList_Warning_Threshold = 10;    //当空闲对象高于此值时,会进行日志输出提醒
        Action<GameObject> releaseCallBack;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
        private Transform m_DebugContainer;
        private Transform m_DebugActive;
        private Transform m_DebugFree;
#endif
        public string assetBundleName;
        public string assetName;
@@ -159,17 +358,9 @@
        {
            m_Prefab = prefab;
            PrefabInstanceId = m_Prefab.GetInstanceID();
            m_ActiveList = new List<GameObject>();
            m_FreeList = new List<GameObject>();
            m_ActiveHashSet = new HashSet<GameObject>();
            m_FreeQueue = new Queue<GameObject>();
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
            m_DebugContainer = new GameObject(prefab.name).transform;
            m_DebugActive = new GameObject("Active").transform;
            m_DebugFree = new GameObject("Free").transform;
            m_DebugActive.SetParent(m_DebugContainer);
            m_DebugFree.SetParent(m_DebugContainer);
            m_DebugContainer.SetParent(Instance.transform);
#endif
        }
        public void AddReleaseListener(Action<GameObject> _callBack)
@@ -188,16 +379,17 @@
            for (int i = 0; i < count; ++i)
            {
                _go = Instantiate(m_Prefab, Constants.Special_Hide_Position, Quaternion.identity);
                m_FreeList.Add(_go);
                m_FreeQueue.Enqueue(_go);
                _go.transform.SetParent(Instance.m_TargetContainer);
                _go.transform.position = Constants.Special_Hide_Position;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                DebugItem _debugItem = new GameObject(_go.GetInstanceID().ToString()).AddMissingComponent<DebugItem>();
                _debugItem.transform.SetParent(m_DebugFree);
                _debugItem.target = _go;
                Instance.m_DebugInstIDDict[_go.GetInstanceID()] = _debugItem;
#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)
@@ -212,48 +404,41 @@
        {
            GameObject _go = null;
            while (m_FreeList.Count > 0)
            if (m_FreeQueue.Count > 0)
            {
                if (m_FreeList[0] != null)
                _go = m_FreeQueue.Dequeue();
                m_ActiveHashSet.Add(_go);
                // 统计池的使用频率
                if (Instance.m_PoolUsageFrequency.ContainsKey(m_Prefab.GetInstanceID()))
                {
                    _go = m_FreeList[0];
                    m_ActiveList.Add(_go);
                    m_FreeList.RemoveAt(0);
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                    if (Instance.m_DebugInstIDDict.ContainsKey(_go.GetInstanceID()))
                    {
                        DebugItem _debugItem = Instance.m_DebugInstIDDict[_go.GetInstanceID()];
                        _debugItem.transform.SetParent(m_DebugActive);
                    }
#endif
                    break;
                    Instance.m_PoolUsageFrequency[m_Prefab.GetInstanceID()]++;
                }
                else
                {
                    m_FreeList.RemoveAt(0);
                    Instance.m_PoolUsageFrequency[m_Prefab.GetInstanceID()] = 1;
                }
            }
            if (_go == null)
            {
                _go = Instantiate(m_Prefab, Constants.Special_Hide_Position, Quaternion.identity);
                m_ActiveList.Add(_go);
                m_ActiveHashSet.Add(_go);
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                DebugItem _debugItem = new GameObject(_go.GetInstanceID().ToString()).AddMissingComponent<DebugItem>();
                _debugItem.transform.SetParent(m_DebugActive);
                _debugItem.target = _go;
                Instance.m_DebugInstIDDict[_go.GetInstanceID()] = _debugItem;
#endif
            }
            _go.transform.SetParent(null);
            _go.transform.localScale = Vector3.one;
            // 更新池统计数据
            Instance.UpdatePoolStats();
            return _go;
        }
        public void Release(GameObject go)
        {
@@ -261,24 +446,19 @@
            {
                return;
            }
            if (m_ActiveList.Contains(go))
            {
                m_ActiveList.Remove(go);
            }
            if (m_FreeList.Contains(go))
            {
                return;
            }
            m_ActiveHashSet.Remove(go);
            m_FreeList.Add(go);
            m_FreeQueue.Enqueue(go);
            go.transform.SetParent(Instance.m_TargetContainer);
            go.transform.position = Constants.Special_Hide_Position;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
            if (Instance.m_DebugInstIDDict.ContainsKey(go.GetInstanceID()))
#if UNITY_EDITOR
            // 检查空闲列表数量是否超过阈值
            if (m_FreeQueue.Count > Pool_FreeList_Warning_Threshold)
            {
                DebugItem _debugItem = Instance.m_DebugInstIDDict[go.GetInstanceID()];
                _debugItem.transform.SetParent(m_DebugFree);
                Debug.LogWarning($"GameObjectPool {m_Prefab.name} 的空闲对象数量 ({m_FreeQueue.Count}) 超过阈值 ({Pool_FreeList_Warning_Threshold}),请检查是否需要清理。");
            }
#endif
@@ -286,98 +466,96 @@
            {
                releaseCallBack(go);
            }
            // 更新池统计数据
            Instance.UpdatePoolStats();
        }
        public void ReleaseAll()
        {
            for (int i = m_ActiveList.Count - 1; i >= 0; i--)
            //释放 m_ActiveHashSet中 的GameObject,排除
            foreach (GameObject go in m_ActiveHashSet)
            {
                var _go = m_ActiveList[i];
                m_ActiveList.Remove(_go);
                if (!m_FreeList.Contains(_go))
                if (go != null)
                {
                    m_FreeList.Add(_go);
                }
                    m_FreeQueue.Enqueue(go);
                    go.transform.SetParent(Instance.m_TargetContainer);
                    go.transform.position = Constants.Special_Hide_Position;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                DebugItem _debugItem = Instance.m_DebugInstIDDict[_go.GetInstanceID()];
                _debugItem.transform.SetParent(m_DebugFree);
#endif
                _go.transform.SetParent(Instance.m_TargetContainer);
                _go.transform.position = Constants.Special_Hide_Position;
                if (releaseCallBack != null)
                {
                    releaseCallBack(_go);
                    if (releaseCallBack != null)
                    {
                        releaseCallBack(go);
                    }
                }
            }
            m_ActiveHashSet.Clear();
            // 更新池统计数据
            Instance.UpdatePoolStats();
        }
        public void Clear()
        {
            for (int i = m_ActiveList.Count - 1; i >= 0; --i)
            foreach (GameObject go in m_ActiveHashSet)
            {
                if (m_ActiveList[i] == null
                || Instance.dontDestoryGoInstIDList.Contains(m_ActiveList[i].GetInstanceID()))
                    continue;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                Destroy(Instance.m_DebugInstIDDict[m_ActiveList[i].GetInstanceID()].gameObject);
#endif
                Destroy(m_ActiveList[i]);
                m_ActiveList.RemoveAt(i);
            }
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
            if (m_ActiveList.Count != 0)
            {
                Debug.LogWarningFormat("{0} 池清理后, 还有 {1} 个活跃对象. ", m_Prefab.name, m_ActiveList.Count);
                for (int i = 0; i < m_ActiveList.Count; ++i)
                if (go != null)
                {
                    Debug.LogWarningFormat(" |-- {0}", m_ActiveList[i].name);
                    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
            for (int i = m_FreeList.Count - 1; i >= 0; --i)
            Queue<GameObject> tempQueue = new Queue<GameObject>();
            while (m_FreeQueue.Count > 0)
            {
                if (m_FreeList[i] == null
                 || Instance.dontDestoryGoInstIDList.Contains(m_FreeList[i].GetInstanceID()))
                    continue;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                Destroy(Instance.m_DebugInstIDDict[m_FreeList[i].GetInstanceID()].gameObject);
#endif
                Destroy(m_FreeList[i]);
                m_FreeList.RemoveAt(i);
                GameObject obj = m_FreeQueue.Dequeue();
                if (Instance.dontDestoryGoInstIDList.Contains(obj.GetInstanceID()))
                    tempQueue.Enqueue(obj);
                else
                    Destroy(obj);
            }
            m_FreeQueue = tempQueue;
#if UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
            if (m_FreeList.Count != 0)
#if UNITY_EDITOR
            if (m_FreeQueue.Count != 0)
            {
                Debug.LogWarningFormat("{0} 池清理后, 还有 {1} 个空闲对象. ", m_Prefab.name, m_FreeList.Count);
                for (int i = 0; i < m_FreeList.Count; ++i)
                {
                    Debug.LogWarningFormat(" |-- {0}", m_FreeList[i].name);
                }
                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 UNITY_EDITOR && !UNITY_ANDROID && !UNITY_IOS
                Destroy(m_DebugContainer.gameObject);
#endif
            }
            if (!string.IsNullOrEmpty(assetBundleName) && !string.IsNullOrEmpty(assetName))
            {
                ResManager.Instance.UnloadAsset(assetBundleName);
                ResManager.Instance.UnloadAsset(assetBundleName, assetName);
                // ResManager.Instance.UnloadAssetBundle(assetBundleName, true, false);
            }
        }
        public bool IsEmpty()
        {
            return m_ActiveList.Count == 0 && m_FreeList.Count == 0;
            return m_ActiveHashSet.Count == 0 && m_FreeQueue.Count == 0;
        }
    }
}