优化ObjectPool和MemoryPool性能

This commit is contained in:
陈思海 2026-04-23 19:09:56 +08:00
parent 8c3f2c99cc
commit aa37eecf8b
17 changed files with 515 additions and 324 deletions

View File

@ -11,12 +11,13 @@ namespace AlicizaX.Editor
[CustomEditor(typeof(MemoryPoolSetting))] [CustomEditor(typeof(MemoryPoolSetting))]
internal sealed class MemoryPoolComponentInspector : GameFrameworkInspector internal sealed class MemoryPoolComponentInspector : GameFrameworkInspector
{ {
private readonly Dictionary<string, List<MemoryPoolInfo>> m_ReferencePoolInfos = new Dictionary<string, List<MemoryPoolInfo>>(StringComparer.Ordinal); private readonly Dictionary<string, List<int>> m_GroupedIndices = new Dictionary<string, List<int>>(StringComparer.Ordinal);
private readonly List<string> m_ActiveAssemblyKeys = new List<string>(16);
private readonly HashSet<string> m_OpenedItems = new HashSet<string>(); private readonly HashSet<string> m_OpenedItems = new HashSet<string>();
private MemoryPoolInfo[] m_InfoBuffer = Array.Empty<MemoryPoolInfo>();
private SerializedProperty m_EnableStrictCheck = null; private SerializedProperty m_EnableStrictCheck;
private bool m_ShowFullClassName;
private bool m_ShowFullClassName = false;
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
@ -24,114 +25,10 @@ namespace AlicizaX.Editor
serializedObject.Update(); serializedObject.Update();
MemoryPoolSetting t = (MemoryPoolSetting)target; MemoryPoolSetting setting = (MemoryPoolSetting)target;
if (EditorApplication.isPlaying && IsPrefabInHierarchy(setting.gameObject))
if (EditorApplication.isPlaying && IsPrefabInHierarchy(t.gameObject))
{ {
bool enableStrictCheck = EditorGUILayout.Toggle("Enable Strict Check", t.EnableStrictCheck); DrawRuntimeInspector(setting);
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<MemoryPoolInfo> results))
{
results = new List<MemoryPoolInfo>();
m_ReferencePoolInfos.Add(assemblyName, results);
}
results.Add(referencePoolInfo);
}
foreach (KeyValuePair<string, List<MemoryPoolInfo>> 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();
}
}
} }
else else
{ {
@ -139,7 +36,6 @@ namespace AlicizaX.Editor
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
Repaint(); Repaint();
} }
@ -148,8 +44,116 @@ namespace AlicizaX.Editor
m_EnableStrictCheck = serializedObject.FindProperty("m_EnableStrictCheck"); 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<int> 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<string, List<int>> 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<int> indices))
{
indices = new List<int>(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 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}", 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.UnusedCount, info.UsingCount,
@ -159,12 +163,58 @@ namespace AlicizaX.Editor
EditorGUILayout.LabelField(name, values); EditorGUILayout.LabelField(name, values);
} }
private int Comparison(MemoryPoolInfo a, MemoryPoolInfo b) private void ExportCsv(string assemblyName, List<int> indices)
{ {
if (m_ShowFullClassName) string exportFileName = EditorUtility.SaveFilePanel("Export CSV Data", string.Empty, Utility.Text.Format("Memory Pool Data - {0}.csv", assemblyName), string.Empty);
return a.Type.FullName.CompareTo(b.Type.FullName); if (string.IsNullOrEmpty(exportFileName))
else return;
return a.Type.Name.CompareTo(b.Type.Name);
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;
} }
} }
} }

View File

