From aa37eecf8b03b4d6d40cc506aa5f932f9fe2082e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Thu, 23 Apr 2026 19:09:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96ObjectPool=E5=92=8CMemoryPool?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Inspector/MemoryPoolComponentInspector.cs | 286 ++++++++++-------- .../ObjectPoolComponentInspector.cs | 44 ++- ...erComponent.ObjectPoolInformationWindow.cs | 52 +++- ...omponent.ReferencePoolInformationWindow.cs | 146 +++++---- Runtime/MemoryPool/MemoryPool.Core.cs | 4 +- Runtime/MemoryPool/MemoryPool.cs | 5 + Runtime/MemoryPool/MemoryPoolInfo.cs | 69 ++--- Runtime/MemoryPool/MemoryPoolRegistry.cs | 33 +- Runtime/ObjectPool/IObjectPoolDebugView.cs | 7 + .../ObjectPool/IObjectPoolDebugView.cs.meta | 11 + Runtime/ObjectPool/IObjectPoolService.cs | 7 - .../ObjectPool/IObjectPoolServiceDebugView.cs | 7 + .../IObjectPoolServiceDebugView.cs.meta | 11 + Runtime/ObjectPool/ObjectPoolBase.cs | 18 +- Runtime/ObjectPool/ObjectPoolComponent.cs | 7 +- .../ObjectPoolService.ObjectPool.cs | 74 ++--- Runtime/ObjectPool/ObjectPoolService.cs | 58 ++-- 17 files changed, 515 insertions(+), 324 deletions(-) create mode 100644 Runtime/ObjectPool/IObjectPoolDebugView.cs create mode 100644 Runtime/ObjectPool/IObjectPoolDebugView.cs.meta create mode 100644 Runtime/ObjectPool/IObjectPoolServiceDebugView.cs create mode 100644 Runtime/ObjectPool/IObjectPoolServiceDebugView.cs.meta diff --git a/Editor/Inspector/MemoryPoolComponentInspector.cs b/Editor/Inspector/MemoryPoolComponentInspector.cs index a825670..c17ac88 100644 --- a/Editor/Inspector/MemoryPoolComponentInspector.cs +++ b/Editor/Inspector/MemoryPoolComponentInspector.cs @@ -11,12 +11,13 @@ namespace AlicizaX.Editor [CustomEditor(typeof(MemoryPoolSetting))] internal sealed class MemoryPoolComponentInspector : GameFrameworkInspector { - private readonly Dictionary> m_ReferencePoolInfos = new Dictionary>(StringComparer.Ordinal); + private readonly Dictionary> m_GroupedIndices = new Dictionary>(StringComparer.Ordinal); + private readonly List m_ActiveAssemblyKeys = new List(16); private readonly HashSet m_OpenedItems = new HashSet(); + private MemoryPoolInfo[] m_InfoBuffer = Array.Empty(); - private SerializedProperty m_EnableStrictCheck = null; - - private bool m_ShowFullClassName = false; + private SerializedProperty m_EnableStrictCheck; + private bool m_ShowFullClassName; public override void OnInspectorGUI() { @@ -24,114 +25,10 @@ namespace AlicizaX.Editor serializedObject.Update(); - MemoryPoolSetting t = (MemoryPoolSetting)target; - - if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject)) + MemoryPoolSetting setting = (MemoryPoolSetting)target; + if (EditorApplication.isPlaying && IsPrefabInHierarchy(setting.gameObject)) { - bool enableStrictCheck = EditorGUILayout.Toggle("Enable Strict Check", t.EnableStrictCheck); - if (enableStrictCheck != t.EnableStrictCheck) - { - t.EnableStrictCheck = enableStrictCheck; - } - - EditorGUILayout.LabelField("Memory Pool Count", MemoryPool.Count.ToString()); - m_ShowFullClassName = EditorGUILayout.Toggle("Show Full Class Name", m_ShowFullClassName); - - // 全局统计 - MemoryPoolInfo[] referencePoolInfos = MemoryPool.GetAllMemoryPoolInfos(); - int totalUnused = 0, totalUsing = 0, totalArrayLen = 0; - foreach (var info in referencePoolInfos) - { - totalUnused += info.UnusedCount; - totalUsing += info.UsingCount; - totalArrayLen += info.PoolArrayLength; - } - EditorGUILayout.LabelField("Total Cached", totalUnused.ToString()); - EditorGUILayout.LabelField("Total In Use", totalUsing.ToString()); - EditorGUILayout.LabelField("Total Array Capacity", totalArrayLen.ToString()); - - EditorGUILayout.Space(); - - // 全局操作按钮 - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("Clear All Pools")) - { - MemoryPoolRegistry.ClearAll(); - } - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.Space(); - - // 按 Assembly 分组 - m_ReferencePoolInfos.Clear(); - foreach (MemoryPoolInfo referencePoolInfo in referencePoolInfos) - { - string assemblyName = referencePoolInfo.Type.Assembly.GetName().Name; - if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out List results)) - { - results = new List(); - m_ReferencePoolInfos.Add(assemblyName, results); - } - results.Add(referencePoolInfo); - } - - foreach (KeyValuePair> assemblyReferencePoolInfo in m_ReferencePoolInfos) - { - bool lastState = m_OpenedItems.Contains(assemblyReferencePoolInfo.Key); - bool currentState = EditorGUILayout.Foldout(lastState, assemblyReferencePoolInfo.Key); - if (currentState != lastState) - { - if (currentState) - m_OpenedItems.Add(assemblyReferencePoolInfo.Key); - else - m_OpenedItems.Remove(assemblyReferencePoolInfo.Key); - } - - if (currentState) - { - EditorGUILayout.BeginVertical("box"); - { - var label = "Unused\tUsing\tAcquire\tRelease\tCreated\tHiWater\tMaxCap\tIdle\tArrLen"; - EditorGUILayout.LabelField(m_ShowFullClassName ? "Full Class Name" : "Class Name", label); - assemblyReferencePoolInfo.Value.Sort(Comparison); - foreach (MemoryPoolInfo referencePoolInfo in assemblyReferencePoolInfo.Value) - { - DrawReferencePoolInfo(referencePoolInfo); - } - - if (GUILayout.Button("Export CSV Data")) - { - string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Memory Pool Data - {0}.csv", assemblyReferencePoolInfo.Key), string.Empty); - if (!string.IsNullOrEmpty(exportFileName)) - { - try - { - int index = 0; - string[] data = new string[assemblyReferencePoolInfo.Value.Count + 1]; - data[index++] = "Class Name,Full Class Name,Unused,Using,Acquire,Release,Created,HighWaterMark,MaxCapacity,IdleFrames,ArrayLength"; - foreach (MemoryPoolInfo info in assemblyReferencePoolInfo.Value) - { - data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}", - info.Type.Name, info.Type.FullName, - info.UnusedCount, info.UsingCount, - info.AcquireCount, info.ReleaseCount, - info.CreateCount, info.HighWaterMark, - info.MaxCapacity, info.IdleFrames, info.PoolArrayLength); - } - File.WriteAllLines(exportFileName, data, Encoding.UTF8); - Debug.Log(Utility.Text.Format("Export memory pool CSV data to '{0}' success.", exportFileName)); - } - catch (Exception exception) - { - Debug.LogError(Utility.Text.Format("Export memory pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception)); - } - } - } - } - EditorGUILayout.EndVertical(); - EditorGUILayout.Separator(); - } - } + DrawRuntimeInspector(setting); } else { @@ -139,7 +36,6 @@ namespace AlicizaX.Editor } serializedObject.ApplyModifiedProperties(); - Repaint(); } @@ -148,8 +44,116 @@ namespace AlicizaX.Editor m_EnableStrictCheck = serializedObject.FindProperty("m_EnableStrictCheck"); } - private void DrawReferencePoolInfo(MemoryPoolInfo info) + private void DrawRuntimeInspector(MemoryPoolSetting setting) { + bool enableStrictCheck = EditorGUILayout.Toggle("Enable Strict Check", setting.EnableStrictCheck); + if (enableStrictCheck != setting.EnableStrictCheck) + setting.EnableStrictCheck = enableStrictCheck; + + EditorGUILayout.LabelField("Memory Pool Count", MemoryPool.Count.ToString()); + m_ShowFullClassName = EditorGUILayout.Toggle("Show Full Class Name", m_ShowFullClassName); + + int infoCount = FetchInfos(); + DrawOverview(infoCount); + + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Clear All Pools")) + MemoryPoolRegistry.ClearAll(); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + + RebuildGroups(infoCount); + for (int i = 0; i < m_ActiveAssemblyKeys.Count; i++) + { + string assemblyName = m_ActiveAssemblyKeys[i]; + List indices = m_GroupedIndices[assemblyName]; + + bool lastState = m_OpenedItems.Contains(assemblyName); + bool currentState = EditorGUILayout.Foldout(lastState, assemblyName); + if (currentState != lastState) + { + if (currentState) + m_OpenedItems.Add(assemblyName); + else + m_OpenedItems.Remove(assemblyName); + } + + if (!currentState) + continue; + + indices.Sort(m_ShowFullClassName ? CompareFullClassName : CompareNormalClassName); + + EditorGUILayout.BeginVertical("box"); + string label = "Unused\tUsing\tAcquire\tRelease\tCreated\tHiWater\tMaxCap\tIdle\tArrLen"; + EditorGUILayout.LabelField(m_ShowFullClassName ? "Full Class Name" : "Class Name", label); + + for (int j = 0; j < indices.Count; j++) + DrawReferencePoolInfo(indices[j]); + + if (GUILayout.Button("Export CSV Data")) + ExportCsv(assemblyName, indices); + + EditorGUILayout.EndVertical(); + EditorGUILayout.Separator(); + } + } + + private int FetchInfos() + { + int poolCount = MemoryPool.Count; + if (m_InfoBuffer.Length < poolCount) + m_InfoBuffer = new MemoryPoolInfo[GetBufferCapacity(poolCount)]; + + return MemoryPool.GetAllMemoryPoolInfos(m_InfoBuffer); + } + + private void DrawOverview(int infoCount) + { + int totalUnused = 0; + int totalUsing = 0; + int totalArrayLen = 0; + for (int i = 0; i < infoCount; i++) + { + ref MemoryPoolInfo info = ref m_InfoBuffer[i]; + totalUnused += info.UnusedCount; + totalUsing += info.UsingCount; + totalArrayLen += info.PoolArrayLength; + } + + EditorGUILayout.LabelField("Total Cached", totalUnused.ToString()); + EditorGUILayout.LabelField("Total In Use", totalUsing.ToString()); + EditorGUILayout.LabelField("Total Array Capacity", totalArrayLen.ToString()); + } + + private void RebuildGroups(int infoCount) + { + foreach (KeyValuePair> pair in m_GroupedIndices) + pair.Value.Clear(); + + m_ActiveAssemblyKeys.Clear(); + for (int i = 0; i < infoCount; i++) + { + ref MemoryPoolInfo info = ref m_InfoBuffer[i]; + string assemblyName = info.Type.Assembly.GetName().Name; + if (!m_GroupedIndices.TryGetValue(assemblyName, out List indices)) + { + indices = new List(8); + m_GroupedIndices.Add(assemblyName, indices); + } + + if (indices.Count == 0) + m_ActiveAssemblyKeys.Add(assemblyName); + + indices.Add(i); + } + + m_ActiveAssemblyKeys.Sort(StringComparer.Ordinal); + } + + private void DrawReferencePoolInfo(int bufferIndex) + { + ref MemoryPoolInfo info = ref m_InfoBuffer[bufferIndex]; string name = m_ShowFullClassName ? info.Type.FullName : info.Type.Name; string values = Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}", info.UnusedCount, info.UsingCount, @@ -159,12 +163,58 @@ namespace AlicizaX.Editor EditorGUILayout.LabelField(name, values); } - private int Comparison(MemoryPoolInfo a, MemoryPoolInfo b) + private void ExportCsv(string assemblyName, List indices) { - if (m_ShowFullClassName) - return a.Type.FullName.CompareTo(b.Type.FullName); - else - return a.Type.Name.CompareTo(b.Type.Name); + string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Memory Pool Data - {0}.csv", assemblyName), string.Empty); + if (string.IsNullOrEmpty(exportFileName)) + return; + + try + { + int index = 0; + string[] data = new string[indices.Count + 1]; + data[index++] = "Class Name,Full Class Name,Unused,Using,Acquire,Release,Created,HighWaterMark,MaxCapacity,IdleFrames,ArrayLength"; + for (int i = 0; i < indices.Count; i++) + { + ref MemoryPoolInfo info = ref m_InfoBuffer[indices[i]]; + data[index++] = Utility.Text.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}", + info.Type.Name, info.Type.FullName, + info.UnusedCount, info.UsingCount, + info.AcquireCount, info.ReleaseCount, + info.CreateCount, info.HighWaterMark, + info.MaxCapacity, info.IdleFrames, info.PoolArrayLength); + } + + File.WriteAllLines(exportFileName, data, Encoding.UTF8); + Debug.Log(Utility.Text.Format("Export memory pool CSV data to '{0}' success.", exportFileName)); + } + catch (Exception exception) + { + Debug.LogError(Utility.Text.Format("Export memory pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception)); + } + } + + private int CompareNormalClassName(int leftIndex, int rightIndex) + { + ref MemoryPoolInfo left = ref m_InfoBuffer[leftIndex]; + ref MemoryPoolInfo right = ref m_InfoBuffer[rightIndex]; + return left.Type.Name.CompareTo(right.Type.Name); + } + + private int CompareFullClassName(int leftIndex, int rightIndex) + { + ref MemoryPoolInfo left = ref m_InfoBuffer[leftIndex]; + ref MemoryPoolInfo right = ref m_InfoBuffer[rightIndex]; + return left.Type.FullName.CompareTo(right.Type.FullName); + } + + private static int GetBufferCapacity(int count) + { + int capacity = 8; + while (capacity < count) + capacity <<= 1; + + return capacity; } } } diff --git a/Editor/ObjectPool/ObjectPoolComponentInspector.cs b/Editor/ObjectPool/ObjectPoolComponentInspector.cs index fa2d75c..52e16ad 100644 --- a/Editor/ObjectPool/ObjectPoolComponentInspector.cs +++ b/Editor/ObjectPool/ObjectPoolComponentInspector.cs @@ -13,6 +13,8 @@ namespace AlicizaX.Editor internal sealed class ObjectPoolComponentInspector : GameFrameworkInspector { private readonly HashSet m_OpenedItems = new HashSet(); + private ObjectPoolBase[] m_ObjectPools = new ObjectPoolBase[1]; + private ObjectInfo[] m_ObjectInfos = new ObjectInfo[1]; public override void OnInspectorGUI() { @@ -30,10 +32,11 @@ namespace AlicizaX.Editor { EditorGUILayout.LabelField("Object Pool Count", t.Count.ToString()); - ObjectPoolBase[] objectPools = t.GetAllObjectPools(true); - foreach (ObjectPoolBase objectPool in objectPools) + int objectPoolCount = EnsureObjectPoolBuffer(t.Count); + objectPoolCount = t.GetAllObjectPools(true, m_ObjectPools); + for (int i = 0; i < objectPoolCount; i++) { - DrawObjectPool(objectPool); + DrawObjectPool(m_ObjectPools[i]); } } @@ -69,12 +72,14 @@ namespace AlicizaX.Editor EditorGUILayout.LabelField("Used Count", objectPool.Count.ToString()); EditorGUILayout.LabelField("Expire Time", objectPool.ExpireTime.ToString()); EditorGUILayout.LabelField("Priority", objectPool.Priority.ToString()); - ObjectInfo[] objectInfos = objectPool.GetAllObjectInfos(); - if (objectInfos.Length > 0) + int objectInfoCount = EnsureObjectInfoBuffer(objectPool.Count); + objectInfoCount = objectPool.GetAllObjectInfos(m_ObjectInfos); + if (objectInfoCount > 0) { EditorGUILayout.LabelField("Name", objectPool.AllowMultiSpawn ? "Locked\tCount\tFlag\tLast Use Time" : "Locked\tIn Use\tFlag\tLast Use Time"); - foreach (ObjectInfo objectInfo in objectInfos) + for (int i = 0; i < objectInfoCount; i++) { + ObjectInfo objectInfo = m_ObjectInfos[i]; #if UNITY_6000_0_OR_NEWER string lastUse = Utility.Text.Format("{0:F1}s ago", UnityEngine.Time.realtimeSinceStartup - objectInfo.LastUseTime); @@ -109,10 +114,11 @@ namespace AlicizaX.Editor try { int index = 0; - string[] data = new string[objectInfos.Length + 1]; + string[] data = new string[objectInfoCount + 1]; data[index++] = Utility.Text.Format("Name,Locked,{0},Custom Can Release Flag,Last Use Time", objectPool.AllowMultiSpawn ? "Count" : "In Use"); - foreach (ObjectInfo objectInfo in objectInfos) + for (int i = 0; i < objectInfoCount; i++) { + ObjectInfo objectInfo = m_ObjectInfos[i]; string csvLastUse = Utility.Text.Format("{0:F1}s ago", UnityEngine.Time.realtimeSinceStartup - objectInfo.LastUseTime); data[index++] = objectPool.AllowMultiSpawn ? Utility.Text.Format("{0},{1},{2},{3},{4}", objectInfo.Name, objectInfo.Locked, objectInfo.SpawnCount, objectInfo.CustomCanReleaseFlag, csvLastUse) @@ -139,5 +145,27 @@ namespace AlicizaX.Editor EditorGUILayout.Separator(); } } + + private int EnsureObjectPoolBuffer(int count) + { + if (count <= 0) + return 0; + + if (m_ObjectPools == null || m_ObjectPools.Length < count) + m_ObjectPools = new ObjectPoolBase[count]; + + return count; + } + + private int EnsureObjectInfoBuffer(int count) + { + if (count <= 0) + return 0; + + if (m_ObjectInfos == null || m_ObjectInfos.Length < count) + m_ObjectInfos = new ObjectInfo[count]; + + return count; + } } } diff --git a/Runtime/Debugger/DebuggerComponent.ObjectPoolInformationWindow.cs b/Runtime/Debugger/DebuggerComponent.ObjectPoolInformationWindow.cs index e94bac4..79f29ee 100644 --- a/Runtime/Debugger/DebuggerComponent.ObjectPoolInformationWindow.cs +++ b/Runtime/Debugger/DebuggerComponent.ObjectPoolInformationWindow.cs @@ -9,10 +9,14 @@ namespace AlicizaX.Debugger.Runtime private sealed class ObjectPoolInformationWindow : PollingDebuggerWindowBase { private IObjectPoolService m_ObjectPoolService; + private IObjectPoolServiceDebugView m_ObjectPoolDebugView; + private ObjectPoolBase[] m_ObjectPools; + private ObjectInfo[] m_ObjectInfos; public override void Initialize(params object[] args) { m_ObjectPoolService = AppServices.Require(); + m_ObjectPoolDebugView = m_ObjectPoolService as IObjectPoolServiceDebugView; } protected override void BuildWindow(VisualElement root) @@ -26,10 +30,13 @@ namespace AlicizaX.Debugger.Runtime overviewCard.Add(CreateRow("Object Pool Count", m_ObjectPoolService.Count.ToString())); root.Add(overview); - ObjectPoolBase[] objectPools = m_ObjectPoolService.GetAllObjectPools(true); - for (int i = 0; i < objectPools.Length; i++) + int objectPoolCount = EnsureObjectPoolBuffer(m_ObjectPoolService.Count); + objectPoolCount = m_ObjectPoolDebugView != null + ? m_ObjectPoolDebugView.GetAllObjectPools(true, m_ObjectPools) + : 0; + for (int i = 0; i < objectPoolCount; i++) { - ObjectPoolBase objectPool = objectPools[i]; + ObjectPoolBase objectPool = m_ObjectPools[i]; VisualElement section = CreateSection(Utility.Text.Format("Object Pool: {0}", objectPool.FullName), out VisualElement card); card.Add(CreateRow("Name", objectPool.Name)); card.Add(CreateRow("Type", objectPool.ObjectType.FullName)); @@ -39,16 +46,17 @@ namespace AlicizaX.Debugger.Runtime card.Add(CreateRow("Expire Time", objectPool.ExpireTime.ToString())); card.Add(CreateRow("Priority", objectPool.Priority.ToString())); - ObjectInfo[] objectInfos = objectPool.GetAllObjectInfos(); - if (objectInfos.Length <= 0) + int objectInfoCount = EnsureObjectInfoBuffer(objectPool.Count); + objectInfoCount = objectPool.GetAllObjectInfos(m_ObjectInfos); + if (objectInfoCount <= 0) { card.Add(CreateRow("Entries", "Object Pool is Empty ...")); } else { - for (int j = 0; j < objectInfos.Length; j++) + for (int j = 0; j < objectInfoCount; j++) { - ObjectInfo info = objectInfos[j]; + ObjectInfo info = m_ObjectInfos[j]; string title = string.IsNullOrEmpty(info.Name) ? "" : info.Name; string content = Utility.Text.Format( "Locked {0} | {1} {2} | Flag {3} | Last Use {4}", @@ -64,6 +72,36 @@ namespace AlicizaX.Debugger.Runtime root.Add(section); } } + + private int EnsureObjectPoolBuffer(int count) + { + if (count <= 0) + { + if (m_ObjectPools == null || m_ObjectPools.Length == 0) + m_ObjectPools = new ObjectPoolBase[1]; + return 0; + } + + if (m_ObjectPools == null || m_ObjectPools.Length < count) + m_ObjectPools = new ObjectPoolBase[count]; + + return count; + } + + private int EnsureObjectInfoBuffer(int count) + { + if (count <= 0) + { + if (m_ObjectInfos == null || m_ObjectInfos.Length == 0) + m_ObjectInfos = new ObjectInfo[1]; + return 0; + } + + if (m_ObjectInfos == null || m_ObjectInfos.Length < count) + m_ObjectInfos = new ObjectInfo[count]; + + return count; + } } } } diff --git a/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs b/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs index c88cc0e..a215970 100644 --- a/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs +++ b/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs @@ -9,98 +9,145 @@ namespace AlicizaX.Debugger.Runtime { private sealed class ReferencePoolInformationWindow : PollingDebuggerWindowBase { - private readonly Dictionary> m_ReferencePoolInfos = new Dictionary>(StringComparer.Ordinal); - private readonly Comparison m_NormalClassNameComparer = NormalClassNameComparer; - private readonly Comparison m_FullClassNameComparer = FullClassNameComparer; + private readonly Dictionary> m_GroupedIndices = new Dictionary>(StringComparer.Ordinal); + private readonly List m_ActiveAssemblyKeys = new List(16); + private readonly Comparison m_NormalIndexComparer; + private readonly Comparison m_FullIndexComparer; + + private MemoryPoolInfo[] m_InfoBuffer = Array.Empty(); private bool m_ShowFullClassName; - private Toggle showFullClassNameToggle; + private Toggle m_ShowFullClassNameToggle; + + public ReferencePoolInformationWindow() + { + m_NormalIndexComparer = CompareNormalClassName; + m_FullIndexComparer = CompareFullClassName; + } protected override void BuildWindow(VisualElement root) { float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; + int infoCount = FetchInfos(); - // ---- Overview Section ---- VisualElement overview = CreateSection("Memory Pool Overview", out VisualElement overviewCard); overviewCard.Add(CreateRow("Enable Strict Check", MemoryPool.EnableStrictCheck.ToString())); overviewCard.Add(CreateRow("Pool Type Count", MemoryPool.Count.ToString())); - // 统计总缓存对象数和总数组容量 - MemoryPoolInfo[] allInfos = MemoryPool.GetAllMemoryPoolInfos(); int totalUnused = 0; int totalUsing = 0; int totalArrayLen = 0; - foreach (var info in allInfos) + for (int i = 0; i < infoCount; i++) { + ref MemoryPoolInfo info = ref m_InfoBuffer[i]; totalUnused += info.UnusedCount; totalUsing += info.UsingCount; totalArrayLen += info.PoolArrayLength; } + overviewCard.Add(CreateRow("Total Cached Objects", totalUnused.ToString())); overviewCard.Add(CreateRow("Total In Use", totalUsing.ToString())); overviewCard.Add(CreateRow("Total Array Capacity", totalArrayLen.ToString())); - showFullClassNameToggle = CreateConsoleFilterToggle("Show Full ClassName", m_ShowFullClassName, DebuggerTheme.PrimaryText, value => m_ShowFullClassName = value); - overviewCard.Add(showFullClassNameToggle); + m_ShowFullClassNameToggle = CreateConsoleFilterToggle("Show Full ClassName", m_ShowFullClassName, DebuggerTheme.PrimaryText, OnShowFullClassNameChanged); + overviewCard.Add(m_ShowFullClassNameToggle); - // ---- 操作按钮 ---- VisualElement buttonRow = new VisualElement(); buttonRow.style.flexDirection = FlexDirection.Row; buttonRow.style.marginTop = 8f * scale; - - buttonRow.Add(CreateActionButton("Clear All Pools", () => - { - MemoryPoolRegistry.ClearAll(); - Rebuild(); - }, DebuggerTheme.Danger)); - + buttonRow.Add(CreateActionButton("Clear All Pools", OnClearAllPools, DebuggerTheme.Danger)); overviewCard.Add(buttonRow); root.Add(overview); - // ---- 按 Assembly 分组 ---- - m_ReferencePoolInfos.Clear(); - foreach (MemoryPoolInfo info in allInfos) + RebuildGroups(infoCount); + for (int i = 0; i < m_ActiveAssemblyKeys.Count; i++) { - string assemblyName = info.Type.Assembly.GetName().Name; - if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out List results)) - { - results = new List(); - m_ReferencePoolInfos.Add(assemblyName, results); - } - results.Add(info); - } + string assemblyKey = m_ActiveAssemblyKeys[i]; + List indices = m_GroupedIndices[assemblyKey]; + indices.Sort(m_ShowFullClassName ? m_FullIndexComparer : m_NormalIndexComparer); - foreach (KeyValuePair> assemblyInfo in m_ReferencePoolInfos) - { - assemblyInfo.Value.Sort(m_ShowFullClassName ? m_FullClassNameComparer : m_NormalClassNameComparer); - VisualElement section = CreateSection(Utility.Text.Format("Assembly: {0}", assemblyInfo.Key), out VisualElement card); - - if (assemblyInfo.Value.Count <= 0) + VisualElement section = CreateSection(Utility.Text.Format("Assembly: {0}", assemblyKey), out VisualElement card); + for (int j = 0; j < indices.Count; j++) { - card.Add(CreateRow("State", "Memory Pool is Empty ...")); - } - else - { - for (int i = 0; i < assemblyInfo.Value.Count; i++) - { - card.Add(CreatePoolInfoItem(assemblyInfo.Value[i], m_ShowFullClassName, scale)); - } + card.Add(CreatePoolInfoItem(in m_InfoBuffer[indices[j]], m_ShowFullClassName, scale)); } root.Add(section); } } - private static int NormalClassNameComparer(MemoryPoolInfo a, MemoryPoolInfo b) + private int FetchInfos() { - return a.Type.Name.CompareTo(b.Type.Name); + int poolCount = MemoryPool.Count; + if (m_InfoBuffer.Length < poolCount) + m_InfoBuffer = new MemoryPoolInfo[GetBufferCapacity(poolCount)]; + + return MemoryPool.GetAllMemoryPoolInfos(m_InfoBuffer); } - private static int FullClassNameComparer(MemoryPoolInfo a, MemoryPoolInfo b) + private void RebuildGroups(int infoCount) { - return a.Type.FullName.CompareTo(b.Type.FullName); + foreach (KeyValuePair> pair in m_GroupedIndices) + pair.Value.Clear(); + + m_ActiveAssemblyKeys.Clear(); + for (int i = 0; i < infoCount; i++) + { + ref MemoryPoolInfo info = ref m_InfoBuffer[i]; + string assemblyName = info.Type.Assembly.GetName().Name; + if (!m_GroupedIndices.TryGetValue(assemblyName, out List indices)) + { + indices = new List(8); + m_GroupedIndices.Add(assemblyName, indices); + } + + if (indices.Count == 0) + m_ActiveAssemblyKeys.Add(assemblyName); + + indices.Add(i); + } + + m_ActiveAssemblyKeys.Sort(StringComparer.Ordinal); } - private static VisualElement CreatePoolInfoItem(MemoryPoolInfo info, bool showFullName, float scale) + private void OnShowFullClassNameChanged(bool value) + { + if (m_ShowFullClassName == value) + return; + + m_ShowFullClassName = value; + Rebuild(); + } + + private void OnClearAllPools() + { + MemoryPoolRegistry.ClearAll(); + Rebuild(); + } + + private int CompareNormalClassName(int leftIndex, int rightIndex) + { + ref MemoryPoolInfo left = ref m_InfoBuffer[leftIndex]; + ref MemoryPoolInfo right = ref m_InfoBuffer[rightIndex]; + return left.Type.Name.CompareTo(right.Type.Name); + } + + private int CompareFullClassName(int leftIndex, int rightIndex) + { + ref MemoryPoolInfo left = ref m_InfoBuffer[leftIndex]; + ref MemoryPoolInfo right = ref m_InfoBuffer[rightIndex]; + return left.Type.FullName.CompareTo(right.Type.FullName); + } + + private static int GetBufferCapacity(int count) + { + int capacity = 8; + while (capacity < count) + capacity <<= 1; + + return capacity; + } + + private static VisualElement CreatePoolInfoItem(in MemoryPoolInfo info, bool showFullName, float scale) { VisualElement item = CreateCard(); item.style.marginBottom = 8f * scale; @@ -115,7 +162,6 @@ namespace AlicizaX.Debugger.Runtime titleLabel.style.marginBottom = 6f * scale; item.Add(titleLabel); - // 基础统计 string stats = Utility.Text.Format( "Unused {0} | Using {1} | Acquire {2} | Release {3} | Created {4}", info.UnusedCount, info.UsingCount, @@ -128,7 +174,6 @@ namespace AlicizaX.Debugger.Runtime statsLabel.style.marginBottom = 4f * scale; item.Add(statsLabel); - // 回收策略状态 string recycleStatus = Utility.Text.Format( "HighWater {0} | MaxCap {1} | Idle {2}f | Array {3}", info.HighWaterMark, info.MaxCapacity, @@ -137,7 +182,6 @@ namespace AlicizaX.Debugger.Runtime recycleLabel.style.fontSize = 13f * scale; recycleLabel.style.whiteSpace = WhiteSpace.Normal; - // 根据空闲帧数着色:接近回收阈值时变色 if (info.IdleFrames >= 300) recycleLabel.style.color = DebuggerTheme.Warning; else if (info.IdleFrames >= 200) diff --git a/Runtime/MemoryPool/MemoryPool.Core.cs b/Runtime/MemoryPool/MemoryPool.Core.cs index cd8ef4f..3884d98 100644 --- a/Runtime/MemoryPool/MemoryPool.Core.cs +++ b/Runtime/MemoryPool/MemoryPool.Core.cs @@ -250,9 +250,9 @@ namespace AlicizaX get => s_Count; } - internal static MemoryPoolInfo GetInfo() + internal static void GetInfo(ref MemoryPoolInfo info) { - return new MemoryPoolInfo( + info.Set( typeof(T), s_Count, s_CurrentInUse, s_AcquireCount, s_ReleaseCount, diff --git a/Runtime/MemoryPool/MemoryPool.cs b/Runtime/MemoryPool/MemoryPool.cs index 5832093..c703de2 100644 --- a/Runtime/MemoryPool/MemoryPool.cs +++ b/Runtime/MemoryPool/MemoryPool.cs @@ -41,6 +41,11 @@ namespace AlicizaX return MemoryPoolRegistry.GetAllInfos(); } + public static int GetAllMemoryPoolInfos(MemoryPoolInfo[] infos) + { + return MemoryPoolRegistry.GetAllInfos(infos); + } + /// /// 清除所有内存池。 /// diff --git a/Runtime/MemoryPool/MemoryPoolInfo.cs b/Runtime/MemoryPool/MemoryPoolInfo.cs index e9bf602..60b60f8 100644 --- a/Runtime/MemoryPool/MemoryPoolInfo.cs +++ b/Runtime/MemoryPool/MemoryPoolInfo.cs @@ -4,21 +4,21 @@ using System.Runtime.InteropServices; namespace AlicizaX { /// - /// 内存池信息。 + /// Memory pool snapshot info. /// [StructLayout(LayoutKind.Auto)] public struct MemoryPoolInfo { - private readonly Type _type; - private readonly int _unusedCount; - private readonly int _usingCount; - private readonly int _acquireCount; - private readonly int _releaseCount; - private readonly int _createCount; - private readonly int _highWaterMark; - private readonly int _maxCapacity; - private readonly int _idleFrames; - private readonly int _poolArrayLength; + private Type _type; + private int _unusedCount; + private int _usingCount; + private int _acquireCount; + private int _releaseCount; + private int _createCount; + private int _highWaterMark; + private int _maxCapacity; + private int _idleFrames; + private int _poolArrayLength; public MemoryPoolInfo(Type type, int unusedCount, int usingCount, int acquireCount, int releaseCount, int createCount, @@ -37,54 +37,41 @@ namespace AlicizaX _poolArrayLength = poolArrayLength; } - /// - /// 池类型。 - /// public Type Type => _type; - /// - /// 池中空闲对象数量(可立即借出)。 - /// public int UnusedCount => _unusedCount; - /// - /// 当前被借出、尚未归还的对象数量。 - /// public int UsingCount => _usingCount; - /// - /// 累计 Acquire 调用次数(仅开发模式有效)。 - /// public int AcquireCount => _acquireCount; - /// - /// 累计 Release 调用次数(仅开发模式有效)。 - /// public int ReleaseCount => _releaseCount; - /// - /// 累计 new T() 创建次数(池不够时的实际分配,仅开发模式有效)。 - /// public int CreateCount => _createCount; - /// - /// 近期峰值并发使用量。回收策略据此决定保留多少对象。 - /// public int HighWaterMark => _highWaterMark; - /// - /// 池容量硬上限。超出后 Release 的对象直接丢弃交给 GC。 - /// public int MaxCapacity => _maxCapacity; - /// - /// 连续无 Acquire 的帧数。>=300 开始温和回收,>=900 激进回收+高水位衰减。 - /// public int IdleFrames => _idleFrames; - /// - /// 底层 T[] 数组的实际长度。反映真实内存占用(含空槽)。 - /// public int PoolArrayLength => _poolArrayLength; + + internal void Set(Type type, int unusedCount, int usingCount, + int acquireCount, int releaseCount, int createCount, + int highWaterMark, int maxCapacity, + int idleFrames, int poolArrayLength) + { + _type = type; + _unusedCount = unusedCount; + _usingCount = usingCount; + _acquireCount = acquireCount; + _releaseCount = releaseCount; + _createCount = createCount; + _highWaterMark = highWaterMark; + _maxCapacity = maxCapacity; + _idleFrames = idleFrames; + _poolArrayLength = poolArrayLength; + } } } diff --git a/Runtime/MemoryPool/MemoryPoolRegistry.cs b/Runtime/MemoryPool/MemoryPoolRegistry.cs index 0855008..e08fe43 100644 --- a/Runtime/MemoryPool/MemoryPoolRegistry.cs +++ b/Runtime/MemoryPool/MemoryPoolRegistry.cs @@ -13,7 +13,7 @@ namespace AlicizaX public delegate void ReleaseHandler(IMemory memory); public delegate void ClearHandler(); public delegate void IntHandler(int value); - public delegate MemoryPoolInfo GetInfoHandler(); + public delegate void GetInfoHandler(ref MemoryPoolInfo info); public readonly AcquireHandler Acquire; public readonly ReleaseHandler Release; @@ -99,12 +99,29 @@ namespace AlicizaX throw new Exception($"MemoryPool: Type '{type.FullName}' is not a valid IMemory type."); } + public static int GetAllInfos(MemoryPoolInfo[] infos) + { + if (infos == null) + throw new ArgumentNullException(nameof(infos)); + + int count = s_Handles.Count; + if (infos.Length < count) + throw new ArgumentException("Target buffer is too small.", nameof(infos)); + + int i = 0; + foreach (var kv in s_Handles) + { + kv.Value.GetInfo(ref infos[i]); + i++; + } + + return count; + } + public static MemoryPoolInfo[] GetAllInfos() { var infos = new MemoryPoolInfo[s_Handles.Count]; - int i = 0; - foreach (var kv in s_Handles) - infos[i++] = kv.Value.GetInfo(); + GetAllInfos(infos); return infos; } @@ -158,7 +175,9 @@ namespace AlicizaX if (s_Handles.TryGetValue(type, out var handle)) { - int unused = handle.GetInfo().UnusedCount; + MemoryPoolInfo info = default; + handle.GetInfo(ref info); + int unused = info.UnusedCount; handle.Shrink(unused - count); return; } @@ -166,7 +185,9 @@ namespace AlicizaX EnsureRegistered(type); if (s_Handles.TryGetValue(type, out handle)) { - int unused = handle.GetInfo().UnusedCount; + MemoryPoolInfo info = default; + handle.GetInfo(ref info); + int unused = info.UnusedCount; handle.Shrink(unused - count); return; } diff --git a/Runtime/ObjectPool/IObjectPoolDebugView.cs b/Runtime/ObjectPool/IObjectPoolDebugView.cs new file mode 100644 index 0000000..f310ec2 --- /dev/null +++ b/Runtime/ObjectPool/IObjectPoolDebugView.cs @@ -0,0 +1,7 @@ +namespace AlicizaX.ObjectPool +{ + internal interface IObjectPoolDebugView + { + int GetAllObjectInfos(ObjectInfo[] results); + } +} diff --git a/Runtime/ObjectPool/IObjectPoolDebugView.cs.meta b/Runtime/ObjectPool/IObjectPoolDebugView.cs.meta new file mode 100644 index 0000000..87ba027 --- /dev/null +++ b/Runtime/ObjectPool/IObjectPoolDebugView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b99f3fc658c4a3d4f80218ab7113341e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ObjectPool/IObjectPoolService.cs b/Runtime/ObjectPool/IObjectPoolService.cs index 5453732..8862518 100644 --- a/Runtime/ObjectPool/IObjectPoolService.cs +++ b/Runtime/ObjectPool/IObjectPoolService.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; - namespace AlicizaX.ObjectPool { public readonly struct ObjectPoolCreateOptions @@ -53,11 +51,6 @@ namespace AlicizaX.ObjectPool ObjectPoolBase GetObjectPool(Type objectType); ObjectPoolBase GetObjectPool(Type objectType, string name); - ObjectPoolBase[] GetAllObjectPools(); - ObjectPoolBase[] GetAllObjectPools(bool sort); - void GetAllObjectPools(List results); - void GetAllObjectPools(bool sort, List results); - IObjectPool CreatePool(ObjectPoolCreateOptions options = default) where T : ObjectBase; ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default); diff --git a/Runtime/ObjectPool/IObjectPoolServiceDebugView.cs b/Runtime/ObjectPool/IObjectPoolServiceDebugView.cs new file mode 100644 index 0000000..f2d0a56 --- /dev/null +++ b/Runtime/ObjectPool/IObjectPoolServiceDebugView.cs @@ -0,0 +1,7 @@ +namespace AlicizaX.ObjectPool +{ + internal interface IObjectPoolServiceDebugView + { + int GetAllObjectPools(bool sort, ObjectPoolBase[] results); + } +} diff --git a/Runtime/ObjectPool/IObjectPoolServiceDebugView.cs.meta b/Runtime/ObjectPool/IObjectPoolServiceDebugView.cs.meta new file mode 100644 index 0000000..dadcadb --- /dev/null +++ b/Runtime/ObjectPool/IObjectPoolServiceDebugView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e3fa6e0005d58a4eba0e005ee613c61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ObjectPool/ObjectPoolBase.cs b/Runtime/ObjectPool/ObjectPoolBase.cs index 2ea4056..5e8cee1 100644 --- a/Runtime/ObjectPool/ObjectPoolBase.cs +++ b/Runtime/ObjectPool/ObjectPoolBase.cs @@ -1,12 +1,11 @@ using System; -using System.Collections.Generic; - namespace AlicizaX.ObjectPool { - public abstract class ObjectPoolBase + public abstract class ObjectPoolBase : IObjectPoolDebugView { private readonly string m_Name; + private string m_FullName; public ObjectPoolBase() : this(null) { } @@ -17,7 +16,15 @@ namespace AlicizaX.ObjectPool public string Name => m_Name; - public string FullName => new TypeNamePair(ObjectType, m_Name).ToString(); + public string FullName + { + get + { + if (m_FullName == null) + m_FullName = new TypeNamePair(ObjectType, m_Name).ToString(); + return m_FullName; + } + } public abstract Type ObjectType { get; } public abstract int Count { get; } @@ -39,8 +46,7 @@ namespace AlicizaX.ObjectPool public abstract void Release(int toReleaseCount); public abstract void ReleaseAllUnused(); - public abstract ObjectInfo[] GetAllObjectInfos(); - public abstract void GetAllObjectInfos(List results); + public abstract int GetAllObjectInfos(ObjectInfo[] results); internal abstract void Update(float elapseSeconds, float realElapseSeconds); internal abstract void Shutdown(); diff --git a/Runtime/ObjectPool/ObjectPoolComponent.cs b/Runtime/ObjectPool/ObjectPoolComponent.cs index f12263c..a2646e9 100644 --- a/Runtime/ObjectPool/ObjectPoolComponent.cs +++ b/Runtime/ObjectPool/ObjectPoolComponent.cs @@ -28,9 +28,12 @@ namespace AlicizaX svc.OnLowMemory(); } - public ObjectPoolBase[] GetAllObjectPools(bool sort) + internal int GetAllObjectPools(bool sort, ObjectPoolBase[] results) { - return _mObjectPoolService.GetAllObjectPools(sort); + if (_mObjectPoolService is IObjectPoolServiceDebugView debugView) + return debugView.GetAllObjectPools(sort, results); + + return 0; } } } diff --git a/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs b/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs index a9557df..f03b233 100644 --- a/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs +++ b/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs @@ -184,24 +184,14 @@ namespace AlicizaX.ObjectPool return SpawnAny(name); if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return null; + ref var slot = ref m_Slots[head]; + if (!slot.IsAlive() || slot.SpawnCount != 0 || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal)) + throw new GameFrameworkException($"Object pool '{FullName}' available-name head is inconsistent."); float now = Time.realtimeSinceStartup; - int current = head; - while (current >= 0) - { - ref var slot = ref m_Slots[current]; - if (slot.IsAlive() && string.Equals(slot.Obj.Name, name) - && slot.SpawnCount == 0) - { - SpawnSlot(current, now); - ValidateState(); - return slot.Obj; - } - - current = slot.NextAvailableByName; - } - - return null; + SpawnSlot(head, now); + ValidateState(); + return slot.Obj; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -213,18 +203,7 @@ namespace AlicizaX.ObjectPool if (m_AllowMultiSpawn) return m_AllNameHeadMap.ContainsKey(name); - if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return false; - - int current = head; - while (current >= 0) - { - ref var slot = ref m_Slots[current]; - if (slot.IsAlive() && slot.SpawnCount == 0 && string.Equals(slot.Obj.Name, name)) - return true; - current = slot.NextAvailableByName; - } - - return false; + return m_AvailableNameHeadMap.ContainsKey(name); } public void Unspawn(T obj) @@ -334,34 +313,28 @@ namespace AlicizaX.ObjectPool ValidateState(); } - public override ObjectInfo[] GetAllObjectInfos() - { - var list = new ObjectInfo[m_TargetMap.Count]; - int write = 0; - for (int i = 0; i < m_SlotCount && write < list.Length; i++) - { - ref var slot = ref m_Slots[i]; - if (!slot.IsAlive()) continue; - list[write++] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked, - slot.Obj.CustomCanReleaseFlag, - slot.Obj.LastUseTime, slot.SpawnCount); - } - - return list; - } - - public override void GetAllObjectInfos(List results) + public override int GetAllObjectInfos(ObjectInfo[] results) { if (results == null) throw new GameFrameworkException("Results is invalid."); - results.Clear(); + + int write = 0; + int capacity = results.Length; for (int i = 0; i < m_SlotCount; i++) { ref var slot = ref m_Slots[i]; if (!slot.IsAlive()) continue; - results.Add(new ObjectInfo(slot.Obj.Name, slot.Obj.Locked, - slot.Obj.CustomCanReleaseFlag, - slot.Obj.LastUseTime, slot.SpawnCount)); + + if (write < capacity) + { + results[write] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked, + slot.Obj.CustomCanReleaseFlag, + slot.Obj.LastUseTime, slot.SpawnCount); + } + + write++; } + + return write; } internal override void OnLowMemory() @@ -704,7 +677,7 @@ namespace AlicizaX.ObjectPool { ref var slot = ref m_Slots[idx]; if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0) - return; + throw new GameFrameworkException($"Object pool '{FullName}' available-name chain is inconsistent."); string objectName = slot.Obj.Name ?? string.Empty; if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail)) @@ -766,6 +739,7 @@ namespace AlicizaX.ObjectPool return null; SpawnSlot(head, now); + ValidateState(); return slot.Obj; } } diff --git a/Runtime/ObjectPool/ObjectPoolService.cs b/Runtime/ObjectPool/ObjectPoolService.cs index 93f85b2..ca796de 100644 --- a/Runtime/ObjectPool/ObjectPoolService.cs +++ b/Runtime/ObjectPool/ObjectPoolService.cs @@ -6,7 +6,7 @@ namespace AlicizaX.ObjectPool { [UnityEngine.Scripting.Preserve] - internal sealed partial class ObjectPoolService : ServiceBase, IObjectPoolService, IServiceTickable + internal sealed partial class ObjectPoolService : ServiceBase, IObjectPoolService, IObjectPoolServiceDebugView, IServiceTickable { private const float DefaultAutoReleaseInterval = float.MaxValue; private const int DefaultCapacity = int.MaxValue; @@ -15,7 +15,7 @@ namespace AlicizaX.ObjectPool private readonly Dictionary m_ObjectPools; private readonly List m_ObjectPoolList; private readonly Dictionary m_ObjectPoolIndexMap; - private readonly List m_CachedAllObjectPools; + private readonly List m_CachedSortedObjectPools; private readonly Comparison m_ObjectPoolComparer; public ObjectPoolService() @@ -23,7 +23,7 @@ namespace AlicizaX.ObjectPool m_ObjectPools = new Dictionary(); m_ObjectPoolList = new List(); m_ObjectPoolIndexMap = new Dictionary(AlicizaX.ReferenceComparer.Instance); - m_CachedAllObjectPools = new List(); + m_CachedSortedObjectPools = new List(); m_ObjectPoolComparer = ObjectPoolComparer; } @@ -45,7 +45,7 @@ namespace AlicizaX.ObjectPool m_ObjectPools.Clear(); m_ObjectPoolList.Clear(); m_ObjectPoolIndexMap.Clear(); - m_CachedAllObjectPools.Clear(); + m_CachedSortedObjectPools.Clear(); } // ========== Has ========== @@ -90,24 +90,23 @@ namespace AlicizaX.ObjectPool // ========== GetAll ========== - public ObjectPoolBase[] GetAllObjectPools() => m_ObjectPoolList.ToArray(); - - public ObjectPoolBase[] GetAllObjectPools(bool sort) - { - if (!sort) return m_ObjectPoolList.ToArray(); - var results = new List(m_ObjectPoolList); - results.Sort(m_ObjectPoolComparer); - return results.ToArray(); - } - - public void GetAllObjectPools(List results) => GetAllObjectPools(false, results); - - public void GetAllObjectPools(bool sort, List results) + int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results) { if (results == null) throw new GameFrameworkException("Results is invalid."); - results.Clear(); - results.AddRange(m_ObjectPoolList); - if (sort) results.Sort(m_ObjectPoolComparer); + + List source = m_ObjectPoolList; + if (sort) + { + CacheSortedObjectPools(); + source = m_CachedSortedObjectPools; + } + + int count = source.Count; + int copyCount = results.Length < count ? results.Length : count; + for (int i = 0; i < copyCount; i++) + results[i] = source[i]; + + return count; } // ========== Create (single entry point) ========== @@ -190,16 +189,16 @@ namespace AlicizaX.ObjectPool public void Release() { - GetAllObjectPools(true, m_CachedAllObjectPools); - for (int i = 0; i < m_CachedAllObjectPools.Count; i++) - m_CachedAllObjectPools[i].Release(); + CacheSortedObjectPools(); + for (int i = 0; i < m_CachedSortedObjectPools.Count; i++) + m_CachedSortedObjectPools[i].Release(); } public void ReleaseAllUnused() { - GetAllObjectPools(true, m_CachedAllObjectPools); - for (int i = 0; i < m_CachedAllObjectPools.Count; i++) - m_CachedAllObjectPools[i].ReleaseAllUnused(); + CacheSortedObjectPools(); + for (int i = 0; i < m_CachedSortedObjectPools.Count; i++) + m_CachedSortedObjectPools[i].ReleaseAllUnused(); } // ========== Low memory ========== @@ -230,6 +229,13 @@ namespace AlicizaX.ObjectPool return false; } + private void CacheSortedObjectPools() + { + m_CachedSortedObjectPools.Clear(); + m_CachedSortedObjectPools.AddRange(m_ObjectPoolList); + m_CachedSortedObjectPools.Sort(m_ObjectPoolComparer); + } + private void RemovePoolFromList(ObjectPoolBase pool) { if (!m_ObjectPoolIndexMap.TryGetValue(pool, out int index))