diff --git a/Editor/Inspector/MemoryPoolComponentInspector.cs b/Editor/Inspector/MemoryPoolComponentInspector.cs index e172f8e..a825670 100644 --- a/Editor/Inspector/MemoryPoolComponentInspector.cs +++ b/Editor/Inspector/MemoryPoolComponentInspector.cs @@ -1,4 +1,4 @@ -using AlicizaX; +using AlicizaX; using System; using System.Collections.Generic; using System.IO; @@ -34,20 +34,44 @@ namespace AlicizaX.Editor t.EnableStrictCheck = enableStrictCheck; } - EditorGUILayout.LabelField("Reference Pool Count", MemoryPool.Count.ToString()); + EditorGUILayout.LabelField("Memory Pool Count", MemoryPool.Count.ToString()); m_ShowFullClassName = EditorGUILayout.Toggle("Show Full Class Name", m_ShowFullClassName); - m_ReferencePoolInfos.Clear(); + + // 全局统计 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; - List results = null; - if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out results)) + if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out List results)) { results = new List(); m_ReferencePoolInfos.Add(assemblyName, results); } - results.Add(referencePoolInfo); } @@ -58,20 +82,16 @@ namespace AlicizaX.Editor 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\tAdd\tRemove"; + 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) @@ -81,31 +101,34 @@ namespace AlicizaX.Editor if (GUILayout.Button("Export CSV Data")) { - string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Reference Pool Data - {0}.csv", assemblyReferencePoolInfo.Key), string.Empty); + 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,Add,Remove"; - foreach (MemoryPoolInfo referencePoolInfo in assemblyReferencePoolInfo.Value) + 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}", referencePoolInfo.Type.Name, referencePoolInfo.Type.FullName, referencePoolInfo.UnusedMemoryCount, referencePoolInfo.UsingMemoryCount, referencePoolInfo.AcquireMemoryCount, referencePoolInfo.ReleaseMemoryCount, referencePoolInfo.AddMemoryCount, referencePoolInfo.RemoveMemoryCount); + 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 reference pool CSV data to '{0}' success.", exportFileName)); + Debug.Log(Utility.Text.Format("Export memory pool CSV data to '{0}' success.", exportFileName)); } catch (Exception exception) { - Debug.LogError(Utility.Text.Format("Export reference pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception)); + Debug.LogError(Utility.Text.Format("Export memory pool CSV data to '{0}' failure, exception is '{1}'.", exportFileName, exception)); } } } } EditorGUILayout.EndVertical(); - EditorGUILayout.Separator(); } } @@ -125,26 +148,23 @@ namespace AlicizaX.Editor m_EnableStrictCheck = serializedObject.FindProperty("m_EnableStrictCheck"); } - private void DrawReferencePoolInfo(MemoryPoolInfo referencePoolInfo) + private void DrawReferencePoolInfo(MemoryPoolInfo info) { -#if UNITY_6000_0_OR_NEWER - EditorGUILayout.LabelField(m_ShowFullClassName ? referencePoolInfo.Type.FullName : referencePoolInfo.Type.Name, Utility.Text.Format("{0,-12}\t{1,-12}\t{2,-12}\t{3,-12}\t{4}\t{5}", referencePoolInfo.UnusedMemoryCount, referencePoolInfo.UsingMemoryCount, referencePoolInfo.AcquireMemoryCount, referencePoolInfo.ReleaseMemoryCount, referencePoolInfo.AddMemoryCount, referencePoolInfo.RemoveMemoryCount)); -#else - EditorGUILayout.LabelField(m_ShowFullClassName ? referencePoolInfo.Type.FullName : - referencePoolInfo.Type.Name, Utility.Text.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}",referencePoolInfo.UnusedMemoryCount, referencePoolInfo.UsingMemoryCount, referencePoolInfo.AcquireMemoryCount, referencePoolInfo.ReleaseMemoryCount, referencePoolInfo.AddMemoryCount, referencePoolInfo.RemoveMemoryCount)); -#endif + 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, + info.AcquireCount, info.ReleaseCount, + info.CreateCount, info.HighWaterMark, + info.MaxCapacity, info.IdleFrames, info.PoolArrayLength); + EditorGUILayout.LabelField(name, values); } private int Comparison(MemoryPoolInfo a, MemoryPoolInfo b) { if (m_ShowFullClassName) - { return a.Type.FullName.CompareTo(b.Type.FullName); - } else - { return a.Type.Name.CompareTo(b.Type.Name); - } } } } diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.Core.cs b/Runtime/ABase/Base/MemoryPool/MemoryPool.Core.cs new file mode 100644 index 0000000..c9f977f --- /dev/null +++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.Core.cs @@ -0,0 +1,231 @@ +using System; +using System.Runtime.CompilerServices; + +namespace AlicizaX +{ + + public static class MemoryPool where T : class, IMemory, new() + { + private static T[] s_Stack = Array.Empty(); + private static int s_Count; + private static int s_MaxCapacity = 2048; + + // ---- 回收策略状态 ---- + private static int s_HighWaterMark; + private static int s_RecentAcquireCount; + private static int s_IdleFrames; + private static int s_LastTickFrame; + private static int s_PeakInUse; + private static int s_CurrentInUse; + + private const int IDLE_THRESHOLD = 300; // ~5s @60fps + private const int IDLE_AGGRESSIVE = 900; // ~15s @60fps + private const int MIN_KEEP = 4; + + // ---- 统计计数器 ---- + private static int s_AcquireCount; + private static int s_ReleaseCount; + private static int s_CreateCount; + + static MemoryPool() + { + MemoryPoolRegistry.Register(typeof(T), new MemoryPoolRegistry.MemoryPoolHandle( + acquire: () => Acquire(), + release: obj => Release((T)obj), + clear: ClearAll, + prewarm: Prewarm, + getInfo: GetInfo, + tick: Tick, + shrink: Shrink + )); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Acquire() + { + s_AcquireCount++; + s_RecentAcquireCount++; + s_CurrentInUse++; + if (s_CurrentInUse > s_PeakInUse) + s_PeakInUse = s_CurrentInUse; + + if (s_Count > 0) + { + int idx = --s_Count; + T item = s_Stack[idx]; + s_Stack[idx] = null; + return item; + } + + return CreateNew(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T CreateNew() + { + s_CreateCount++; + return new T(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Release(T item) + { + if (item == null) return; + + s_ReleaseCount++; + // 防止 ClearAll 后 Release 导致负数 + if (s_CurrentInUse > 0) + s_CurrentInUse--; + + item.Clear(); + + if (s_Count >= s_MaxCapacity) + return; + + if (MemoryPool.EnableStrictCheck && s_Count > 0 && ReferenceEquals(s_Stack[s_Count - 1], item)) + throw new InvalidOperationException($"MemoryPool<{typeof(T).Name}>: Double release detected."); + + if (s_Count == s_Stack.Length) + Grow(); + + s_Stack[s_Count++] = item; + } + + internal static void Tick(int frameCount) + { + if (frameCount == s_LastTickFrame) return; + s_LastTickFrame = frameCount; + + // 更新高水位:基于实际并发使用量(在外对象数),而非单帧 Acquire 次数 + if (s_PeakInUse > s_HighWaterMark) + s_HighWaterMark = s_PeakInUse; + + if (s_RecentAcquireCount == 0) + s_IdleFrames++; + else + s_IdleFrames = 0; + + s_RecentAcquireCount = 0; + + if (s_Count <= MIN_KEEP) return; + + // ---- 双阶段渐进回收 ---- + if (s_IdleFrames >= IDLE_THRESHOLD) + { + int target = Math.Max((int)(s_HighWaterMark * 1.5f), MIN_KEEP); + + if (s_Count > target) + { + int excess = s_Count - target; + // 阶段1:温和回收 / 阶段2:激进回收 + float ratio = s_IdleFrames < IDLE_AGGRESSIVE ? 0.25f : 0.5f; + int removeCount = Math.Max((int)(excess * ratio), 1); + + int newCount = s_Count - removeCount; + Array.Clear(s_Stack, newCount, removeCount); + s_Count = newCount; + + TryShrinkArray(); + } + + if (s_IdleFrames >= IDLE_AGGRESSIVE) + { + s_HighWaterMark = Math.Max(s_HighWaterMark >> 1, MIN_KEEP); + s_PeakInUse = s_CurrentInUse; + } + } + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Grow() + { + int newLen = s_Stack.Length == 0 ? 8 : s_Stack.Length << 1; + if (newLen > s_MaxCapacity) newLen = s_MaxCapacity; + var newStack = new T[newLen]; + Array.Copy(s_Stack, 0, newStack, 0, s_Count); + s_Stack = newStack; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void TryShrinkArray() + { + if (s_Stack.Length > 32 && s_Count < s_Stack.Length >> 2) + { + int newLen = Math.Max(s_Count << 1, 8); + var newStack = new T[newLen]; + Array.Copy(s_Stack, 0, newStack, 0, s_Count); + s_Stack = newStack; + } + } + + /// + /// 预热池,启动时调用避免运行时 GC。 + /// + public static void Prewarm(int count) + { + count = Math.Min(count, s_MaxCapacity); + if (count <= s_Count) return; + + if (count > s_Stack.Length) + { + var newStack = new T[count]; + Array.Copy(s_Stack, 0, newStack, 0, s_Count); + s_Stack = newStack; + } + + while (s_Count < count) + { + s_Stack[s_Count++] = new T(); + s_CreateCount++; + } + } + + /// + /// 缩容:场景切换时调用,释放多余内存。 + /// + public static void Shrink(int keepCount) + { + if (keepCount >= s_Count) return; + keepCount = Math.Max(keepCount, 0); + + Array.Clear(s_Stack, keepCount, s_Count - keepCount); + s_Count = keepCount; + TryShrinkArray(); + } + + public static void SetMaxCapacity(int max) + { + s_MaxCapacity = Math.Max(max, MIN_KEEP); + } + + public static void ClearAll() + { + Array.Clear(s_Stack, 0, s_Count); + s_Count = 0; + s_HighWaterMark = 0; + s_PeakInUse = 0; + s_CurrentInUse = 0; + s_IdleFrames = 0; + s_RecentAcquireCount = 0; + s_Stack = Array.Empty(); + } + + public static int UnusedCount + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => s_Count; + } + + internal static MemoryPoolInfo GetInfo() + { + return new MemoryPoolInfo( + typeof(T), s_Count, + s_AcquireCount - s_ReleaseCount, + s_AcquireCount, s_ReleaseCount, + s_CreateCount, + s_HighWaterMark, s_MaxCapacity, + s_IdleFrames, s_Stack.Length); + } + } +} diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPool.Core.cs.meta similarity index 83% rename from Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs.meta rename to Runtime/ABase/Base/MemoryPool/MemoryPool.Core.cs.meta index 3cab765..b676d4e 100644 --- a/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs.meta +++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.Core.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: e4ec5f33991c55f41bceefbdb8089ae2 +guid: 9db12599664ce4d4eb5d5f581cf97900 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs b/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs deleted file mode 100644 index f550e88..0000000 --- a/Runtime/ABase/Base/MemoryPool/MemoryPool.MemoryCollection.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace AlicizaX -{ - public static partial class MemoryPool - { - /// - /// 内存池收集器。 - /// - private sealed class MemoryCollection - { - private readonly Queue _memories; - private readonly Type _memoryType; - private int _usingMemoryCount; - private int _acquireMemoryCount; - private int _releaseMemoryCount; - private int _addMemoryCount; - private int _removeMemoryCount; - - public MemoryCollection(Type memoryType) - { - _memories = new Queue(); - _memoryType = memoryType; - _usingMemoryCount = 0; - _acquireMemoryCount = 0; - _releaseMemoryCount = 0; - _addMemoryCount = 0; - _removeMemoryCount = 0; - } - - public Type MemoryType => _memoryType; - - public int UnusedMemoryCount => _memories.Count; - - public int UsingMemoryCount => _usingMemoryCount; - - public int AcquireMemoryCount => _acquireMemoryCount; - - public int ReleaseMemoryCount => _releaseMemoryCount; - - public int AddMemoryCount => _addMemoryCount; - - public int RemoveMemoryCount => _removeMemoryCount; - - public T Acquire() where T : class, IMemory, new() - { - if (typeof(T) != _memoryType) - { - throw new Exception("Type is invalid."); - } - - _usingMemoryCount++; - _acquireMemoryCount++; - lock (_memories) - { - if (_memories.Count > 0) - { - return (T)_memories.Dequeue(); - } - } - - _addMemoryCount++; - return new T(); - } - - public IMemory Acquire() - { - _usingMemoryCount++; - _acquireMemoryCount++; - lock (_memories) - { - if (_memories.Count > 0) - { - return _memories.Dequeue(); - } - } - - _addMemoryCount++; - return (IMemory)Activator.CreateInstance(_memoryType); - } - - public void Release(IMemory memory) - { - memory.Clear(); - lock (_memories) - { - if (_enableStrictCheck && _memories.Contains(memory)) - { - throw new Exception("The memory has been released."); - } - - _memories.Enqueue(memory); - } - - _releaseMemoryCount++; - _usingMemoryCount--; - } - - public void Add(int count) where T : class, IMemory, new() - { - if (typeof(T) != _memoryType) - { - throw new Exception("Type is invalid."); - } - - lock (_memories) - { - _addMemoryCount += count; - while (count-- > 0) - { - _memories.Enqueue(new T()); - } - } - } - - public void Add(int count) - { - lock (_memories) - { - _addMemoryCount += count; - while (count-- > 0) - { - _memories.Enqueue((IMemory)Activator.CreateInstance(_memoryType)); - } - } - } - - public void Remove(int count) - { - lock (_memories) - { - if (count > _memories.Count) - { - count = _memories.Count; - } - - _removeMemoryCount += count; - while (count-- > 0) - { - _memories.Dequeue(); - } - } - } - - public void RemoveAll() - { - lock (_memories) - { - _removeMemoryCount += _memories.Count; - _memories.Clear(); - } - } - } - } -} diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPool.cs b/Runtime/ABase/Base/MemoryPool/MemoryPool.cs index 704429c..d5b31bb 100644 --- a/Runtime/ABase/Base/MemoryPool/MemoryPool.cs +++ b/Runtime/ABase/Base/MemoryPool/MemoryPool.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; +using System; namespace AlicizaX { /// - /// 内存池。 + /// 内存池。保留旧 API 签名,内部转发到 MemoryPool<T> / MemoryPoolRegistry。 /// public static partial class MemoryPool { - private static readonly Dictionary _memoryCollections = new Dictionary(); - private static bool _enableStrictCheck = false; + private static bool _enableStrictCheck; /// /// 获取或设置是否开启强制检查。 @@ -23,28 +21,14 @@ namespace AlicizaX /// /// 获取内存池的数量。 /// - // ReSharper disable once InconsistentlySynchronizedField - public static int Count => _memoryCollections.Count; + public static int Count => MemoryPoolRegistry.Count; /// /// 获取所有内存池的信息。 /// - /// 所有内存池的信息。 public static MemoryPoolInfo[] GetAllMemoryPoolInfos() { - int index = 0; - MemoryPoolInfo[] results = null; - - lock (_memoryCollections) - { - results = new MemoryPoolInfo[_memoryCollections.Count]; - foreach (KeyValuePair memoryCollection in _memoryCollections) - { - results[index++] = new MemoryPoolInfo(memoryCollection.Key, memoryCollection.Value.UnusedMemoryCount, memoryCollection.Value.UsingMemoryCount, memoryCollection.Value.AcquireMemoryCount, memoryCollection.Value.ReleaseMemoryCount, memoryCollection.Value.AddMemoryCount, memoryCollection.Value.RemoveMemoryCount); - } - } - - return results; + return MemoryPoolRegistry.GetAllInfos(); } /// @@ -52,156 +36,80 @@ namespace AlicizaX /// public static void ClearAll() { - lock (_memoryCollections) - { - foreach (KeyValuePair memoryCollection in _memoryCollections) - { - memoryCollection.Value.RemoveAll(); - } - - _memoryCollections.Clear(); - } + MemoryPoolRegistry.ClearAll(); } /// /// 从内存池获取内存对象。 /// - /// 内存对象类型。 - /// 内存对象。 public static T Acquire() where T : class, IMemory, new() { - return GetMemoryCollection(typeof(T)).Acquire(); + return MemoryPool.Acquire(); } /// /// 从内存池获取内存对象。 /// - /// 内存对象类型。 - /// 内存对象。 public static IMemory Acquire(Type memoryType) { - InternalCheckMemoryType(memoryType); - return GetMemoryCollection(memoryType).Acquire(); + return MemoryPoolRegistry.Acquire(memoryType); } /// /// 将内存对象归还内存池。 /// - /// 内存对象。 public static void Release(IMemory memory) { - if (memory == null) - { - throw new Exception("Memory is invalid."); - } - - Type memoryType = memory.GetType(); - InternalCheckMemoryType(memoryType); - GetMemoryCollection(memoryType).Release(memory); + MemoryPoolRegistry.Release(memory); } /// - /// 向内存池中追加指定数量的内存对象。 + /// 向内存池中预热指定数量的内存对象。 /// - /// 内存对象类型。 - /// 追加数量。 public static void Add(int count) where T : class, IMemory, new() { - GetMemoryCollection(typeof(T)).Add(count); + MemoryPool.Prewarm(count); } /// - /// 向内存池中追加指定数量的内存对象。 + /// 向内存池中预热指定数量的内存对象。 /// - /// 内存对象类型。 - /// 追加数量。 public static void Add(Type memoryType, int count) { - InternalCheckMemoryType(memoryType); - GetMemoryCollection(memoryType).Add(count); + MemoryPoolRegistry.Prewarm(memoryType, count); } /// /// 从内存池中移除指定数量的内存对象。 /// - /// 内存对象类型。 - /// 移除数量。 - public static void Remove(int count) where T : class, IMemory + public static void Remove(int count) where T : class, IMemory, new() { - GetMemoryCollection(typeof(T)).Remove(count); + int target = MemoryPool.UnusedCount - count; + MemoryPool.Shrink(target); } /// /// 从内存池中移除指定数量的内存对象。 /// - /// 内存对象类型。 - /// 移除数量。 public static void Remove(Type memoryType, int count) { - InternalCheckMemoryType(memoryType); - GetMemoryCollection(memoryType).Remove(count); + MemoryPoolRegistry.RemoveFromType(memoryType, count); } /// /// 从内存池中移除所有的内存对象。 /// - /// 内存对象类型。 - public static void RemoveAll() where T : class, IMemory + public static void RemoveAll() where T : class, IMemory, new() { - GetMemoryCollection(typeof(T)).RemoveAll(); + MemoryPool.ClearAll(); } /// /// 从内存池中移除所有的内存对象。 /// - /// 内存对象类型。 public static void RemoveAll(Type memoryType) { - InternalCheckMemoryType(memoryType); - GetMemoryCollection(memoryType).RemoveAll(); - } - - private static void InternalCheckMemoryType(Type memoryType) - { - if (!_enableStrictCheck) - { - return; - } - - if (memoryType == null) - { - throw new Exception("Memory type is invalid."); - } - - if (!memoryType.IsClass || memoryType.IsAbstract) - { - throw new Exception("Memory type is not a non-abstract class type."); - } - - if (!typeof(IMemory).IsAssignableFrom(memoryType)) - { - throw new Exception(string.Format("Memory type '{0}' is invalid.", memoryType.FullName)); - } - } - - private static MemoryCollection GetMemoryCollection(Type memoryType) - { - if (memoryType == null) - { - throw new Exception("MemoryType is invalid."); - } - - MemoryCollection memoryCollection = null; - lock (_memoryCollections) - { - if (!_memoryCollections.TryGetValue(memoryType, out memoryCollection)) - { - memoryCollection = new MemoryCollection(memoryType); - _memoryCollections.Add(memoryType, memoryCollection); - } - } - - return memoryCollection; + MemoryPoolRegistry.ClearType(memoryType); } } } diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs index 612bb83..2105e79 100644 --- a/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs +++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolExtension.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace AlicizaX { @@ -30,11 +30,9 @@ namespace AlicizaX /// /// 从内存池获取内存对象。 /// - /// 内存对象类型。 - /// 内存对象。 public static T Alloc() where T : MemoryObject, new() { - T memory = Acquire(); + T memory = MemoryPool.Acquire(); memory.InitFromPool(); return memory; } @@ -42,16 +40,15 @@ namespace AlicizaX /// /// 将内存对象归还内存池。 /// - /// 内存对象。 public static void Dealloc(MemoryObject memory) { if (memory == null) { - throw new Exception("Memory is invalid."); + throw new ArgumentNullException(nameof(memory)); } memory.RecycleToPool(); - Release(memory); + MemoryPoolRegistry.Release(memory); } } } diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs index 85591b1..e9bf602 100644 --- a/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs +++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; namespace AlicizaX @@ -10,109 +10,81 @@ namespace AlicizaX public struct MemoryPoolInfo { private readonly Type _type; - private readonly int _unusedMemoryCount; - private readonly int _usingMemoryCount; - private readonly int _acquireMemoryCount; - private readonly int _releaseMemoryCount; - private readonly int _addMemoryCount; - private readonly int _removeMemoryCount; + 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; - /// - /// 初始化内存池信息的新实例。 - /// - /// 内存池类型。 - /// 未使用内存对象数量。 - /// 正在使用内存对象数量。 - /// 获取内存对象数量。 - /// 归还内存对象数量。 - /// 增加内存对象数量。 - /// 移除内存对象数量。 - public MemoryPoolInfo(Type type, int unusedMemoryCount, int usingMemoryCount, int acquireMemoryCount, int releaseMemoryCount, int addMemoryCount, int removeMemoryCount) + public MemoryPoolInfo(Type type, int unusedCount, int usingCount, + int acquireCount, int releaseCount, int createCount, + int highWaterMark, int maxCapacity, + int idleFrames, int poolArrayLength) { _type = type; - _unusedMemoryCount = unusedMemoryCount; - _usingMemoryCount = usingMemoryCount; - _acquireMemoryCount = acquireMemoryCount; - _releaseMemoryCount = releaseMemoryCount; - _addMemoryCount = addMemoryCount; - _removeMemoryCount = removeMemoryCount; + _unusedCount = unusedCount; + _usingCount = usingCount; + _acquireCount = acquireCount; + _releaseCount = releaseCount; + _createCount = createCount; + _highWaterMark = highWaterMark; + _maxCapacity = maxCapacity; + _idleFrames = idleFrames; + _poolArrayLength = poolArrayLength; } /// - /// 获取内存池类型。 + /// 池类型。 /// - public Type Type - { - get - { - return _type; - } - } + public Type Type => _type; /// - /// 获取未使用内存对象数量。 + /// 池中空闲对象数量(可立即借出)。 /// - public int UnusedMemoryCount - { - get - { - return _unusedMemoryCount; - } - } + public int UnusedCount => _unusedCount; /// - /// 获取正在使用内存对象数量。 + /// 当前被借出、尚未归还的对象数量。 /// - public int UsingMemoryCount - { - get - { - return _usingMemoryCount; - } - } + public int UsingCount => _usingCount; /// - /// 获取获取内存对象数量。 + /// 累计 Acquire 调用次数(仅开发模式有效)。 /// - public int AcquireMemoryCount - { - get - { - return _acquireMemoryCount; - } - } + public int AcquireCount => _acquireCount; /// - /// 获取归还内存对象数量。 + /// 累计 Release 调用次数(仅开发模式有效)。 /// - public int ReleaseMemoryCount - { - get - { - return _releaseMemoryCount; - } - } + public int ReleaseCount => _releaseCount; /// - /// 获取增加内存对象数量。 + /// 累计 new T() 创建次数(池不够时的实际分配,仅开发模式有效)。 /// - public int AddMemoryCount - { - get - { - return _addMemoryCount; - } - } + public int CreateCount => _createCount; /// - /// 获取移除内存对象数量。 + /// 近期峰值并发使用量。回收策略据此决定保留多少对象。 /// - public int RemoveMemoryCount - { - get - { - return _removeMemoryCount; - } - } + public int HighWaterMark => _highWaterMark; + + /// + /// 池容量硬上限。超出后 Release 的对象直接丢弃交给 GC。 + /// + public int MaxCapacity => _maxCapacity; + + /// + /// 连续无 Acquire 的帧数。>=300 开始温和回收,>=900 激进回收+高水位衰减。 + /// + public int IdleFrames => _idleFrames; + + /// + /// 底层 T[] 数组的实际长度。反映真实内存占用(含空槽)。 + /// + public int PoolArrayLength => _poolArrayLength; } } diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolRegistry.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolRegistry.cs new file mode 100644 index 0000000..1e69b0d --- /dev/null +++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolRegistry.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace AlicizaX +{ + /// + /// 内存池注册表。非泛型路径通过此表查找对应的泛型池。 + /// 注册发生在 MemoryPool<T> 静态构造器中,仅一次。 + /// + public static class MemoryPoolRegistry + { + internal sealed class MemoryPoolHandle + { + public readonly Func Acquire; + public readonly Action Release; + public readonly Action Clear; + public readonly Action Prewarm; + public readonly Func GetInfo; + public readonly Action Tick; + public readonly Action Shrink; + + public MemoryPoolHandle( + Func acquire, + Action release, + Action clear, + Action prewarm, + Func getInfo, + Action tick, + Action shrink) + { + Acquire = acquire; + Release = release; + Clear = clear; + Prewarm = prewarm; + GetInfo = getInfo; + Tick = tick; + Shrink = shrink; + } + } + + private static readonly Dictionary s_Handles + = new Dictionary(64); + + // Tick 回调用数组缓存,避免每帧遍历 Dictionary + private static Action[] s_TickArray = Array.Empty>(); + private static int s_TickCount; + private static bool s_TickArrayDirty; + + public static int Count => s_Handles.Count; + + internal static void Register(Type type, MemoryPoolHandle handle) + { + s_Handles[type] = handle; + s_TickArrayDirty = true; + } + + /// + /// 非泛型 Acquire,用于只有 Type 没有泛型参数的场景。 + /// + public static IMemory Acquire(Type type) + { + if (s_Handles.TryGetValue(type, out var handle)) + return handle.Acquire(); + + // 首次访问:触发 MemoryPool 静态构造器 + RuntimeHelpers.RunClassConstructor( + typeof(MemoryPool<>).MakeGenericType(type).TypeHandle); + + if (s_Handles.TryGetValue(type, out handle)) + return handle.Acquire(); + + throw new Exception($"MemoryPool: Type '{type.FullName}' is not a valid IMemory type."); + } + + public static void Release(IMemory memory) + { + if (memory == null) + throw new ArgumentNullException(nameof(memory)); + + Type type = memory.GetType(); + if (s_Handles.TryGetValue(type, out var handle)) + { + handle.Release(memory); + return; + } + + // 首次访问:触发静态构造器 + RuntimeHelpers.RunClassConstructor( + typeof(MemoryPool<>).MakeGenericType(type).TypeHandle); + + if (s_Handles.TryGetValue(type, out handle)) + handle.Release(memory); + } + + 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(); + return infos; + } + + public static void ClearAll() + { + foreach (var kv in s_Handles) + kv.Value.Clear(); + } + + public static void Prewarm(Type type, int count) + { + if (s_Handles.TryGetValue(type, out var handle)) + { + handle.Prewarm(count); + return; + } + + RuntimeHelpers.RunClassConstructor( + typeof(MemoryPool<>).MakeGenericType(type).TypeHandle); + + if (s_Handles.TryGetValue(type, out handle)) + handle.Prewarm(count); + } + + public static void ClearType(Type type) + { + if (s_Handles.TryGetValue(type, out var handle)) + handle.Clear(); + } + + public static void RemoveFromType(Type type, int count) + { + if (s_Handles.TryGetValue(type, out var handle)) + { + int unused = handle.GetInfo().UnusedCount; + handle.Shrink(unused - count); + } + } + + /// + /// 每帧由 MemoryPoolTicker 调用,驱动所有池的自动回收。 + /// + public static void TickAll(int frameCount) + { + if (s_TickArrayDirty) + RebuildTickArray(); + + for (int i = 0; i < s_TickCount; i++) + s_TickArray[i](frameCount); + } + + private static void RebuildTickArray() + { + s_TickCount = s_Handles.Count; + if (s_TickArray.Length < s_TickCount) + s_TickArray = new Action[s_TickCount]; + + int i = 0; + foreach (var kv in s_Handles) + s_TickArray[i++] = kv.Value.Tick; + + s_TickArrayDirty = false; + } + } +} diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolRegistry.cs.meta b/Runtime/ABase/Base/MemoryPool/MemoryPoolRegistry.cs.meta new file mode 100644 index 0000000..3a1baf4 --- /dev/null +++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolRegistry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e39b8f96e3d484a40bf40d2517b4de02 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs b/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs index bc60dab..7af794b 100644 --- a/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs +++ b/Runtime/ABase/Base/MemoryPool/MemoryPoolSetting.cs @@ -74,5 +74,11 @@ namespace AlicizaX break; } } + + private void Update() + { + MemoryPoolRegistry.TickAll(Time.frameCount); + } + } } diff --git a/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs b/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs index 0165e18..c88cc0e 100644 --- a/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs +++ b/Runtime/Debugger/DebuggerComponent.ReferencePoolInformationWindow.cs @@ -17,50 +17,72 @@ namespace AlicizaX.Debugger.Runtime protected override void BuildWindow(VisualElement root) { - VisualElement overview = CreateSection("Reference Pool Overview", out VisualElement overviewCard); + float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; + + // ---- Overview Section ---- + VisualElement overview = CreateSection("Memory Pool Overview", out VisualElement overviewCard); overviewCard.Add(CreateRow("Enable Strict Check", MemoryPool.EnableStrictCheck.ToString())); - overviewCard.Add(CreateRow("Reference Pool Count", MemoryPool.Count.ToString())); - showFullClassNameToggle = ScrollableDebuggerWindowBase.CreateConsoleFilterToggle("Show Full ClassName", m_ShowFullClassName, DebuggerTheme.PrimaryText, value => m_ShowFullClassName = value); + 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) + { + 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); + + // ---- 操作按钮 ---- + 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)); + + overviewCard.Add(buttonRow); root.Add(overview); + // ---- 按 Assembly 分组 ---- m_ReferencePoolInfos.Clear(); - MemoryPoolInfo[] referencePoolInfos = MemoryPool.GetAllMemoryPoolInfos(); - foreach (MemoryPoolInfo referencePoolInfo in referencePoolInfos) + foreach (MemoryPoolInfo info in allInfos) { - string assemblyName = referencePoolInfo.Type.Assembly.GetName().Name; + 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(referencePoolInfo); + results.Add(info); } - foreach (KeyValuePair> assemblyReferencePoolInfo in m_ReferencePoolInfos) + foreach (KeyValuePair> assemblyInfo in m_ReferencePoolInfos) { - assemblyReferencePoolInfo.Value.Sort(m_ShowFullClassName ? m_FullClassNameComparer : m_NormalClassNameComparer); - VisualElement section = CreateSection(Utility.Text.Format("Assembly: {0}", assemblyReferencePoolInfo.Key), out VisualElement card); - if (assemblyReferencePoolInfo.Value.Count <= 0) + 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) { - card.Add(CreateRow("State", "Reference Pool is Empty ...")); + card.Add(CreateRow("State", "Memory Pool is Empty ...")); } else { - for (int i = 0; i < assemblyReferencePoolInfo.Value.Count; i++) + for (int i = 0; i < assemblyInfo.Value.Count; i++) { - MemoryPoolInfo info = assemblyReferencePoolInfo.Value[i]; - string title = m_ShowFullClassName ? info.Type.FullName : info.Type.Name; - string content = Utility.Text.Format( - "Unused {0} | Using {1} | Acquire {2} | Release {3} | Add {4} | Remove {5}", - info.UnusedMemoryCount, - info.UsingMemoryCount, - info.AcquireMemoryCount, - info.ReleaseMemoryCount, - info.AddMemoryCount, - info.RemoveMemoryCount); - card.Add(CreatePoolInfoItem(title, content)); + card.Add(CreatePoolInfoItem(assemblyInfo.Value[i], m_ShowFullClassName, scale)); } } @@ -78,27 +100,52 @@ namespace AlicizaX.Debugger.Runtime return a.Type.FullName.CompareTo(b.Type.FullName); } - private static VisualElement CreatePoolInfoItem(string title, string content) + private static VisualElement CreatePoolInfoItem(MemoryPoolInfo info, bool showFullName, float scale) { - float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; VisualElement item = CreateCard(); item.style.marginBottom = 8f * scale; item.style.backgroundColor = DebuggerTheme.PanelSurfaceAlt; + string title = showFullName ? info.Type.FullName : info.Type.Name; Label titleLabel = new Label(title ?? string.Empty); titleLabel.style.color = DebuggerTheme.PrimaryText; titleLabel.style.fontSize = 16f * scale; titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; titleLabel.style.whiteSpace = WhiteSpace.Normal; titleLabel.style.marginBottom = 6f * scale; - - Label contentLabel = new Label(content ?? string.Empty); - contentLabel.style.color = DebuggerTheme.SecondaryText; - contentLabel.style.fontSize = 14f * scale; - contentLabel.style.whiteSpace = WhiteSpace.Normal; - item.Add(titleLabel); - item.Add(contentLabel); + + // 基础统计 + string stats = Utility.Text.Format( + "Unused {0} | Using {1} | Acquire {2} | Release {3} | Created {4}", + info.UnusedCount, info.UsingCount, + info.AcquireCount, info.ReleaseCount, + info.CreateCount); + Label statsLabel = new Label(stats); + statsLabel.style.color = DebuggerTheme.SecondaryText; + statsLabel.style.fontSize = 14f * scale; + statsLabel.style.whiteSpace = WhiteSpace.Normal; + 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, + info.IdleFrames, info.PoolArrayLength); + Label recycleLabel = new Label(recycleStatus); + recycleLabel.style.fontSize = 13f * scale; + recycleLabel.style.whiteSpace = WhiteSpace.Normal; + + // 根据空闲帧数着色:接近回收阈值时变色 + if (info.IdleFrames >= 300) + recycleLabel.style.color = DebuggerTheme.Warning; + else if (info.IdleFrames >= 200) + recycleLabel.style.color = new Color(0.9f, 0.7f, 0.3f); + else + recycleLabel.style.color = DebuggerTheme.SecondaryText; + + item.Add(recycleLabel); return item; } }