@ -13,6 +13,8 @@ namespace AlicizaX.Editor
internal sealed class ObjectPoolComponentInspector : GameFrameworkInspector internal sealed class ObjectPoolComponentInspector : GameFrameworkInspector
{ {
private readonly HashSet<string> m_OpenedItems = new HashSet<string>(); private readonly HashSet<string> m_OpenedItems = new HashSet<string>();
private ObjectPoolBase[] m_ObjectPools = new ObjectPoolBase[1];
private ObjectInfo[] m_ObjectInfos = new ObjectInfo[1];
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
@ -30,10 +32,11 @@ namespace AlicizaX.Editor
{ {
EditorGUILayout.LabelField("Object Pool Count", t.Count.ToString()); EditorGUILayout.LabelField("Object Pool Count", t.Count.ToString());
ObjectPoolBase[] objectPools = t.GetAllObjectPools(true); int objectPoolCount = EnsureObjectPoolBuffer(t.Count);
foreach (ObjectPoolBase objectPool in objectPools) 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("Used Count", objectPool.Count.ToString());
EditorGUILayout.LabelField("Expire Time", objectPool.ExpireTime.ToString()); EditorGUILayout.LabelField("Expire Time", objectPool.ExpireTime.ToString());
EditorGUILayout.LabelField("Priority", objectPool.Priority.ToString()); EditorGUILayout.LabelField("Priority", objectPool.Priority.ToString());
ObjectInfo[] objectInfos = objectPool.GetAllObjectInfos(); int objectInfoCount = EnsureObjectInfoBuffer(objectPool.Count);
if (objectInfos.Length > 0) 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"); 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 #if UNITY_6000_0_OR_NEWER
string lastUse = Utility.Text.Format("{0:F1}s ago", UnityEngine.Time.realtimeSinceStartup - objectInfo.LastUseTime); string lastUse = Utility.Text.Format("{0:F1}s ago", UnityEngine.Time.realtimeSinceStartup - objectInfo.LastUseTime);
@ -109,10 +114,11 @@ namespace AlicizaX.Editor
try try
{ {
int index = 0; 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"); 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); string csvLastUse = Utility.Text.Format("{0:F1}s ago", UnityEngine.Time.realtimeSinceStartup - objectInfo.LastUseTime);
data[index++] = objectPool.AllowMultiSpawn data[index++] = objectPool.AllowMultiSpawn
? Utility.Text.Format("{0},{1},{2},{3},{4}", objectInfo.Name, objectInfo.Locked, objectInfo.SpawnCount, objectInfo.CustomCanReleaseFlag, csvLastUse) ? 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(); 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;
}
} }
} }

View File

@ -9,10 +9,14 @@ namespace AlicizaX.Debugger.Runtime
private sealed class ObjectPoolInformationWindow : PollingDebuggerWindowBase private sealed class ObjectPoolInformationWindow : PollingDebuggerWindowBase
{ {
private IObjectPoolService m_ObjectPoolService; private IObjectPoolService m_ObjectPoolService;
private IObjectPoolServiceDebugView m_ObjectPoolDebugView;
private ObjectPoolBase[] m_ObjectPools;
private ObjectInfo[] m_ObjectInfos;
public override void Initialize(params object[] args) public override void Initialize(params object[] args)
{ {
m_ObjectPoolService = AppServices.Require<IObjectPoolService>(); m_ObjectPoolService = AppServices.Require<IObjectPoolService>();
m_ObjectPoolDebugView = m_ObjectPoolService as IObjectPoolServiceDebugView;
} }
protected override void BuildWindow(VisualElement root) protected override void BuildWindow(VisualElement root)
@ -26,10 +30,13 @@ namespace AlicizaX.Debugger.Runtime
overviewCard.Add(CreateRow("Object Pool Count", m_ObjectPoolService.Count.ToString())); overviewCard.Add(CreateRow("Object Pool Count", m_ObjectPoolService.Count.ToString()));
root.Add(overview); root.Add(overview);
ObjectPoolBase[] objectPools = m_ObjectPoolService.GetAllObjectPools(true); int objectPoolCount = EnsureObjectPoolBuffer(m_ObjectPoolService.Count);
for (int i = 0; i < objectPools.Length; i++) 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); VisualElement section = CreateSection(Utility.Text.Format("Object Pool: {0}", objectPool.FullName), out VisualElement card);
card.Add(CreateRow("Name", objectPool.Name)); card.Add(CreateRow("Name", objectPool.Name));
card.Add(CreateRow("Type", objectPool.ObjectType.FullName)); 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("Expire Time", objectPool.ExpireTime.ToString()));
card.Add(CreateRow("Priority", objectPool.Priority.ToString())); card.Add(CreateRow("Priority", objectPool.Priority.ToString()));
ObjectInfo[] objectInfos = objectPool.GetAllObjectInfos(); int objectInfoCount = EnsureObjectInfoBuffer(objectPool.Count);
if (objectInfos.Length <= 0) objectInfoCount = objectPool.GetAllObjectInfos(m_ObjectInfos);
if (objectInfoCount <= 0)
{ {
card.Add(CreateRow("Entries", "Object Pool is Empty ...")); card.Add(CreateRow("Entries", "Object Pool is Empty ..."));
} }
else 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) ? "<None>" : info.Name; string title = string.IsNullOrEmpty(info.Name) ? "<None>" : info.Name;
string content = Utility.Text.Format( string content = Utility.Text.Format(
"Locked {0} | {1} {2} | Flag {3} | Last Use {4}", "Locked {0} | {1} {2} | Flag {3} | Last Use {4}",
@ -64,6 +72,36 @@ namespace AlicizaX.Debugger.Runtime
root.Add(section); 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;
}
} }
} }
} }

View File

@ -9,98 +9,145 @@ namespace AlicizaX.Debugger.Runtime
{ {
private sealed class ReferencePoolInformationWindow : PollingDebuggerWindowBase private sealed class ReferencePoolInformationWindow : PollingDebuggerWindowBase
{ {
private readonly Dictionary<string, List<MemoryPoolInfo>> m_ReferencePoolInfos = new Dictionary<string, List<MemoryPoolInfo>>(StringComparer.Ordinal); private readonly Dictionary<string, List<int>> m_GroupedIndices = new Dictionary<string, List<int>>(StringComparer.Ordinal);
private readonly Comparison<MemoryPoolInfo> m_NormalClassNameComparer = NormalClassNameComparer; private readonly List<string> m_ActiveAssemblyKeys = new List<string>(16);
private readonly Comparison<MemoryPoolInfo> m_FullClassNameComparer = FullClassNameComparer; private readonly Comparison<int> m_NormalIndexComparer;
private readonly Comparison<int> m_FullIndexComparer;
private MemoryPoolInfo[] m_InfoBuffer = Array.Empty<MemoryPoolInfo>();
private bool m_ShowFullClassName; 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) protected override void BuildWindow(VisualElement root)
{ {
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
int infoCount = FetchInfos();
// ---- Overview Section ----
VisualElement overview = CreateSection("Memory Pool Overview", out VisualElement overviewCard); VisualElement overview = CreateSection("Memory Pool Overview", out VisualElement overviewCard);
overviewCard.Add(CreateRow("Enable Strict Check", MemoryPool.EnableStrictCheck.ToString())); overviewCard.Add(CreateRow("Enable Strict Check", MemoryPool.EnableStrictCheck.ToString()));
overviewCard.Add(CreateRow("Pool Type Count", MemoryPool.Count.ToString())); overviewCard.Add(CreateRow("Pool Type Count", MemoryPool.Count.ToString()));
// 统计总缓存对象数和总数组容量
MemoryPoolInfo[] allInfos = MemoryPool.GetAllMemoryPoolInfos();
int totalUnused = 0; int totalUnused = 0;
int totalUsing = 0; int totalUsing = 0;
int totalArrayLen = 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; totalUnused += info.UnusedCount;
totalUsing += info.UsingCount; totalUsing += info.UsingCount;
totalArrayLen += info.PoolArrayLength; totalArrayLen += info.PoolArrayLength;
} }
overviewCard.Add(CreateRow("Total Cached Objects", totalUnused.ToString())); overviewCard.Add(CreateRow("Total Cached Objects", totalUnused.ToString()));
overviewCard.Add(CreateRow("Total In Use", totalUsing.ToString())); overviewCard.Add(CreateRow("Total In Use", totalUsing.ToString()));
overviewCard.Add(CreateRow("Total Array Capacity", totalArrayLen.ToString())); overviewCard.Add(CreateRow("Total Array Capacity", totalArrayLen.ToString()));
showFullClassNameToggle = CreateConsoleFilterToggle("Show Full ClassName", m_ShowFullClassName, DebuggerTheme.PrimaryText, value => m_ShowFullClassName = value); m_ShowFullClassNameToggle = CreateConsoleFilterToggle("Show Full ClassName", m_ShowFullClassName, DebuggerTheme.PrimaryText, OnShowFullClassNameChanged);
overviewCard.Add(showFullClassNameToggle); overviewCard.Add(m_ShowFullClassNameToggle);
// ---- 操作按钮 ----
VisualElement buttonRow = new VisualElement(); VisualElement buttonRow = new VisualElement();
buttonRow.style.flexDirection = FlexDirection.Row; buttonRow.style.flexDirection = FlexDirection.Row;
buttonRow.style.marginTop = 8f * scale; buttonRow.style.marginTop = 8f * scale;
buttonRow.Add(CreateActionButton("Clear All Pools", OnClearAllPools, DebuggerTheme.Danger));
buttonRow.Add(CreateActionButton("Clear All Pools", () =>
{
MemoryPoolRegistry.ClearAll();
Rebuild();
}, DebuggerTheme.Danger));
overviewCard.Add(buttonRow); overviewCard.Add(buttonRow);
root.Add(overview); root.Add(overview);
// ---- 按 Assembly 分组 ---- RebuildGroups(infoCount);
m_ReferencePoolInfos.Clear(); for (int i = 0; i < m_ActiveAssemblyKeys.Count; i++)
foreach (MemoryPoolInfo info in allInfos)
{ {
string assemblyName = info.Type.Assembly.GetName().Name; string assemblyKey = m_ActiveAssemblyKeys[i];
if (!m_ReferencePoolInfos.TryGetValue(assemblyName, out List<MemoryPoolInfo> results)) List<int> indices = m_GroupedIndices[assemblyKey];
{ indices.Sort(m_ShowFullClassName ? m_FullIndexComparer : m_NormalIndexComparer);
results = new List<MemoryPoolInfo>();
m_ReferencePoolInfos.Add(assemblyName, results);
}
results.Add(info);
}
foreach (KeyValuePair<string, List<MemoryPoolInfo>> assemblyInfo in m_ReferencePoolInfos) VisualElement section = CreateSection(Utility.Text.Format("Assembly: {0}", assemblyKey), out VisualElement card);
for (int j = 0; j < indices.Count; j++)
{ {
assemblyInfo.Value.Sort(m_ShowFullClassName ? m_FullClassNameComparer : m_NormalClassNameComparer); card.Add(CreatePoolInfoItem(in m_InfoBuffer[indices[j]], m_ShowFullClassName, scale));
VisualElement section = CreateSection(Utility.Text.Format("Assembly: {0}", assemblyInfo.Key), out VisualElement card);
if (assemblyInfo.Value.Count <= 0)
{
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));
}
} }
root.Add(section); 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<string, List<int>> 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<int> indices))
{
indices = new List<int>(8);
m_GroupedIndices.Add(assemblyName, indices);
} }
private static VisualElement CreatePoolInfoItem(MemoryPoolInfo info, bool showFullName, float scale) if (indices.Count == 0)
m_ActiveAssemblyKeys.Add(assemblyName);
indices.Add(i);
}
m_ActiveAssemblyKeys.Sort(StringComparer.Ordinal);
}
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(); VisualElement item = CreateCard();
item.style.marginBottom = 8f * scale; item.style.marginBottom = 8f * scale;
@ -115,7 +162,6 @@ namespace AlicizaX.Debugger.Runtime
titleLabel.style.marginBottom = 6f * scale; titleLabel.style.marginBottom = 6f * scale;
item.Add(titleLabel); item.Add(titleLabel);
// 基础统计
string stats = Utility.Text.Format( string stats = Utility.Text.Format(
"Unused {0} | Using {1} | Acquire {2} | Release {3} | Created {4}", "Unused {0} | Using {1} | Acquire {2} | Release {3} | Created {4}",
info.UnusedCount, info.UsingCount, info.UnusedCount, info.UsingCount,
@ -128,7 +174,6 @@ namespace AlicizaX.Debugger.Runtime
statsLabel.style.marginBottom = 4f * scale; statsLabel.style.marginBottom = 4f * scale;
item.Add(statsLabel); item.Add(statsLabel);
// 回收策略状态
string recycleStatus = Utility.Text.Format( string recycleStatus = Utility.Text.Format(
"HighWater {0} | MaxCap {1} | Idle {2}f | Array {3}", "HighWater {0} | MaxCap {1} | Idle {2}f | Array {3}",
info.HighWaterMark, info.MaxCapacity, info.HighWaterMark, info.MaxCapacity,
@ -137,7 +182,6 @@ namespace AlicizaX.Debugger.Runtime
recycleLabel.style.fontSize = 13f * scale; recycleLabel.style.fontSize = 13f * scale;
recycleLabel.style.whiteSpace = WhiteSpace.Normal; recycleLabel.style.whiteSpace = WhiteSpace.Normal;
// 根据空闲帧数着色:接近回收阈值时变色
if (info.IdleFrames >= 300) if (info.IdleFrames >= 300)
recycleLabel.style.color = DebuggerTheme.Warning; recycleLabel.style.color = DebuggerTheme.Warning;
else if (info.IdleFrames >= 200) else if (info.IdleFrames >= 200)

View File

@ -250,9 +250,9 @@ namespace AlicizaX
get => s_Count; get => s_Count;
} }
internal static MemoryPoolInfo GetInfo() internal static void GetInfo(ref MemoryPoolInfo info)
{ {
return new MemoryPoolInfo( info.Set(
typeof(T), s_Count, typeof(T), s_Count,
s_CurrentInUse, s_CurrentInUse,
s_AcquireCount, s_ReleaseCount, s_AcquireCount, s_ReleaseCount,

View File

@ -41,6 +41,11 @@ namespace AlicizaX
return MemoryPoolRegistry.GetAllInfos(); return MemoryPoolRegistry.GetAllInfos();
} }
public static int GetAllMemoryPoolInfos(MemoryPoolInfo[] infos)
{
return MemoryPoolRegistry.GetAllInfos(infos);
}
/// <summary> /// <summary>
/// 清除所有内存池。 /// 清除所有内存池。
/// </summary> /// </summary>

View File

@ -4,21 +4,21 @@ using System.Runtime.InteropServices;
namespace AlicizaX namespace AlicizaX
{ {
/// <summary> /// <summary>
/// 内存池信息。 /// Memory pool snapshot info.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Auto)] [StructLayout(LayoutKind.Auto)]
public struct MemoryPoolInfo public struct MemoryPoolInfo
{ {
private readonly Type _type; private Type _type;
private readonly int _unusedCount; private int _unusedCount;
private readonly int _usingCount; private int _usingCount;
private readonly int _acquireCount; private int _acquireCount;
private readonly int _releaseCount; private int _releaseCount;
private readonly int _createCount; private int _createCount;
private readonly int _highWaterMark; private int _highWaterMark;
private readonly int _maxCapacity; private int _maxCapacity;
private readonly int _idleFrames; private int _idleFrames;
private readonly int _poolArrayLength; private int _poolArrayLength;
public MemoryPoolInfo(Type type, int unusedCount, int usingCount, public MemoryPoolInfo(Type type, int unusedCount, int usingCount,
int acquireCount, int releaseCount, int createCount, int acquireCount, int releaseCount, int createCount,
@ -37,54 +37,41 @@ namespace AlicizaX
_poolArrayLength = poolArrayLength; _poolArrayLength = poolArrayLength;
} }
/// <summary>
/// 池类型。
/// </summary>
public Type Type => _type; public Type Type => _type;
/// <summary>
/// 池中空闲对象数量(可立即借出)。
/// </summary>
public int UnusedCount => _unusedCount; public int UnusedCount => _unusedCount;
/// <summary>
/// 当前被借出、尚未归还的对象数量。
/// </summary>
public int UsingCount => _usingCount; public int UsingCount => _usingCount;
/// <summary>
/// 累计 Acquire 调用次数(仅开发模式有效)。
/// </summary>
public int AcquireCount => _acquireCount; public int AcquireCount => _acquireCount;
/// <summary>
/// 累计 Release 调用次数(仅开发模式有效)。
/// </summary>
public int ReleaseCount => _releaseCount; public int ReleaseCount => _releaseCount;
/// <summary>
/// 累计 new T() 创建次数(池不够时的实际分配,仅开发模式有效)。
/// </summary>
public int CreateCount => _createCount; public int CreateCount => _createCount;
/// <summary>
/// 近期峰值并发使用量。回收策略据此决定保留多少对象。
/// </summary>
public int HighWaterMark => _highWaterMark; public int HighWaterMark => _highWaterMark;
/// <summary>
/// 池容量硬上限。超出后 Release 的对象直接丢弃交给 GC。
/// </summary>
public int MaxCapacity => _maxCapacity; public int MaxCapacity => _maxCapacity;
/// <summary>
/// 连续无 Acquire 的帧数。>=300 开始温和回收,>=900 激进回收+高水位衰减。
/// </summary>
public int IdleFrames => _idleFrames; public int IdleFrames => _idleFrames;
/// <summary>
/// 底层 T[] 数组的实际长度。反映真实内存占用(含空槽)。
/// </summary>
public int PoolArrayLength => _poolArrayLength; 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;
}
} }
} }

View File

@ -13,7 +13,7 @@ namespace AlicizaX
public delegate void ReleaseHandler(IMemory memory); public delegate void ReleaseHandler(IMemory memory);
public delegate void ClearHandler(); public delegate void ClearHandler();
public delegate void IntHandler(int value); public delegate void IntHandler(int value);
public delegate MemoryPoolInfo GetInfoHandler(); public delegate void GetInfoHandler(ref MemoryPoolInfo info);
public readonly AcquireHandler Acquire; public readonly AcquireHandler Acquire;
public readonly ReleaseHandler Release; public readonly ReleaseHandler Release;
@ -99,12 +99,29 @@ namespace AlicizaX
throw new Exception($"MemoryPool: Type '{type.FullName}' is not a valid IMemory type."); 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() public static MemoryPoolInfo[] GetAllInfos()
{ {
var infos = new MemoryPoolInfo[s_Handles.Count]; var infos = new MemoryPoolInfo[s_Handles.Count];
int i = 0; GetAllInfos(infos);
foreach (var kv in s_Handles)
infos[i++] = kv.Value.GetInfo();
return infos; return infos;
} }
@ -158,7 +175,9 @@ namespace AlicizaX
if (s_Handles.TryGetValue(type, out var handle)) 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); handle.Shrink(unused - count);
return; return;
} }
@ -166,7 +185,9 @@ namespace AlicizaX
EnsureRegistered(type); EnsureRegistered(type);
if (s_Handles.TryGetValue(type, out handle)) 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); handle.Shrink(unused - count);
return; return;
} }

View File

@ -0,0 +1,7 @@
namespace AlicizaX.ObjectPool
{
internal interface IObjectPoolDebugView
{
int GetAllObjectInfos(ObjectInfo[] results);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b99f3fc658c4a3d4f80218ab7113341e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
namespace AlicizaX.ObjectPool namespace AlicizaX.ObjectPool
{ {
public readonly struct ObjectPoolCreateOptions public readonly struct ObjectPoolCreateOptions
@ -53,11 +51,6 @@ namespace AlicizaX.ObjectPool
ObjectPoolBase GetObjectPool(Type objectType); ObjectPoolBase GetObjectPool(Type objectType);
ObjectPoolBase GetObjectPool(Type objectType, string name); ObjectPoolBase GetObjectPool(Type objectType, string name);
ObjectPoolBase[] GetAllObjectPools();
ObjectPoolBase[] GetAllObjectPools(bool sort);
void GetAllObjectPools(List<ObjectPoolBase> results);
void GetAllObjectPools(bool sort, List<ObjectPoolBase> results);
IObjectPool<T> CreatePool<T>(ObjectPoolCreateOptions options = default) where T : ObjectBase; IObjectPool<T> CreatePool<T>(ObjectPoolCreateOptions options = default) where T : ObjectBase;
ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default); ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default);

View File

@ -0,0 +1,7 @@
namespace AlicizaX.ObjectPool
{
internal interface IObjectPoolServiceDebugView
{
int GetAllObjectPools(bool sort, ObjectPoolBase[] results);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e3fa6e0005d58a4eba0e005ee613c61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,12 +1,11 @@
using System; using System;
using System.Collections.Generic;
namespace AlicizaX.ObjectPool namespace AlicizaX.ObjectPool
{ {
public abstract class ObjectPoolBase public abstract class ObjectPoolBase : IObjectPoolDebugView
{ {
private readonly string m_Name; private readonly string m_Name;
private string m_FullName;
public ObjectPoolBase() : this(null) { } public ObjectPoolBase() : this(null) { }
@ -17,7 +16,15 @@ namespace AlicizaX.ObjectPool
public string Name => m_Name; 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 Type ObjectType { get; }
public abstract int Count { get; } public abstract int Count { get; }
@ -39,8 +46,7 @@ namespace AlicizaX.ObjectPool
public abstract void Release(int toReleaseCount); public abstract void Release(int toReleaseCount);
public abstract void ReleaseAllUnused(); public abstract void ReleaseAllUnused();
public abstract ObjectInfo[] GetAllObjectInfos(); public abstract int GetAllObjectInfos(ObjectInfo[] results);
public abstract void GetAllObjectInfos(List<ObjectInfo> results);
internal abstract void Update(float elapseSeconds, float realElapseSeconds); internal abstract void Update(float elapseSeconds, float realElapseSeconds);
internal abstract void Shutdown(); internal abstract void Shutdown();

View File

@ -28,9 +28,12 @@ namespace AlicizaX
svc.OnLowMemory(); 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;
} }
} }
} }

View File

@ -184,26 +184,16 @@ namespace AlicizaX.ObjectPool
return SpawnAny(name); return SpawnAny(name);
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return null; 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; float now = Time.realtimeSinceStartup;
int current = head; SpawnSlot(head, now);
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(); ValidateState();
return slot.Obj; return slot.Obj;
} }
current = slot.NextAvailableByName;
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanSpawn() => CanSpawn(string.Empty); public bool CanSpawn() => CanSpawn(string.Empty);
@ -213,18 +203,7 @@ namespace AlicizaX.ObjectPool
if (m_AllowMultiSpawn) if (m_AllowMultiSpawn)
return m_AllNameHeadMap.ContainsKey(name); return m_AllNameHeadMap.ContainsKey(name);
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return false; return m_AvailableNameHeadMap.ContainsKey(name);
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;
} }
public void Unspawn(T obj) public void Unspawn(T obj)
@ -334,34 +313,28 @@ namespace AlicizaX.ObjectPool
ValidateState(); ValidateState();
} }
public override ObjectInfo[] GetAllObjectInfos() public override int GetAllObjectInfos(ObjectInfo[] results)
{
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<ObjectInfo> results)
{ {
if (results == null) throw new GameFrameworkException("Results is invalid."); 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++) for (int i = 0; i < m_SlotCount; i++)
{ {
ref var slot = ref m_Slots[i]; ref var slot = ref m_Slots[i];
if (!slot.IsAlive()) continue; if (!slot.IsAlive()) continue;
results.Add(new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
if (write < capacity)
{
results[write] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
slot.Obj.CustomCanReleaseFlag, slot.Obj.CustomCanReleaseFlag,
slot.Obj.LastUseTime, slot.SpawnCount)); slot.Obj.LastUseTime, slot.SpawnCount);
} }
write++;
}
return write;
} }
internal override void OnLowMemory() internal override void OnLowMemory()
@ -704,7 +677,7 @@ namespace AlicizaX.ObjectPool
{ {
ref var slot = ref m_Slots[idx]; ref var slot = ref m_Slots[idx];
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0) 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; string objectName = slot.Obj.Name ?? string.Empty;
if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail)) if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail))
@ -766,6 +739,7 @@ namespace AlicizaX.ObjectPool
return null; return null;
SpawnSlot(head, now); SpawnSlot(head, now);
ValidateState();
return slot.Obj; return slot.Obj;
} }
} }

View File

@ -6,7 +6,7 @@ namespace AlicizaX.ObjectPool
{ {
[UnityEngine.Scripting.Preserve] [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 float DefaultAutoReleaseInterval = float.MaxValue;
private const int DefaultCapacity = int.MaxValue; private const int DefaultCapacity = int.MaxValue;
@ -15,7 +15,7 @@ namespace AlicizaX.ObjectPool
private readonly Dictionary<TypeNamePair, ObjectPoolBase> m_ObjectPools; private readonly Dictionary<TypeNamePair, ObjectPoolBase> m_ObjectPools;
private readonly List<ObjectPoolBase> m_ObjectPoolList; private readonly List<ObjectPoolBase> m_ObjectPoolList;
private readonly Dictionary<ObjectPoolBase, int> m_ObjectPoolIndexMap; private readonly Dictionary<ObjectPoolBase, int> m_ObjectPoolIndexMap;
private readonly List<ObjectPoolBase> m_CachedAllObjectPools; private readonly List<ObjectPoolBase> m_CachedSortedObjectPools;
private readonly Comparison<ObjectPoolBase> m_ObjectPoolComparer; private readonly Comparison<ObjectPoolBase> m_ObjectPoolComparer;
public ObjectPoolService() public ObjectPoolService()
@ -23,7 +23,7 @@ namespace AlicizaX.ObjectPool
m_ObjectPools = new Dictionary<TypeNamePair, ObjectPoolBase>(); m_ObjectPools = new Dictionary<TypeNamePair, ObjectPoolBase>();
m_ObjectPoolList = new List<ObjectPoolBase>(); m_ObjectPoolList = new List<ObjectPoolBase>();
m_ObjectPoolIndexMap = new Dictionary<ObjectPoolBase, int>(AlicizaX.ReferenceComparer<ObjectPoolBase>.Instance); m_ObjectPoolIndexMap = new Dictionary<ObjectPoolBase, int>(AlicizaX.ReferenceComparer<ObjectPoolBase>.Instance);
m_CachedAllObjectPools = new List<ObjectPoolBase>(); m_CachedSortedObjectPools = new List<ObjectPoolBase>();
m_ObjectPoolComparer = ObjectPoolComparer; m_ObjectPoolComparer = ObjectPoolComparer;
} }
@ -45,7 +45,7 @@ namespace AlicizaX.ObjectPool
m_ObjectPools.Clear(); m_ObjectPools.Clear();
m_ObjectPoolList.Clear(); m_ObjectPoolList.Clear();
m_ObjectPoolIndexMap.Clear(); m_ObjectPoolIndexMap.Clear();
m_CachedAllObjectPools.Clear(); m_CachedSortedObjectPools.Clear();
} }
// ========== Has ========== // ========== Has ==========
@ -90,24 +90,23 @@ namespace AlicizaX.ObjectPool
// ========== GetAll ========== // ========== GetAll ==========
public ObjectPoolBase[] GetAllObjectPools() => m_ObjectPoolList.ToArray(); int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results)
public ObjectPoolBase[] GetAllObjectPools(bool sort)
{
if (!sort) return m_ObjectPoolList.ToArray();
var results = new List<ObjectPoolBase>(m_ObjectPoolList);
results.Sort(m_ObjectPoolComparer);
return results.ToArray();
}
public void GetAllObjectPools(List<ObjectPoolBase> results) => GetAllObjectPools(false, results);
public void GetAllObjectPools(bool sort, List<ObjectPoolBase> results)
{ {
if (results == null) throw new GameFrameworkException("Results is invalid."); if (results == null) throw new GameFrameworkException("Results is invalid.");
results.Clear();
results.AddRange(m_ObjectPoolList); List<ObjectPoolBase> source = m_ObjectPoolList;
if (sort) results.Sort(m_ObjectPoolComparer); 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) ========== // ========== Create (single entry point) ==========
@ -190,16 +189,16 @@ namespace AlicizaX.ObjectPool
public void Release() public void Release()
{ {
GetAllObjectPools(true, m_CachedAllObjectPools); CacheSortedObjectPools();
for (int i = 0; i < m_CachedAllObjectPools.Count; i++) for (int i = 0; i < m_CachedSortedObjectPools.Count; i++)
m_CachedAllObjectPools[i].Release(); m_CachedSortedObjectPools[i].Release();
} }
public void ReleaseAllUnused() public void ReleaseAllUnused()
{ {
GetAllObjectPools(true, m_CachedAllObjectPools); CacheSortedObjectPools();
for (int i = 0; i < m_CachedAllObjectPools.Count; i++) for (int i = 0; i < m_CachedSortedObjectPools.Count; i++)
m_CachedAllObjectPools[i].ReleaseAllUnused(); m_CachedSortedObjectPools[i].ReleaseAllUnused();
} }
// ========== Low memory ========== // ========== Low memory ==========
@ -230,6 +229,13 @@ namespace AlicizaX.ObjectPool
return false; return false;
} }
private void CacheSortedObjectPools()
{
m_CachedSortedObjectPools.Clear();
m_CachedSortedObjectPools.AddRange(m_ObjectPoolList);
m_CachedSortedObjectPools.Sort(m_ObjectPoolComparer);
}
private void RemovePoolFromList(ObjectPoolBase pool) private void RemovePoolFromList(ObjectPoolBase pool)
{ {
if (!m_ObjectPoolIndexMap.TryGetValue(pool, out int index)) if (!m_ObjectPoolIndexMap.TryGetValue(pool, out int index))