[Opt] ObjectPoolService && MemoryPool [Add]Benchmark Example

优化ObjcetPoolService 优化MemoryPoolService
增加ObjectPool 和MemoryPool的Benchmark
This commit is contained in:
陈思海 2026-04-27 14:38:24 +08:00
parent 58fb685792
commit 9afd5d9ff9
25 changed files with 2380 additions and 379 deletions

View File

@ -99,7 +99,7 @@ namespace AlicizaX
/// <returns>被比较的对象是否与自身相等。</returns>
public bool Equals(TypeNamePair value)
{
return m_Type == value.m_Type && m_Name == value.m_Name;
return m_Type == value.m_Type && string.Equals(m_Name, value.m_Name, StringComparison.Ordinal);
}
/// <summary>

View File

@ -4,7 +4,7 @@ using UnityEngine;
namespace AlicizaX.Audio.Runtime
{
internal sealed class AudioSourceObject : ObjectBase
internal sealed class AudioSourceObject : ObjectBase<AudioSource>
{
private AudioSource _source;
private AudioLowPassFilter _lowPassFilter;

View File

@ -9,14 +9,14 @@ namespace AlicizaX.Debugger.Runtime
private sealed class ObjectPoolInformationWindow : PollingDebuggerWindowBase
{
private IObjectPoolService m_ObjectPoolService;
private IObjectPoolServiceDebugView m_ObjectPoolDebugView;
private ObjectPoolService m_ObjectPoolServiceImpl;
private ObjectPoolBase[] m_ObjectPools;
private ObjectInfo[] m_ObjectInfos;
public override void Initialize(params object[] args)
{
m_ObjectPoolService = AppServices.Require<IObjectPoolService>();
m_ObjectPoolDebugView = m_ObjectPoolService as IObjectPoolServiceDebugView;
m_ObjectPoolServiceImpl = m_ObjectPoolService as ObjectPoolService;
}
protected override void BuildWindow(VisualElement root)
@ -31,8 +31,8 @@ namespace AlicizaX.Debugger.Runtime
root.Add(overview);
int objectPoolCount = EnsureObjectPoolBuffer(m_ObjectPoolService.Count);
objectPoolCount = m_ObjectPoolDebugView != null
? m_ObjectPoolDebugView.GetAllObjectPools(true, m_ObjectPools)
objectPoolCount = m_ObjectPoolServiceImpl != null
? m_ObjectPoolServiceImpl.GetAllObjectPools(true, m_ObjectPools)
: 0;
for (int i = 0; i < objectPoolCount; i++)
{

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0cd45b8170524028b8e9d569c3bb5b9d
timeCreated: 1777270000

View File

@ -0,0 +1,814 @@
#if UNITY_EDITOR
using System;
using System.Diagnostics;
using Cysharp.Text;
using Unity.Profiling;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace AlicizaX
{
[DisallowMultipleComponent]
[AddComponentMenu("Game Framework/MemoryPool Benchmark")]
public sealed class MemoryPoolBenchmark : MonoBehaviour
{
[SerializeField] private bool runOnStart = true;
[SerializeField] private int objectCount = 10000;
[SerializeField] private int loopCount = 100000;
[SerializeField] private int adaptiveFrameCount = 420;
[SerializeField] private int burstSize = 4096;
[SerializeField] private int extremeBurstSize = 32768;
[SerializeField] private int waveCount = 24;
[SerializeField] private int multiTypeCount = 2048;
[SerializeField] private bool logEachCase = true;
[SerializeField] private bool logMemoryDelta = true;
[SerializeField] private int maxCapturedLogChars = 128 * 1024;
private static readonly ProfilerMarker s_TotalMarker = new ProfilerMarker("MemoryPoolBenchmark.Total");
private static readonly ProfilerMarker s_SimpleMarker = new ProfilerMarker("MemoryPoolBenchmark.Simple");
private static readonly ProfilerMarker s_AcquireReleaseMarker = new ProfilerMarker("MemoryPoolBenchmark.AcquireRelease");
private static readonly ProfilerMarker s_AdaptiveMarker = new ProfilerMarker("MemoryPoolBenchmark.AdaptivePolicy");
private static readonly ProfilerMarker s_ExtremeMarker = new ProfilerMarker("MemoryPoolBenchmark.Extreme");
private static readonly ProfilerMarker s_InfoMarker = new ProfilerMarker("MemoryPoolBenchmark.InfoBuffer");
private static readonly ProfilerMarker s_CompactMarker = new ProfilerMarker("MemoryPoolBenchmark.Compact");
private readonly Stopwatch m_Stopwatch = new Stopwatch();
private Utf16ValueStringBuilder m_LogBuilder;
private bool m_LogBuilderCreated;
private int m_FailCount;
private int m_CaseCount;
private long m_CaseAllocBefore;
private long m_CaseAllocAfter;
private BenchmarkMemory[] m_Buffer;
private SimpleMemory[] m_SimpleBuffer;
private BenchmarkMemoryA[] m_BufferA;
private BenchmarkMemoryB[] m_BufferB;
private BenchmarkMemoryC[] m_BufferC;
private MemoryPoolInfo[] m_InfoBuffer;
private void OnEnable()
{
ClearCapturedConsoleOutput();
Application.logMessageReceived += OnLogMessageReceived;
}
private void OnDisable()
{
Application.logMessageReceived -= OnLogMessageReceived;
m_LogBuilder.Dispose();
m_LogBuilderCreated = false;
}
private void Start()
{
if (runOnStart)
RunAll();
}
[ContextMenu("Run MemoryPool Benchmark")]
public void RunAll()
{
ClearCapturedConsoleOutput();
m_FailCount = 0;
m_CaseCount = 0;
int maxBuffer = Math.Max(Math.Max(objectCount, burstSize), extremeBurstSize);
EnsureBuffer(maxBuffer);
EnsureSimpleBuffer(64);
EnsureTypedBuffers(Math.Max(multiTypeCount, burstSize));
using (s_TotalMarker.Auto())
{
RunCase("Simple Acquire/Release", RunSimpleAcquireRelease);
RunCase("Simple Reuse Identity", RunSimpleReuseIdentity);
RunCase("Simple Capacity Learning", RunSimpleCapacityLearning);
RunCase("Acquire/Release Hot Loop", RunAcquireReleaseHotLoop);
RunCase("Interleaved Acquire Release", RunInterleavedAcquireRelease);
RunCase("Generic API Hot Loop", RunGenericApiHotLoop);
RunCase("Facade Generic Release Hot Loop", RunFacadeGenericReleaseHotLoop);
RunCase("Facade Acquire Direct Release", RunFacadeAcquireDirectRelease);
RunCase("Direct Acquire Facade Release", RunDirectAcquireFacadeRelease);
RunCase("Adaptive Burst Fill", RunAdaptiveBurstFill);
RunCase("Idle Budget Release", RunIdleBudgetRelease);
RunCase("Wave Burst Anti Thrash", RunWaveBurstAntiThrash);
RunCase("Extreme Single Burst", RunExtremeSingleBurst);
RunCase("Extreme Hard Capacity Overflow", RunExtremeHardCapacityOverflow);
RunCase("Multi Type Active Queue", RunMultiTypeActiveQueue);
RunCase("ClearAll Unschedule", RunClearAllUnschedule);
RunCase("ClearAll Active Queue Reset", RunClearAllActiveQueueReset);
RunCase("Type API Cold Path", RunTypeApiColdPath);
RunCase("Info Buffer No Alloc", RunInfoBufferNoAlloc);
RunCase("Explicit Compact", RunExplicitCompact);
}
Debug.Log(BuildLog("MemoryPool benchmark finished. cases=", m_CaseCount, ", fails=", m_FailCount));
}
[ContextMenu("Copy Captured Console Output")]
public void CopyCapturedConsoleOutput()
{
EnsureLogBuilder();
string text = m_LogBuilder.ToString();
GUIUtility.systemCopyBuffer = text;
Debug.Log(BuildLog("MemoryPoolBenchmark copied console output chars=", text.Length, ", max=", maxCapturedLogChars));
}
[ContextMenu("Clear Captured Console Output")]
public void ClearCapturedConsoleOutput()
{
m_LogBuilder.Dispose();
m_LogBuilder = ZString.CreateStringBuilder();
m_LogBuilderCreated = true;
}
private void EnsureLogBuilder()
{
if (!m_LogBuilderCreated)
{
m_LogBuilder = ZString.CreateStringBuilder();
m_LogBuilderCreated = true;
}
}
private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
EnsureLogBuilder();
if (m_LogBuilder.Length >= maxCapturedLogChars)
return;
m_LogBuilder.Append('[');
m_LogBuilder.Append(type);
m_LogBuilder.Append("] ");
m_LogBuilder.Append(condition);
m_LogBuilder.AppendLine();
if (type == LogType.Exception || type == LogType.Error || type == LogType.Assert)
{
m_LogBuilder.Append(stackTrace);
m_LogBuilder.AppendLine();
}
}
private void RunCase(string caseName, Action action)
{
m_CaseCount++;
m_CaseAllocBefore = GetAllocatedBytesForCurrentThread();
m_CaseAllocAfter = m_CaseAllocBefore;
m_Stopwatch.Restart();
action();
if (m_Stopwatch.IsRunning)
{
m_Stopwatch.Stop();
m_CaseAllocAfter = GetAllocatedBytesForCurrentThread();
}
if (!logEachCase)
return;
if (logMemoryDelta)
Debug.Log(BuildLog("[MemoryPoolBenchmark] ", caseName, " ms=", m_Stopwatch.Elapsed.TotalMilliseconds, " gcAlloc=", m_CaseAllocAfter - m_CaseAllocBefore));
else
Debug.Log(BuildLog("[MemoryPoolBenchmark] ", caseName, " ms=", m_Stopwatch.Elapsed.TotalMilliseconds));
}
private void RestartCaseMeasure()
{
m_CaseAllocBefore = GetAllocatedBytesForCurrentThread();
m_CaseAllocAfter = m_CaseAllocBefore;
m_Stopwatch.Restart();
}
private void StopCaseMeasure()
{
m_Stopwatch.Stop();
m_CaseAllocAfter = GetAllocatedBytesForCurrentThread();
}
private long GetAllocatedBytesForCurrentThread()
{
return logMemoryDelta ? GC.GetAllocatedBytesForCurrentThread() : 0L;
}
private void RunSimpleAcquireRelease()
{
using (s_SimpleMarker.Auto())
{
MemoryPool<SimpleMemory>.ClearAll();
MemoryPool<SimpleMemory>.Prewarm(1);
RestartCaseMeasure();
SimpleMemory item = MemoryPool<SimpleMemory>.Acquire();
item.Value = 7;
MemoryPool<SimpleMemory>.Release(item);
StopCaseMeasure();
AssertEqual(item.Value, 0, "simple release did not clear object");
MemoryPool<SimpleMemory>.ClearAll();
}
}
private void RunSimpleReuseIdentity()
{
using (s_SimpleMarker.Auto())
{
MemoryPool<SimpleMemory>.ClearAll();
MemoryPool<SimpleMemory>.Prewarm(1);
SimpleMemory first = MemoryPool<SimpleMemory>.Acquire();
MemoryPool<SimpleMemory>.Release(first);
RestartCaseMeasure();
SimpleMemory second = MemoryPool<SimpleMemory>.Acquire();
StopCaseMeasure();
AssertTrue(ReferenceEquals(first, second), "simple reuse did not return same instance");
MemoryPool<SimpleMemory>.Release(second);
MemoryPool<SimpleMemory>.ClearAll();
}
}
private void RunSimpleCapacityLearning()
{
using (s_SimpleMarker.Auto())
{
MemoryPool<SimpleMemory>.ClearAll();
MemoryPool<SimpleMemory>.SetCapacity(64, 256);
for (int i = 0; i < 48; i++)
m_SimpleBuffer[i] = null;
RestartCaseMeasure();
for (int i = 0; i < 48; i++)
m_SimpleBuffer[i] = MemoryPool<SimpleMemory>.Acquire();
StopCaseMeasure();
for (int i = 0; i < 48; i++)
{
MemoryPool<SimpleMemory>.Release(m_SimpleBuffer[i]);
m_SimpleBuffer[i] = null;
}
MemoryPool<SimpleMemory>.ClearAll();
}
}
private void RunAcquireReleaseHotLoop()
{
using (s_AcquireReleaseMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(objectCount, objectCount << 2);
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkMemory item = MemoryPool<BenchmarkMemory>.Acquire();
item.Value = i;
MemoryPool<BenchmarkMemory>.Release(item);
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunInterleavedAcquireRelease()
{
using (s_AcquireReleaseMarker.Auto())
{
int count = Math.Min(objectCount, m_Buffer.Length);
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(count, count << 2);
MemoryPool<BenchmarkMemory>.Prewarm(count >> 1);
RestartCaseMeasure();
for (int i = 0; i < count; i++)
{
BenchmarkMemory item = MemoryPool<BenchmarkMemory>.Acquire();
item.Value = i;
if ((i & 1) == 0)
{
MemoryPool<BenchmarkMemory>.Release(item);
}
else
{
m_Buffer[i] = item;
}
}
for (int i = 1; i < count; i += 2)
{
MemoryPool<BenchmarkMemory>.Release(m_Buffer[i]);
m_Buffer[i] = null;
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunGenericApiHotLoop()
{
using (s_AcquireReleaseMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(objectCount, objectCount << 2);
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkMemory item = MemoryPool.Acquire<BenchmarkMemory>();
MemoryPool.Release(item);
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunFacadeGenericReleaseHotLoop()
{
using (s_AcquireReleaseMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(objectCount, objectCount << 2);
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkMemory item = MemoryPool.Acquire<BenchmarkMemory>();
MemoryPool.Release<BenchmarkMemory>(item);
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunFacadeAcquireDirectRelease()
{
using (s_AcquireReleaseMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(objectCount, objectCount << 2);
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkMemory item = MemoryPool.Acquire<BenchmarkMemory>();
MemoryPool<BenchmarkMemory>.Release(item);
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunDirectAcquireFacadeRelease()
{
using (s_AcquireReleaseMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(objectCount, objectCount << 2);
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkMemory item = MemoryPool<BenchmarkMemory>.Acquire();
MemoryPool.Release<BenchmarkMemory>(item);
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunAdaptiveBurstFill()
{
using (s_AdaptiveMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(Math.Max(64, burstSize >> 1), burstSize << 1);
RestartCaseMeasure();
for (int i = 0; i < burstSize; i++)
m_Buffer[i] = MemoryPool<BenchmarkMemory>.Acquire();
for (int i = 0; i < burstSize; i++)
{
MemoryPool<BenchmarkMemory>.Release(m_Buffer[i]);
m_Buffer[i] = null;
}
for (int frame = 0; frame < adaptiveFrameCount; frame++)
MemoryPoolRegistry.TickAll(frame);
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.UnusedCount > 0, "adaptive fill did not keep reserve");
AssertTrue(info.PoolArrayLength >= info.UnusedCount, "pool array smaller than unused count");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunIdleBudgetRelease()
{
using (s_AdaptiveMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(burstSize, burstSize << 1);
MemoryPool<BenchmarkMemory>.Prewarm(burstSize);
RestartCaseMeasure();
for (int frame = 0; frame < adaptiveFrameCount + 360; frame++)
MemoryPoolRegistry.TickAll(frame + 10000);
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.UnusedCount < burstSize, "idle release did not reduce unused objects");
AssertTrue(info.PoolArrayLength >= burstSize, "idle release should not compact backing array");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunWaveBurstAntiThrash()
{
using (s_ExtremeMarker.Auto())
{
int count = Math.Min(burstSize, m_Buffer.Length);
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(count, count << 1);
RestartCaseMeasure();
for (int wave = 0; wave < waveCount; wave++)
{
int waveSize = (wave & 1) == 0 ? count : count >> 2;
for (int i = 0; i < waveSize; i++)
m_Buffer[i] = MemoryPool<BenchmarkMemory>.Acquire();
for (int i = 0; i < waveSize; i++)
{
MemoryPool<BenchmarkMemory>.Release(m_Buffer[i]);
m_Buffer[i] = null;
}
for (int frame = 0; frame < 12; frame++)
MemoryPoolRegistry.TickAll(20000 + wave * 16 + frame);
}
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.PoolArrayLength >= count, "wave burst backing array shrank unexpectedly");
AssertTrue(info.UnusedCount > 0, "wave burst failed to retain reserve");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunExtremeSingleBurst()
{
using (s_ExtremeMarker.Auto())
{
int count = Math.Min(extremeBurstSize, m_Buffer.Length);
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(Math.Max(128, count >> 2), count);
RestartCaseMeasure();
for (int i = 0; i < count; i++)
m_Buffer[i] = MemoryPool<BenchmarkMemory>.Acquire();
for (int i = 0; i < count; i++)
{
MemoryPool<BenchmarkMemory>.Release(m_Buffer[i]);
m_Buffer[i] = null;
}
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.UnusedCount == count, "extreme single burst did not keep released objects under hard cap");
AssertTrue(info.PoolArrayLength >= count, "extreme single burst did not grow backing array");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunExtremeHardCapacityOverflow()
{
using (s_ExtremeMarker.Auto())
{
int count = Math.Min(burstSize, m_Buffer.Length);
int hardCapacity = Math.Max(32, count >> 3);
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(hardCapacity >> 1, hardCapacity);
RestartCaseMeasure();
for (int i = 0; i < count; i++)
m_Buffer[i] = MemoryPool<BenchmarkMemory>.Acquire();
for (int i = 0; i < count; i++)
{
MemoryPool<BenchmarkMemory>.Release(m_Buffer[i]);
m_Buffer[i] = null;
}
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.UnusedCount == hardCapacity, "hard capacity overflow retained more than hard cap");
AssertTrue(info.PoolArrayLength == hardCapacity, "hard capacity overflow grew array past hard cap");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunMultiTypeActiveQueue()
{
using (s_ExtremeMarker.Auto())
{
int count = Math.Min(multiTypeCount, m_BufferA.Length);
MemoryPool<BenchmarkMemoryA>.ClearAll();
MemoryPool<BenchmarkMemoryB>.ClearAll();
MemoryPool<BenchmarkMemoryC>.ClearAll();
MemoryPool<BenchmarkMemoryA>.SetCapacity(count, count << 1);
MemoryPool<BenchmarkMemoryB>.SetCapacity(count, count << 1);
MemoryPool<BenchmarkMemoryC>.SetCapacity(count, count << 1);
RestartCaseMeasure();
for (int i = 0; i < count; i++)
{
m_BufferA[i] = MemoryPool<BenchmarkMemoryA>.Acquire();
m_BufferB[i] = MemoryPool<BenchmarkMemoryB>.Acquire();
m_BufferC[i] = MemoryPool<BenchmarkMemoryC>.Acquire();
}
for (int i = 0; i < count; i++)
{
MemoryPool<BenchmarkMemoryA>.Release(m_BufferA[i]);
MemoryPool<BenchmarkMemoryB>.Release(m_BufferB[i]);
MemoryPool<BenchmarkMemoryC>.Release(m_BufferC[i]);
m_BufferA[i] = null;
m_BufferB[i] = null;
m_BufferC[i] = null;
}
for (int frame = 0; frame < adaptiveFrameCount; frame++)
MemoryPoolRegistry.TickAll(30000 + frame);
StopCaseMeasure();
AssertTrue(GetBenchmarkInfo(typeof(BenchmarkMemoryA)).UnusedCount > 0, "type A did not tick");
AssertTrue(GetBenchmarkInfo(typeof(BenchmarkMemoryB)).UnusedCount > 0, "type B did not tick");
AssertTrue(GetBenchmarkInfo(typeof(BenchmarkMemoryC)).UnusedCount > 0, "type C did not tick");
MemoryPool<BenchmarkMemoryA>.ClearAll();
MemoryPool<BenchmarkMemoryB>.ClearAll();
MemoryPool<BenchmarkMemoryC>.ClearAll();
}
}
private void RunClearAllUnschedule()
{
using (s_ExtremeMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(burstSize, burstSize << 1);
MemoryPool<BenchmarkMemory>.Prewarm(burstSize);
MemoryPoolRegistry.TickAll(39000);
MemoryPool<BenchmarkMemory>.ClearAll();
RestartCaseMeasure();
for (int frame = 0; frame < adaptiveFrameCount; frame++)
MemoryPoolRegistry.TickAll(39001 + frame);
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.UnusedCount == 0, "clear all should unschedule single pool tick");
AssertTrue(info.PoolArrayLength == 0, "clear all should keep backing array empty until reused");
}
}
private void RunClearAllActiveQueueReset()
{
using (s_ExtremeMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(burstSize, burstSize << 1);
MemoryPool<BenchmarkMemory>.Prewarm(burstSize);
MemoryPoolRegistry.TickAll(40000);
MemoryPoolRegistry.ClearAll();
RestartCaseMeasure();
MemoryPool<BenchmarkMemory>.Prewarm(16);
MemoryPoolRegistry.TickAll(40001);
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.UnusedCount >= 16, "clear all active queue reset blocked reschedule");
AssertTrue(info.PoolArrayLength >= 16, "clear all active queue reset did not regrow backing array");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunTypeApiColdPath()
{
using (s_ExtremeMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
Type type = typeof(BenchmarkMemory);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
IMemory item = MemoryPool.Acquire(type);
MemoryPool.Release(item);
}
StopCaseMeasure();
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private void RunInfoBufferNoAlloc()
{
using (s_InfoMarker.Auto())
{
EnsureInfoBuffer(Math.Max(1, MemoryPool.Count));
RestartCaseMeasure();
int count = MemoryPool.GetAllMemoryPoolInfos(m_InfoBuffer);
StopCaseMeasure();
AssertTrue(count <= m_InfoBuffer.Length, "info count exceeded buffer length");
}
}
private void RunExplicitCompact()
{
using (s_CompactMarker.Auto())
{
MemoryPool<BenchmarkMemory>.ClearAll();
MemoryPool<BenchmarkMemory>.SetCapacity(objectCount, objectCount << 2);
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
MemoryPool<BenchmarkMemory>.Shrink(8);
RestartCaseMeasure();
MemoryPool<BenchmarkMemory>.Compact();
StopCaseMeasure();
MemoryPoolInfo info = GetBenchmarkInfo(typeof(BenchmarkMemory));
AssertTrue(info.PoolArrayLength <= 8, "compact did not shrink backing array");
MemoryPool<BenchmarkMemory>.ClearAll();
}
}
private MemoryPoolInfo GetBenchmarkInfo(Type targetType)
{
EnsureInfoBuffer(Math.Max(1, MemoryPool.Count));
int count = MemoryPool.GetAllMemoryPoolInfos(m_InfoBuffer);
for (int i = 0; i < count; i++)
{
if (m_InfoBuffer[i].Type == targetType)
return m_InfoBuffer[i];
}
return default;
}
private void EnsureBuffer(int count)
{
if (m_Buffer == null || m_Buffer.Length < count)
m_Buffer = new BenchmarkMemory[count];
}
private void EnsureSimpleBuffer(int count)
{
if (m_SimpleBuffer == null || m_SimpleBuffer.Length < count)
m_SimpleBuffer = new SimpleMemory[count];
}
private void EnsureTypedBuffers(int count)
{
if (m_BufferA == null || m_BufferA.Length < count)
m_BufferA = new BenchmarkMemoryA[count];
if (m_BufferB == null || m_BufferB.Length < count)
m_BufferB = new BenchmarkMemoryB[count];
if (m_BufferC == null || m_BufferC.Length < count)
m_BufferC = new BenchmarkMemoryC[count];
}
private void EnsureInfoBuffer(int count)
{
if (m_InfoBuffer == null || m_InfoBuffer.Length < count)
m_InfoBuffer = new MemoryPoolInfo[count];
}
private void AssertTrue(bool value, string message)
{
if (value)
return;
m_FailCount++;
Debug.LogError(message);
}
private void AssertEqual(int actual, int expected, string message)
{
if (actual == expected)
return;
m_FailCount++;
Debug.LogError(BuildLog(message, " actual=", actual, ", expected=", expected));
}
private static string BuildLog(object a, string b, object c, string d, object e)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(a);
builder.Append(b);
builder.Append(c);
builder.Append(d);
builder.Append(e);
return builder.ToString();
}
}
private static string BuildLog(string a, object b, string c, object d)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(a);
builder.Append(b);
builder.Append(c);
builder.Append(d);
return builder.ToString();
}
}
private static string BuildLog(string a, object b, string c, object d, string e, object f)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(a);
builder.Append(b);
builder.Append(c);
builder.Append(d);
builder.Append(e);
builder.Append(f);
return builder.ToString();
}
}
private sealed class SimpleMemory : IMemory
{
public int Value;
public void Clear()
{
Value = 0;
}
}
private sealed class BenchmarkMemory : IMemory
{
public int Value;
public void Clear()
{
Value = 0;
}
}
private sealed class BenchmarkMemoryA : IMemory
{
public int Value;
public void Clear()
{
Value = 0;
}
}
private sealed class BenchmarkMemoryB : IMemory
{
public int Value;
public void Clear()
{
Value = 0;
}
}
private sealed class BenchmarkMemoryC : IMemory
{
public int Value;
public void Clear()
{
Value = 0;
}
}
}
}
#endif

View File

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

View File

@ -4,7 +4,6 @@ using System.Runtime.CompilerServices;
namespace AlicizaX
{
public static class MemoryPool<T> where T : class, IMemory, new()
{
private sealed class ReferenceComparer : IEqualityComparer<T>
@ -24,40 +23,46 @@ namespace AlicizaX
}
}
private static readonly MemoryPoolRegistry.MemoryPoolHandle s_Handle;
private static T[] s_Stack = Array.Empty<T>();
private static int s_Count;
private static int s_MaxCapacity = 2048;
private static int s_SoftCapacity = 2048;
private static int s_HardCapacity = 8192;
private static Dictionary<T, byte> s_InPoolSet;
private static int s_StrictCheckVersion = -1;
// ---- 回收策略状态 ----
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 static int s_PeakInUseShort;
private static int s_PeakInUseLong;
private static int s_AcquireThisFrame;
private static int s_ReleaseThisFrame;
private static int s_TargetReserve = MIN_KEEP;
private static int s_IdleFrames;
private static int s_HotFrames;
private static int s_LastTickFrame = -1;
private static int s_ConsecutiveMiss;
private const int IDLE_THRESHOLD = 300; // ~5s @60fps
private const int IDLE_AGGRESSIVE = 900; // ~15s @60fps
private const int MIN_KEEP = 4;
private const int SHORT_DECAY_START = 300;
private const int LONG_DECAY_START = 1800;
private const int UNSCHEDULE_IDLE_FRAMES = 3600;
// ---- 统计计数器 ----
private static int s_AcquireCount;
private static int s_ReleaseCount;
private static int s_CreateCount;
static MemoryPool()
{
MemoryPoolRegistry.Register(typeof(T), new MemoryPoolRegistry.MemoryPoolHandle(
s_Handle = new MemoryPoolRegistry.MemoryPoolHandle(
acquire: AcquireAsMemory,
release: ReleaseAsMemory,
clear: ClearAll,
prewarm: Prewarm,
getInfo: GetInfo,
tick: Tick,
shrink: Shrink
));
shrink: Shrink,
compact: Compact);
MemoryPoolRegistry.Register(typeof(T), s_Handle);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -75,14 +80,18 @@ namespace AlicizaX
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Acquire()
{
MemoryPoolRegistry.ScheduleTick(s_Handle);
s_AcquireCount++;
s_RecentAcquireCount++;
s_AcquireThisFrame++;
s_CurrentInUse++;
if (s_CurrentInUse > s_PeakInUse)
s_PeakInUse = s_CurrentInUse;
if (s_CurrentInUse > s_PeakInUseShort)
s_PeakInUseShort = s_CurrentInUse;
if (s_CurrentInUse > s_PeakInUseLong)
s_PeakInUseLong = s_CurrentInUse;
if (s_Count > 0)
{
s_ConsecutiveMiss = 0;
int idx = --s_Count;
T item = s_Stack[idx];
s_Stack[idx] = null;
@ -90,6 +99,7 @@ namespace AlicizaX
return item;
}
s_ConsecutiveMiss++;
return CreateNew();
}
@ -105,107 +115,139 @@ namespace AlicizaX
{
if (item == null) return;
MemoryPoolRegistry.ScheduleTick(s_Handle);
EnsureStrictCheckState();
if (MemoryPool.EnableStrictCheck && s_InPoolSet.ContainsKey(item))
throw new InvalidOperationException($"MemoryPool<{typeof(T).Name}>: Double release detected.");
s_ReleaseCount++;
s_ReleaseThisFrame++;
if (s_CurrentInUse > 0)
s_CurrentInUse--;
item.Clear();
if (s_Count >= s_MaxCapacity)
if (s_Count >= s_HardCapacity)
return;
if (s_Count == s_Stack.Length)
Grow();
EnsureStackCapacity(s_Count + 1);
AddToStrictCheckSet(item);
s_Stack[s_Count++] = item;
}
internal static void Tick(int frameCount)
internal static bool Tick(int frameCount)
{
if (frameCount == s_LastTickFrame) return;
if (frameCount == s_LastTickFrame) return true;
s_LastTickFrame = frameCount;
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)
bool active = s_AcquireThisFrame > 0 || s_ReleaseThisFrame > 0 || s_CurrentInUse > 0;
if (active)
{
int target = Math.Max((int)(s_HighWaterMark * 1.5f), MIN_KEEP);
s_HotFrames++;
s_IdleFrames = 0;
}
else
{
s_IdleFrames++;
s_HotFrames = 0;
}
if (s_Count > target)
{
int excess = s_Count - target;
UpdateTargetReserve();
FillReserveBudgeted();
ReleaseExcessBudgeted();
float ratio = s_IdleFrames < IDLE_AGGRESSIVE ? 0.25f : 0.5f;
int removeCount = Math.Max((int)(excess * ratio), 1);
s_AcquireThisFrame = 0;
s_ReleaseThisFrame = 0;
int newCount = s_Count - removeCount;
RemoveRangeFromStrictCheckSet(newCount, removeCount);
Array.Clear(s_Stack, newCount, removeCount);
s_Count = newCount;
return s_IdleFrames < UNSCHEDULE_IDLE_FRAMES || s_Count > s_TargetReserve;
}
TryShrinkArray();
}
private static void UpdateTargetReserve()
{
if (s_IdleFrames >= SHORT_DECAY_START && s_PeakInUseShort > 0)
s_PeakInUseShort -= Math.Max(1, s_PeakInUseShort >> 4);
if (s_IdleFrames >= IDLE_AGGRESSIVE)
{
s_HighWaterMark = Math.Max(s_HighWaterMark >> 1, MIN_KEEP);
s_PeakInUse = s_CurrentInUse;
}
if (s_IdleFrames >= LONG_DECAY_START && s_PeakInUseLong > 0)
s_PeakInUseLong -= Math.Max(1, s_PeakInUseLong >> 6);
int shortReserve = s_PeakInUseShort + (s_PeakInUseShort >> 1);
int longReserve = s_PeakInUseLong + (s_PeakInUseLong >> 2);
int desired = Math.Max(shortReserve, longReserve);
if (desired < MIN_KEEP) desired = MIN_KEEP;
if (desired > s_SoftCapacity) desired = s_SoftCapacity;
s_TargetReserve = desired;
}
private static void FillReserveBudgeted()
{
int available = s_Count + s_CurrentInUse;
if (available >= s_TargetReserve || s_Count >= s_SoftCapacity)
return;
int need = s_TargetReserve - available;
int budget = GetCreateBudget();
int createCount = Math.Min(need, budget);
int room = s_SoftCapacity - s_Count;
if (createCount > room) createCount = room;
for (int i = 0; i < createCount; i++)
{
EnsureStackCapacity(s_Count + 1);
T item = new T();
s_CreateCount++;
s_Stack[s_Count++] = item;
AddToStrictCheckSet(item);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetCreateBudget()
{
if (s_ConsecutiveMiss > 0) return 8;
if (s_HotFrames > 0) return 4;
return 1;
}
private static void ReleaseExcessBudgeted()
{
if (s_IdleFrames < SHORT_DECAY_START || s_Count <= s_TargetReserve)
return;
int excess = s_Count - s_TargetReserve;
int budget = s_IdleFrames < LONG_DECAY_START ? 4 : 16;
int removeCount = Math.Min(excess, budget);
int newCount = s_Count - removeCount;
RemoveRangeFromStrictCheckSet(newCount, removeCount);
Array.Clear(s_Stack, newCount, removeCount);
s_Count = newCount;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Grow()
private static void EnsureStackCapacity(int required)
{
int newLen = s_Stack.Length == 0 ? 8 : s_Stack.Length << 1;
if (newLen > s_MaxCapacity) newLen = s_MaxCapacity;
if (s_Stack.Length >= required)
return;
int newLen = s_Stack.Length == 0 ? 8 : s_Stack.Length;
while (newLen < required)
newLen <<= 1;
if (newLen > s_HardCapacity)
newLen = s_HardCapacity;
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;
}
}
public static void Prewarm(int count)
{
count = Math.Min(count, s_MaxCapacity);
MemoryPoolRegistry.ScheduleTick(s_Handle);
count = Math.Min(count, s_HardCapacity);
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;
}
EnsureStackCapacity(count);
while (s_Count < count)
{
@ -214,6 +256,9 @@ namespace AlicizaX
AddToStrictCheckSet(item);
s_CreateCount++;
}
if (count > s_TargetReserve)
s_TargetReserve = Math.Min(count, s_SoftCapacity);
}
public static void Shrink(int keepCount)
@ -224,23 +269,57 @@ namespace AlicizaX
RemoveRangeFromStrictCheckSet(keepCount, s_Count - keepCount);
Array.Clear(s_Stack, keepCount, s_Count - keepCount);
s_Count = keepCount;
TryShrinkArray();
if (s_TargetReserve > keepCount)
s_TargetReserve = Math.Max(keepCount, MIN_KEEP);
}
public static void Compact()
{
int newLen = s_Count <= 0 ? 0 : Math.Max(NextPowerOfTwo(s_Count), MIN_KEEP);
if (newLen == s_Stack.Length)
return;
if (newLen == 0)
{
s_Stack = Array.Empty<T>();
return;
}
var newStack = new T[newLen];
Array.Copy(s_Stack, 0, newStack, 0, s_Count);
s_Stack = newStack;
}
public static void SetMaxCapacity(int max)
{
s_MaxCapacity = Math.Max(max, MIN_KEEP);
SetCapacity(max, Math.Max(max << 2, MIN_KEEP));
}
public static void SetCapacity(int softCapacity, int hardCapacity)
{
softCapacity = Math.Max(softCapacity, MIN_KEEP);
hardCapacity = Math.Max(hardCapacity, softCapacity);
s_SoftCapacity = softCapacity;
s_HardCapacity = hardCapacity;
if (s_TargetReserve > s_SoftCapacity)
s_TargetReserve = s_SoftCapacity;
}
public static void ClearAll()
{
MemoryPoolRegistry.UnscheduleTick(s_Handle);
ResetStrictCheckSet();
Array.Clear(s_Stack, 0, s_Count);
s_Count = 0;
s_HighWaterMark = s_CurrentInUse;
s_PeakInUse = s_CurrentInUse;
s_CurrentInUse = 0;
s_PeakInUseShort = 0;
s_PeakInUseLong = 0;
s_AcquireThisFrame = 0;
s_ReleaseThisFrame = 0;
s_TargetReserve = MIN_KEEP;
s_IdleFrames = 0;
s_RecentAcquireCount = 0;
s_HotFrames = 0;
s_ConsecutiveMiss = 0;
s_Stack = Array.Empty<T>();
}
@ -257,7 +336,7 @@ namespace AlicizaX
s_CurrentInUse,
s_AcquireCount, s_ReleaseCount,
s_CreateCount,
s_HighWaterMark, s_MaxCapacity,
s_TargetReserve, s_SoftCapacity,
s_IdleFrames, s_Stack.Length);
}
@ -338,5 +417,16 @@ namespace AlicizaX
s_InPoolSet.Clear();
}
private static int NextPowerOfTwo(int value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
}
}

View File

@ -1,18 +1,14 @@
using System;
using System.Runtime.CompilerServices;
namespace AlicizaX
{
/// <summary>
/// 内存池。保留旧 API 签名,内部转发到 MemoryPool&lt;T&gt; / MemoryPoolRegistry。
/// </summary>
public static partial class MemoryPool
{
private static bool _enableStrictCheck;
private static int _strictCheckVersion;
/// <summary>
/// 获取或设置是否开启强制检查。
/// </summary>
public static bool EnableStrictCheck
{
get => _enableStrictCheck;
@ -28,100 +24,100 @@ namespace AlicizaX
internal static int StrictCheckVersion => _strictCheckVersion;
/// <summary>
/// 获取内存池的数量。
/// </summary>
public static int Count => MemoryPoolRegistry.Count;
/// <summary>
/// 获取所有内存池的信息。
/// </summary>
#if UNITY_EDITOR
public static MemoryPoolInfo[] GetAllMemoryPoolInfos()
{
return MemoryPoolRegistry.GetAllInfos();
}
#endif
public static int GetAllMemoryPoolInfos(MemoryPoolInfo[] infos)
{
return MemoryPoolRegistry.GetAllInfos(infos);
}
/// <summary>
/// 清除所有内存池。
/// </summary>
public static void ClearAll()
{
MemoryPoolRegistry.ClearAll();
}
/// <summary>
/// 从内存池获取内存对象。
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Acquire<T>() where T : class, IMemory, new()
{
return MemoryPool<T>.Acquire();
}
/// <summary>
/// 从内存池获取内存对象。
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMemory Acquire(Type memoryType)
{
return MemoryPoolRegistry.Acquire(memoryType);
}
/// <summary>
/// 将内存对象归还内存池。
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Release<T>(T memory) where T : class, IMemory, new()
{
MemoryPool<T>.Release(memory);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Release(IMemory memory)
{
MemoryPoolRegistry.Release(memory);
}
/// <summary>
/// 向内存池中预热指定数量的内存对象。
/// </summary>
public static void Add<T>(int count) where T : class, IMemory, new()
{
MemoryPool<T>.Prewarm(count);
}
/// <summary>
/// 向内存池中预热指定数量的内存对象。
/// </summary>
public static void Add(Type memoryType, int count)
{
MemoryPoolRegistry.Prewarm(memoryType, count);
}
/// <summary>
/// 从内存池中移除指定数量的内存对象。
/// </summary>
public static void Remove<T>(int count) where T : class, IMemory, new()
{
int target = MemoryPool<T>.UnusedCount - count;
MemoryPool<T>.Shrink(target);
}
/// <summary>
/// 从内存池中移除指定数量的内存对象。
/// </summary>
public static void Remove(Type memoryType, int count)
{
MemoryPoolRegistry.RemoveFromType(memoryType, count);
}
/// <summary>
/// 从内存池中移除所有的内存对象。
/// </summary>
public static void RemoveAll<T>() where T : class, IMemory, new()
{
MemoryPool<T>.ClearAll();
}
/// <summary>
/// 从内存池中移除所有的内存对象。
/// </summary>
public static void SetCapacity<T>(int softCapacity, int hardCapacity) where T : class, IMemory, new()
{
MemoryPool<T>.SetCapacity(softCapacity, hardCapacity);
}
public static void Compact<T>() where T : class, IMemory, new()
{
MemoryPool<T>.Compact();
}
public static void Compact(Type memoryType)
{
MemoryPoolRegistry.CompactType(memoryType);
}
public static void CompactAll()
{
MemoryPoolRegistry.CompactAll();
}
public static void RemoveAll(Type memoryType)
{
MemoryPoolRegistry.ClearType(memoryType);

View File

@ -4,7 +4,6 @@ using System.Runtime.CompilerServices;
namespace AlicizaX
{
public static class MemoryPoolRegistry
{
internal sealed class MemoryPoolHandle
@ -13,6 +12,7 @@ namespace AlicizaX
public delegate void ReleaseHandler(IMemory memory);
public delegate void ClearHandler();
public delegate void IntHandler(int value);
public delegate bool TickHandler(int value);
public delegate void GetInfoHandler(ref MemoryPoolInfo info);
public readonly AcquireHandler Acquire;
@ -20,8 +20,10 @@ namespace AlicizaX
public readonly ClearHandler Clear;
public readonly IntHandler Prewarm;
public readonly GetInfoHandler GetInfo;
public readonly IntHandler Tick;
public readonly TickHandler Tick;
public readonly IntHandler Shrink;
public readonly ClearHandler Compact;
public int ActiveIndex = -1;
public MemoryPoolHandle(
AcquireHandler acquire,
@ -29,8 +31,9 @@ namespace AlicizaX
ClearHandler clear,
IntHandler prewarm,
GetInfoHandler getInfo,
IntHandler tick,
IntHandler shrink)
TickHandler tick,
IntHandler shrink,
ClearHandler compact)
{
Acquire = acquire;
Release = release;
@ -39,27 +42,65 @@ namespace AlicizaX
GetInfo = getInfo;
Tick = tick;
Shrink = shrink;
Compact = compact;
}
}
private static readonly Dictionary<Type, MemoryPoolHandle> s_Handles
= new Dictionary<Type, MemoryPoolHandle>(64);
private static MemoryPoolHandle.IntHandler[] s_TickArray = Array.Empty<MemoryPoolHandle.IntHandler>();
private static int s_TickCount;
private static bool s_TickArrayDirty;
private static MemoryPoolHandle[] s_ActivePools = Array.Empty<MemoryPoolHandle>();
private static int s_ActiveCount;
public static int Count => s_Handles.Count;
internal static void Register(Type type, MemoryPoolHandle handle)
{
s_Handles[type] = handle;
s_TickArrayDirty = true;
}
/// <summary>
/// 非泛型 Acquire用于只有 Type 没有泛型参数的场景。
/// </summary>
internal static void ScheduleTick(MemoryPoolHandle handle)
{
if (handle == null || handle.ActiveIndex >= 0)
return;
if (s_ActiveCount == s_ActivePools.Length)
{
int newLength = s_ActivePools.Length == 0 ? 16 : s_ActivePools.Length << 1;
var activePools = new MemoryPoolHandle[newLength];
Array.Copy(s_ActivePools, 0, activePools, 0, s_ActiveCount);
s_ActivePools = activePools;
}
handle.ActiveIndex = s_ActiveCount;
s_ActivePools[s_ActiveCount++] = handle;
}
internal static void UnscheduleTick(MemoryPoolHandle handle)
{
if (handle == null)
return;
int index = handle.ActiveIndex;
if (index < 0 || index >= s_ActiveCount)
{
handle.ActiveIndex = -1;
return;
}
int lastIndex = --s_ActiveCount;
MemoryPoolHandle last = s_ActivePools[lastIndex];
s_ActivePools[lastIndex] = null;
handle.ActiveIndex = -1;
if (index != lastIndex)
{
s_ActivePools[index] = last;
last.ActiveIndex = index;
}
}
public static IMemory Acquire(Type type)
{
if (type == null)
@ -129,6 +170,18 @@ namespace AlicizaX
{
foreach (var kv in s_Handles)
kv.Value.Clear();
for (int i = 0; i < s_ActiveCount; i++)
s_ActivePools[i].ActiveIndex = -1;
Array.Clear(s_ActivePools, 0, s_ActiveCount);
s_ActiveCount = 0;
}
public static void CompactAll()
{
foreach (var kv in s_Handles)
kv.Value.Compact();
}
public static void Prewarm(Type type, int count)
@ -168,6 +221,21 @@ namespace AlicizaX
handle.Clear();
}
public static void CompactType(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (!s_Handles.TryGetValue(type, out var handle))
{
EnsureRegistered(type);
if (!s_Handles.TryGetValue(type, out handle))
throw new Exception($"MemoryPool: Type '{type.FullName}' is not a valid IMemory type.");
}
handle.Compact();
}
public static void RemoveFromType(Type type, int count)
{
if (type == null)
@ -195,27 +263,29 @@ namespace AlicizaX
throw new Exception($"MemoryPool: Type '{type.FullName}' is not a valid IMemory type.");
}
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 MemoryPoolHandle.IntHandler[s_TickCount];
int i = 0;
foreach (var kv in s_Handles)
s_TickArray[i++] = kv.Value.Tick;
while (i < s_ActiveCount)
{
MemoryPoolHandle handle = s_ActivePools[i];
if (handle.Tick(frameCount))
{
i++;
continue;
}
s_TickArrayDirty = false;
int lastIndex = --s_ActiveCount;
MemoryPoolHandle last = s_ActivePools[lastIndex];
s_ActivePools[lastIndex] = null;
handle.ActiveIndex = -1;
if (i != lastIndex)
{
s_ActivePools[i] = last;
last.ActiveIndex = i;
}
}
}
private static void EnsureRegistered(Type type)

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b0a235d2743b4ec68a4c0bfd5dc2eae6
timeCreated: 1777269624

View File

@ -0,0 +1,739 @@
#if UNITY_EDITOR
using System;
using System.Diagnostics;
using Cysharp.Text;
using Unity.Profiling;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace AlicizaX.ObjectPool
{
[DisallowMultipleComponent]
[AddComponentMenu("Game Framework/ObjectPool Benchmark")]
public sealed class ObjectPoolBenchmark : MonoBehaviour
{
[SerializeField] private bool runOnStart = true;
[SerializeField] private int objectCount = 10000;
[SerializeField] private int loopCount = 100000;
[SerializeField] private int sameNameCount = 1024;
[SerializeField] private int occupiedSameNameCount = 1023;
[SerializeField] private int extremeSameNameCount = 4096;
[SerializeField] private bool includeReleaseAllUnused = true;
[SerializeField] private bool logEachCase = true;
[SerializeField] private bool logMemoryDelta = true;
[SerializeField] private int maxCapturedLogChars = 128 * 1024;
private static readonly ProfilerMarker s_TotalMarker = new ProfilerMarker("ObjectPoolBenchmark.Total");
private static readonly ProfilerMarker s_RegisterMarker = new ProfilerMarker("ObjectPoolBenchmark.Register");
private static readonly ProfilerMarker s_SpawnUnspawnMarker = new ProfilerMarker("ObjectPoolBenchmark.SpawnUnspawn");
private static readonly ProfilerMarker s_OccupiedSameNameMarker = new ProfilerMarker("ObjectPoolBenchmark.OccupiedSameName");
private static readonly ProfilerMarker s_ExtremeSameNameMarker = new ProfilerMarker("ObjectPoolBenchmark.ExtremeSameName");
private static readonly ProfilerMarker s_MultiSpawnMarker = new ProfilerMarker("ObjectPoolBenchmark.MultiSpawn");
private static readonly ProfilerMarker s_CapacityMarker = new ProfilerMarker("ObjectPoolBenchmark.Capacity");
private static readonly ProfilerMarker s_ExpireMarker = new ProfilerMarker("ObjectPoolBenchmark.Expire");
private static readonly ProfilerMarker s_ReleaseMarker = new ProfilerMarker("ObjectPoolBenchmark.ReleaseAllUnused");
private static readonly ProfilerMarker s_MixedNameMarker = new ProfilerMarker("ObjectPoolBenchmark.MixedName");
private static readonly ProfilerMarker s_DestroyMarker = new ProfilerMarker("ObjectPoolBenchmark.DestroyRecreate");
private static readonly ProfilerMarker s_CursorRecoveryMarker = new ProfilerMarker("ObjectPoolBenchmark.CursorRecovery");
private readonly Stopwatch m_Stopwatch = new Stopwatch();
private Utf16ValueStringBuilder m_LogBuilder;
private IObjectPoolService m_Service;
private int m_FailCount;
private int m_CaseCount;
private bool m_LogBuilderCreated;
private long m_CaseAllocBefore;
private long m_CaseAllocAfter;
private void OnEnable()
{
ClearCapturedConsoleOutput();
Application.logMessageReceived += OnLogMessageReceived;
}
private void OnDisable()
{
Application.logMessageReceived -= OnLogMessageReceived;
m_LogBuilder.Dispose();
m_LogBuilderCreated = false;
}
private void Start()
{
if (runOnStart)
RunAll();
}
[ContextMenu("Run ObjectPool Benchmark")]
public void RunAll()
{
ClearCapturedConsoleOutput();
EnsureService();
if (m_Service == null)
return;
m_FailCount = 0;
m_CaseCount = 0;
using (s_TotalMarker.Auto())
{
RunCase("SingleName Spawn/Unspawn", RunSingleNameSpawnUnspawn);
RunCase("NullName Normalization", RunNullNameNormalization);
RunCase("SameName Stress", RunSameNameStress);
RunCase("SameName Occupied Scan", RunSameNameOccupiedScan);
RunCase("SameName Extreme OneFree", RunSameNameExtremeOneFree);
RunCase("MultiSpawn", RunMultiSpawn);
RunCase("MixedName RoundRobin", RunMixedNameRoundRobin);
RunCase("Capacity Guard", RunCapacityGuard);
RunCase("Spawned Release Guard", RunSpawnedReleaseGuard);
RunCase("Destroy Recreate", RunDestroyRecreate);
RunCase("Cursor Release Recovery", RunCursorReleaseRecovery);
RunCase("Locked Release Guard", RunLockedReleaseGuard);
RunCase("Custom Release Guard", RunCustomReleaseGuard);
RunCase("Expire Release", RunExpireRelease);
if (includeReleaseAllUnused)
RunCase("ReleaseAllUnused", RunReleaseAllUnused);
}
Debug.Log(BuildLog("ObjectPool benchmark finished. cases=", m_CaseCount, ", fails=", m_FailCount));
}
[ContextMenu("Copy Captured Console Output")]
public void CopyCapturedConsoleOutput()
{
EnsureLogBuilder();
string text = m_LogBuilder.ToString();
GUIUtility.systemCopyBuffer = text;
Debug.Log(BuildLog("ObjectPoolBenchmark copied console output chars=", text.Length, ", max=", maxCapturedLogChars));
}
[ContextMenu("Clear Captured Console Output")]
public void ClearCapturedConsoleOutput()
{
m_LogBuilder.Dispose();
m_LogBuilder = ZString.CreateStringBuilder();
m_LogBuilderCreated = true;
}
private void EnsureLogBuilder()
{
if (!m_LogBuilderCreated)
{
m_LogBuilder = ZString.CreateStringBuilder();
m_LogBuilderCreated = true;
}
}
private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
EnsureLogBuilder();
if (m_LogBuilder.Length >= maxCapturedLogChars)
return;
m_LogBuilder.Append('[');
m_LogBuilder.Append(type);
m_LogBuilder.Append("] ");
m_LogBuilder.Append(condition);
m_LogBuilder.AppendLine();
if (type == LogType.Exception || type == LogType.Error || type == LogType.Assert)
{
m_LogBuilder.Append(stackTrace);
m_LogBuilder.AppendLine();
}
}
private void EnsureService()
{
if (m_Service != null)
return;
if (AppServices.TryGet(out m_Service))
return;
if (!AppServices.HasWorld && GetComponent<AppServiceRoot>() == null)
gameObject.AddComponent<AppServiceRoot>();
if (GetComponent<ObjectPoolComponent>() == null)
gameObject.AddComponent<ObjectPoolComponent>();
if (!AppServices.TryGet(out m_Service))
Debug.LogError("ObjectPoolBenchmark requires ObjectPoolComponent registration.");
}
private void RunCase(string caseName, Action action)
{
m_CaseCount++;
m_CaseAllocBefore = GetAllocatedBytesForCurrentThread();
m_CaseAllocAfter = m_CaseAllocBefore;
m_Stopwatch.Restart();
action();
if (m_Stopwatch.IsRunning)
{
m_Stopwatch.Stop();
m_CaseAllocAfter = GetAllocatedBytesForCurrentThread();
}
if (logEachCase)
{
if (logMemoryDelta)
Debug.Log(BuildLog("[ObjectPoolBenchmark] ", caseName, " ms=", m_Stopwatch.Elapsed.TotalMilliseconds, " gcAlloc=", m_CaseAllocAfter - m_CaseAllocBefore));
else
Debug.Log(BuildLog("[ObjectPoolBenchmark] ", caseName, " ms=", m_Stopwatch.Elapsed.TotalMilliseconds));
}
}
private void RestartCaseMeasure()
{
m_CaseAllocBefore = GetAllocatedBytesForCurrentThread();
m_CaseAllocAfter = m_CaseAllocBefore;
m_Stopwatch.Restart();
}
private void StopCaseMeasure()
{
m_Stopwatch.Stop();
m_CaseAllocAfter = GetAllocatedBytesForCurrentThread();
}
private long GetAllocatedBytesForCurrentThread()
{
return logMemoryDelta ? GC.GetAllocatedBytesForCurrentThread() : 0L;
}
private void RunSingleNameSpawnUnspawn()
{
using (s_SpawnUnspawnMarker.Auto())
{
string poolName = MakePoolName("single");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, objectCount, float.MaxValue);
BenchmarkTarget target = new BenchmarkTarget(1);
BenchmarkObject obj = BenchmarkObject.Create(string.Empty, target, false, true);
pool.Register(obj, false);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkObject spawned = pool.Spawn();
AssertReference(spawned, obj, "single spawn returned wrong object");
pool.Unspawn(target);
}
StopCaseMeasure();
DestroyPool(poolName);
}
}
private void RunNullNameNormalization()
{
string poolName = MakePoolName("null-name");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 4, float.MaxValue);
BenchmarkTarget target = new BenchmarkTarget(2);
BenchmarkObject obj = BenchmarkObject.Create(null, target, false, true);
pool.Register(obj, false);
RestartCaseMeasure();
BenchmarkObject spawned = pool.Spawn(null);
AssertReference(spawned, obj, "Spawn(null) failed for empty-name object");
pool.Unspawn(target);
StopCaseMeasure();
DestroyPool(poolName);
}
private void RunSameNameStress()
{
using (s_RegisterMarker.Auto())
{
string poolName = MakePoolName("same-name");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, sameNameCount, float.MaxValue);
BenchmarkTarget[] targets = new BenchmarkTarget[sameNameCount];
BenchmarkObject[] objects = new BenchmarkObject[sameNameCount];
for (int i = 0; i < sameNameCount; i++)
{
targets[i] = new BenchmarkTarget(i);
objects[i] = BenchmarkObject.Create("same", targets[i], false, true);
pool.Register(objects[i], false);
}
int cycles = Math.Max(1, loopCount / Math.Max(1, sameNameCount));
RestartCaseMeasure();
for (int c = 0; c < cycles; c++)
{
for (int i = 0; i < sameNameCount; i++)
{
BenchmarkObject spawned = pool.Spawn("same");
AssertNotNull(spawned, "same-name spawn returned null");
pool.Unspawn(spawned.Target);
}
}
StopCaseMeasure();
DestroyPool(poolName);
}
}
private void RunSameNameOccupiedScan()
{
using (s_OccupiedSameNameMarker.Auto())
{
int totalCount = Math.Max(2, sameNameCount);
int blockedCount = Math.Max(1, Math.Min(occupiedSameNameCount, totalCount - 1));
int cycles = Math.Max(1, loopCount / Math.Max(1, blockedCount));
string poolName = MakePoolName("same-name-occupied");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, totalCount, float.MaxValue);
BenchmarkTarget[] blockedTargets = new BenchmarkTarget[blockedCount];
for (int i = 0; i < totalCount; i++)
pool.Register(BenchmarkObject.Create("occupied", new BenchmarkTarget(i), false, true), false);
for (int i = 0; i < blockedCount; i++)
{
BenchmarkObject spawned = pool.Spawn("occupied");
AssertNotNull(spawned, "occupied setup spawn returned null");
if (spawned != null)
blockedTargets[i] = spawned.Target;
}
RestartCaseMeasure();
for (int i = 0; i < cycles; i++)
{
BenchmarkObject spawned = pool.Spawn("occupied");
AssertNotNull(spawned, "occupied scan spawn returned null");
if (spawned != null)
pool.Unspawn(spawned.Target);
}
StopCaseMeasure();
for (int i = 0; i < blockedCount; i++)
{
if (blockedTargets[i] != null)
pool.Unspawn(blockedTargets[i]);
}
DestroyPool(poolName);
}
}
private void RunSameNameExtremeOneFree()
{
using (s_ExtremeSameNameMarker.Auto())
{
int totalCount = Math.Max(2, extremeSameNameCount);
int blockedCount = totalCount - 1;
string poolName = MakePoolName("same-name-extreme");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, totalCount, float.MaxValue);
BenchmarkTarget[] blockedTargets = new BenchmarkTarget[blockedCount];
for (int i = 0; i < totalCount; i++)
pool.Register(BenchmarkObject.Create("extreme", new BenchmarkTarget(i), false, true), false);
for (int i = 0; i < blockedCount; i++)
{
BenchmarkObject spawned = pool.Spawn("extreme");
AssertNotNull(spawned, "extreme setup spawn returned null");
if (spawned != null)
blockedTargets[i] = spawned.Target;
}
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkObject spawned = pool.Spawn("extreme");
AssertNotNull(spawned, "extreme one-free spawn returned null");
if (spawned != null)
pool.Unspawn(spawned.Target);
}
StopCaseMeasure();
for (int i = 0; i < blockedCount; i++)
{
if (blockedTargets[i] != null)
pool.Unspawn(blockedTargets[i]);
}
DestroyPool(poolName);
}
}
private void RunMultiSpawn()
{
using (s_MultiSpawnMarker.Auto())
{
string poolName = MakePoolName("multi");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, true, 4, float.MaxValue);
BenchmarkTarget target = new BenchmarkTarget(3);
BenchmarkObject obj = BenchmarkObject.Create("multi", target, false, true);
pool.Register(obj, false);
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
BenchmarkObject spawned = pool.Spawn("multi");
AssertReference(spawned, obj, "multi spawn returned wrong object");
}
for (int i = 0; i < loopCount; i++)
pool.Unspawn(target);
StopCaseMeasure();
DestroyPool(poolName);
}
}
private void RunMixedNameRoundRobin()
{
using (s_MixedNameMarker.Auto())
{
const int nameCount = 16;
int perNameCount = Math.Max(1, sameNameCount / nameCount);
string poolName = MakePoolName("mixed-name");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, sameNameCount, float.MaxValue);
string[] names = new string[nameCount];
for (int n = 0; n < nameCount; n++)
{
names[n] = MakeIndexedName("mixed", n);
for (int i = 0; i < perNameCount; i++)
pool.Register(BenchmarkObject.Create(names[n], new BenchmarkTarget(n * perNameCount + i), false, true), false);
}
RestartCaseMeasure();
for (int i = 0; i < loopCount; i++)
{
string objectName = names[i & (nameCount - 1)];
BenchmarkObject spawned = pool.Spawn(objectName);
AssertNotNull(spawned, "mixed-name spawn returned null");
if (spawned != null)
pool.Unspawn(spawned.Target);
}
StopCaseMeasure();
DestroyPool(poolName);
}
}
private void RunCapacityGuard()
{
using (s_CapacityMarker.Auto())
{
string poolName = MakePoolName("capacity");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 1, float.MaxValue);
BenchmarkTarget first = new BenchmarkTarget(4);
BenchmarkTarget second = new BenchmarkTarget(5);
BenchmarkObject firstObject = BenchmarkObject.Create("cap", first, false, true);
BenchmarkObject secondObject = BenchmarkObject.Create("cap", second, false, true);
RestartCaseMeasure();
pool.Register(firstObject, false);
pool.Register(secondObject, false);
AssertEqual(pool.Count, 1, "capacity guard allowed over-register");
BenchmarkObject spawned = pool.Spawn("cap");
AssertNotNull(spawned, "capacity replacement object cannot spawn");
if (spawned != null)
AssertReference(spawned.Target, second, "capacity replacement kept wrong object");
pool.Unspawn(second);
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 0, "capacity release did not clear unused object");
StopCaseMeasure();
DestroyPool(poolName);
}
}
private void RunSpawnedReleaseGuard()
{
string poolName = MakePoolName("spawned-release");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 4, float.MaxValue);
BenchmarkTarget target = new BenchmarkTarget(8);
pool.Register(BenchmarkObject.Create("spawned", target, false, true), true);
RestartCaseMeasure();
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 1, "spawned object was released while in use");
pool.Unspawn(target);
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 0, "spawned object was not released after unspawn");
StopCaseMeasure();
DestroyPool(poolName);
}
private void RunDestroyRecreate()
{
using (s_DestroyMarker.Auto())
{
string poolName = MakePoolName("destroy-recreate");
RestartCaseMeasure();
for (int i = 0; i < 64; i++)
{
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 8, float.MaxValue);
pool.Register(BenchmarkObject.Create("destroy", new BenchmarkTarget(i), false, true), false);
AssertEqual(pool.Count, 1, "destroy-recreate register failed");
DestroyPool(poolName);
}
StopCaseMeasure();
}
}
private void RunCursorReleaseRecovery()
{
using (s_CursorRecoveryMarker.Auto())
{
string poolName = MakePoolName("cursor-recovery");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 4, float.MaxValue);
BenchmarkTarget[] blockedTargets = new BenchmarkTarget[3];
for (int i = 0; i < 4; i++)
pool.Register(BenchmarkObject.Create("cursor", new BenchmarkTarget(i), false, true), false);
for (int i = 0; i < blockedTargets.Length; i++)
{
BenchmarkObject spawned = pool.Spawn("cursor");
AssertNotNull(spawned, "cursor setup spawn returned null");
if (spawned != null)
blockedTargets[i] = spawned.Target;
}
BenchmarkObject cursorObject = pool.Spawn("cursor");
AssertNotNull(cursorObject, "cursor target spawn returned null");
if (cursorObject != null)
pool.Unspawn(cursorObject.Target);
RestartCaseMeasure();
pool.ReleaseAllUnused();
if (blockedTargets[0] != null)
pool.Unspawn(blockedTargets[0]);
BenchmarkObject recovered = pool.Spawn("cursor");
AssertNotNull(recovered, "cursor did not recover after release");
if (recovered != null)
pool.Unspawn(recovered.Target);
StopCaseMeasure();
for (int i = 1; i < blockedTargets.Length; i++)
pool.Unspawn(blockedTargets[i]);
DestroyPool(poolName);
}
}
private void RunLockedReleaseGuard()
{
string poolName = MakePoolName("locked");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 4, float.MaxValue);
BenchmarkTarget target = new BenchmarkTarget(6);
BenchmarkObject obj = BenchmarkObject.Create("locked", target, true, true);
pool.Register(obj, false);
RestartCaseMeasure();
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 1, "locked object was released");
StopCaseMeasure();
DestroyPool(poolName);
}
private void RunCustomReleaseGuard()
{
string poolName = MakePoolName("custom-release");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, 4, float.MaxValue);
BenchmarkTarget target = new BenchmarkTarget(7);
BenchmarkObject obj = BenchmarkObject.Create("custom", target, false, false);
pool.Register(obj, false);
RestartCaseMeasure();
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 1, "custom release guard object was released");
StopCaseMeasure();
DestroyPool(poolName);
}
private void RunExpireRelease()
{
using (s_ExpireMarker.Auto())
{
string poolName = MakePoolName("expire");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, objectCount, 0f);
int count = Math.Max(1, Math.Min(objectCount, 4096));
for (int i = 0; i < count; i++)
pool.Register(BenchmarkObject.Create("expire", new BenchmarkTarget(i), false, true), false);
RestartCaseMeasure();
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 0, "expire release did not clear all unused objects");
StopCaseMeasure();
DestroyPool(poolName);
}
}
private void RunReleaseAllUnused()
{
using (s_ReleaseMarker.Auto())
{
string poolName = MakePoolName("release-all");
IObjectPool<BenchmarkObject> pool = CreatePool(poolName, false, objectCount, float.MaxValue);
for (int i = 0; i < objectCount; i++)
pool.Register(BenchmarkObject.Create("release", new BenchmarkTarget(i), false, true), false);
RestartCaseMeasure();
pool.ReleaseAllUnused();
AssertEqual(pool.Count, 0, "ReleaseAllUnused did not clear pool");
StopCaseMeasure();
DestroyPool(poolName);
}
}
private IObjectPool<BenchmarkObject> CreatePool(string poolName, bool multiSpawn, int capacity, float expireTime)
{
if (m_Service.HasObjectPool<BenchmarkObject>(poolName))
m_Service.DestroyObjectPool<BenchmarkObject>(poolName);
return m_Service.CreatePool<BenchmarkObject>(
new ObjectPoolCreateOptions(poolName, multiSpawn, float.MaxValue, capacity, expireTime, 0));
}
private void DestroyPool(string poolName)
{
if (m_Service.HasObjectPool<BenchmarkObject>(poolName))
m_Service.DestroyObjectPool<BenchmarkObject>(poolName);
}
private static string MakePoolName(string suffix)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append("ObjectPoolBenchmark.");
builder.Append(suffix);
return builder.ToString();
}
}
private static string MakeIndexedName(string prefix, int index)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(prefix);
builder.Append('.');
builder.Append(index);
return builder.ToString();
}
}
private void AssertNotNull(object value, string message)
{
if (value != null)
return;
m_FailCount++;
Debug.LogError(message);
}
private void AssertReference(object actual, object expected, string message)
{
if (ReferenceEquals(actual, expected))
return;
m_FailCount++;
Debug.LogError(message);
}
private void AssertEqual(int actual, int expected, string message)
{
if (actual == expected)
return;
m_FailCount++;
Debug.LogError(BuildLog(message, " actual=", actual, ", expected=", expected));
}
private static string BuildLog(object a, string b, object c, string d,object e)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(a);
builder.Append(b);
builder.Append(c);
builder.Append(d);
builder.Append(e);
return builder.ToString();
}
}
private static string BuildLog(string a, object b, string c, object d)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(a);
builder.Append(b);
builder.Append(c);
builder.Append(d);
return builder.ToString();
}
}
private static string BuildLog(string a, object b, string c, object d, string e, object f)
{
using (var builder = ZString.CreateStringBuilder())
{
builder.Append(a);
builder.Append(b);
builder.Append(c);
builder.Append(d);
builder.Append(e);
builder.Append(f);
return builder.ToString();
}
}
private sealed class BenchmarkTarget
{
public readonly int Id;
public BenchmarkTarget(int id)
{
Id = id;
}
}
private sealed class BenchmarkObject : ObjectBase<BenchmarkTarget>
{
private bool m_CustomCanReleaseFlag;
public override bool CustomCanReleaseFlag => m_CustomCanReleaseFlag;
public BenchmarkObject()
{
}
public static BenchmarkObject Create(string name, BenchmarkTarget target, bool locked, bool customCanReleaseFlag)
{
BenchmarkObject obj = MemoryPool.Acquire<BenchmarkObject>();
obj.Initialize(name, target, locked);
obj.m_CustomCanReleaseFlag = customCanReleaseFlag;
return obj;
}
protected internal override void Release(bool isShutdown)
{
}
public override void Clear()
{
base.Clear();
m_CustomCanReleaseFlag = true;
}
}
}
}
#endif

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b99f3fc658c4a3d4f80218ab7113341e
guid: 472607d053c8822499f9960bb6ef21bf
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

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

View File

@ -54,4 +54,24 @@ namespace AlicizaX.ObjectPool
m_LastUseTime = 0f;
}
}
public abstract class ObjectBase<TTarget> : ObjectBase where TTarget : class
{
public new TTarget Target => (TTarget)base.Target;
protected void Initialize(TTarget target)
{
base.Initialize(string.Empty, target, false);
}
protected void Initialize(string name, TTarget target)
{
base.Initialize(name, target, false);
}
protected void Initialize(string name, TTarget target, bool locked)
{
base.Initialize(name, target, locked);
}
}
}

View File

@ -2,10 +2,11 @@ using System;
namespace AlicizaX.ObjectPool
{
public abstract class ObjectPoolBase : IObjectPoolDebugView
public abstract class ObjectPoolBase
{
private readonly string m_Name;
private string m_FullName;
internal bool IsActive;
public ObjectPoolBase() : this(null) { }

View File

@ -32,8 +32,8 @@ namespace AlicizaX
internal int GetAllObjectPools(bool sort, ObjectPoolBase[] results)
{
if (_mObjectPoolService is IObjectPoolServiceDebugView debugView)
return debugView.GetAllObjectPools(sort, results);
if (_mObjectPoolService is ObjectPoolService svc)
return svc.GetAllObjectPools(sort, results);
return 0;
}

View File

@ -17,8 +17,6 @@ namespace AlicizaX.ObjectPool
public float LastUseTime;
public int PrevByName;
public int NextByName;
public int PrevAvailableByName;
public int NextAvailableByName;
public int PrevUnused;
public int NextUnused;
public byte Flags;
@ -41,8 +39,7 @@ namespace AlicizaX.ObjectPool
private ReferenceOpenHashMap m_TargetMap;
private StringOpenHashMap m_AllNameHeadMap;
private StringOpenHashMap m_AvailableNameHeadMap;
private StringOpenHashMap m_AvailableNameTailMap;
private StringOpenHashMap m_NameCursorMap;
private readonly bool m_AllowMultiSpawn;
private float m_AutoReleaseInterval;
@ -72,8 +69,7 @@ namespace AlicizaX.ObjectPool
m_FreeStack = SlotArrayPool<int>.Rent(initCap);
m_TargetMap = new ReferenceOpenHashMap(initCap);
m_AllNameHeadMap = new StringOpenHashMap(initCap);
m_AvailableNameHeadMap = new StringOpenHashMap(initCap);
m_AvailableNameTailMap = new StringOpenHashMap(initCap);
m_NameCursorMap = new StringOpenHashMap(initCap);
m_AllowMultiSpawn = allowMultiSpawn;
m_AutoReleaseInterval = autoReleaseInterval;
m_Capacity = capacity;
@ -167,15 +163,24 @@ namespace AlicizaX.ObjectPool
return;
}
if (!EnsureRegisterCapacity())
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' capacity is full.");
#endif
return;
}
int idx = AllocSlot();
if (idx < 0)
return;
ref var slot = ref m_Slots[idx];
slot.Obj = obj;
slot.SpawnCount = spawned ? 1 : 0;
slot.LastUseTime = Time.realtimeSinceStartup;
slot.PrevByName = -1;
slot.NextByName = -1;
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
slot.PrevUnused = -1;
slot.NextUnused = -1;
slot.SetAlive(true);
@ -188,6 +193,10 @@ namespace AlicizaX.ObjectPool
m_Slots[existingHead].PrevByName = idx;
slot.NextByName = existingHead;
}
else
{
m_NameCursorMap.AddOrUpdate(objectName, idx);
}
m_AllNameHeadMap.AddOrUpdate(objectName, idx);
obj.LastUseTime = slot.LastUseTime;
@ -196,7 +205,7 @@ namespace AlicizaX.ObjectPool
else
MarkSlotAvailable(idx);
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
UpdateActiveState();
ValidateState();
}
@ -205,16 +214,18 @@ namespace AlicizaX.ObjectPool
public T Spawn(string name)
{
if (name == null) return null;
if (name == null) name = string.Empty;
if (m_AllowMultiSpawn)
return SpawnAny(name);
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return null;
int head = FindAvailableByName(name);
if (head < 0) return null;
ref var slot = ref m_Slots[head];
if (!slot.IsAlive() || slot.SpawnCount != 0 || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name head is inconsistent.");
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain is inconsistent.");
#endif
return null;
}
@ -230,11 +241,64 @@ namespace AlicizaX.ObjectPool
public bool CanSpawn(string name)
{
if (name == null) return false;
if (name == null) name = string.Empty;
if (m_AllowMultiSpawn)
return m_AllNameHeadMap.ContainsKey(name);
return m_AvailableNameHeadMap.ContainsKey(name);
return FindAvailableByName(name) >= 0;
}
private int FindAvailableByName(string name)
{
if (!m_AllNameHeadMap.TryGetValue(name, out int head))
return -1;
ref var headSlot = ref m_Slots[head];
if (headSlot.IsAlive()
&& headSlot.SpawnCount == 0
&& string.Equals(headSlot.Obj.Name, name, StringComparison.Ordinal))
{
return head;
}
int current = GetValidNameCursor(name, head);
if (current == head)
current = headSlot.NextByName >= 0 ? headSlot.NextByName : head;
int first = current;
do
{
ref var slot = ref m_Slots[current];
if (slot.IsAlive()
&& slot.SpawnCount == 0
&& string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
{
int nextCursor = slot.NextByName >= 0 ? slot.NextByName : head;
m_NameCursorMap.AddOrUpdate(name, nextCursor);
return current;
}
current = slot.NextByName >= 0 ? slot.NextByName : head;
}
while (current != first);
return -1;
}
private int GetValidNameCursor(string name, int head)
{
if (m_NameCursorMap.TryGetValue(name, out int cursor)
&& cursor >= 0
&& cursor < m_SlotCount)
{
ref var slot = ref m_Slots[cursor];
if (slot.IsAlive() && string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
return cursor;
}
m_NameCursorMap.AddOrUpdate(name, head);
return head;
}
public void Unspawn(T obj)
@ -279,7 +343,7 @@ namespace AlicizaX.ObjectPool
ref var slot = ref m_Slots[current];
if (CanReleaseSlot(ref slot))
{
ReleaseSlot(current);
ReleaseSlot(current, false);
released++;
}
current = next;
@ -288,7 +352,9 @@ namespace AlicizaX.ObjectPool
if (released > 0)
{
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
TrimSlotCountTail();
ShrinkStorageIfEmpty();
UpdateActiveState();
ValidateState();
}
}
@ -306,6 +372,7 @@ namespace AlicizaX.ObjectPool
if (m_PendingReleaseCount <= 0 && !checkExpire)
{
TryProgressiveShrink();
UpdateActiveState();
return;
}
@ -323,6 +390,7 @@ namespace AlicizaX.ObjectPool
}
TryProgressiveShrink();
UpdateActiveState();
}
private void TryProgressiveShrink()
@ -333,14 +401,17 @@ namespace AlicizaX.ObjectPool
m_ShrinkCounter = 0;
TrimSlotCountTail();
int slotArrayLen = m_Slots.Length;
if (m_TargetMap.Count == 0 || slotArrayLen <= InitSlotCapacity)
int aliveCount = m_TargetMap.Count;
if (aliveCount == 0 || slotArrayLen <= InitSlotCapacity)
return;
float usageRatio = (float)m_TargetMap.Count / slotArrayLen;
float usageRatio = (float)aliveCount / slotArrayLen;
if (usageRatio < 0.25f)
{
int targetCapacity = Math.Max(slotArrayLen / 2, InitSlotCapacity);
int targetCapacity = Math.Max(NextPowerOf2(Math.Max(m_SlotCount, aliveCount)), InitSlotCapacity);
if (targetCapacity < slotArrayLen && m_SlotCount <= targetCapacity)
{
var newSlots = SlotArrayPool<ObjectSlot>.Rent(targetCapacity);
@ -380,8 +451,7 @@ namespace AlicizaX.ObjectPool
m_TargetMap.Clear();
m_AllNameHeadMap.Clear();
m_AvailableNameHeadMap.Clear();
m_AvailableNameTailMap.Clear();
m_NameCursorMap.Clear();
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true);
@ -482,14 +552,23 @@ namespace AlicizaX.ObjectPool
return m_FreeStack[--m_FreeTop];
if (m_SlotCount >= m_Slots.Length)
{
GrowSlots();
if (m_SlotCount >= m_Slots.Length)
return -1;
}
return m_SlotCount++;
}
private void GrowSlots()
{
int newCap = Math.Max(m_Slots.Length * 2, InitSlotCapacity);
int currentCap = m_Slots.Length;
int maxCap = m_Capacity == int.MaxValue ? int.MaxValue : Math.Max(m_Capacity, InitSlotCapacity);
int newCap = Math.Min(Math.Max(currentCap * 2, InitSlotCapacity), maxCap);
if (newCap <= currentCap)
return;
var newSlots = SlotArrayPool<ObjectSlot>.Rent(newCap);
var newFreeStack = SlotArrayPool<int>.Rent(newCap);
@ -503,14 +582,14 @@ namespace AlicizaX.ObjectPool
m_FreeStack = newFreeStack;
}
private void ReleaseSlot(int idx)
private void ReleaseSlot(int idx, bool compactStorage = true)
{
ref var slot = ref m_Slots[idx];
if (!slot.IsAlive()) return;
if (slot.SpawnCount > 0) return;
T obj = slot.Obj;
if (slot.SpawnCount == 0)
MarkSlotUnavailable(idx);
MarkSlotUnavailable(idx);
RemoveFromAllNameChain(idx);
m_TargetMap.Remove(obj.Target);
@ -523,8 +602,6 @@ namespace AlicizaX.ObjectPool
slot.SpawnCount = 0;
slot.PrevByName = -1;
slot.NextByName = -1;
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
slot.PrevUnused = -1;
slot.NextUnused = -1;
@ -538,7 +615,53 @@ namespace AlicizaX.ObjectPool
}
m_FreeStack[m_FreeTop++] = idx;
ShrinkStorageIfEmpty();
if (compactStorage)
{
TrimSlotCountTail();
ShrinkStorageIfEmpty();
}
}
private bool EnsureRegisterCapacity()
{
if (m_Capacity == int.MaxValue || Count < m_Capacity)
return true;
int released = ReleaseUnused(1, false, float.MinValue);
if (released > 0)
{
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
return Count < m_Capacity;
}
return false;
}
private void TrimSlotCountTail()
{
while (m_SlotCount > 0 && !m_Slots[m_SlotCount - 1].IsAlive())
m_SlotCount--;
int write = 0;
for (int i = 0; i < m_FreeTop; i++)
{
int freeIndex = m_FreeStack[i];
if (freeIndex < m_SlotCount)
m_FreeStack[write++] = freeIndex;
}
m_FreeTop = write;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int NextPowerOf2(int value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
private void RemoveFromAllNameChain(int idx)
@ -570,6 +693,13 @@ namespace AlicizaX.ObjectPool
m_AllNameHeadMap.Remove(objectName);
}
if (m_NameCursorMap.TryGetValue(objectName, out int cursor) && cursor == idx)
{
if (next >= 0) m_NameCursorMap.AddOrUpdate(objectName, next);
else if (prev >= 0) m_NameCursorMap.AddOrUpdate(objectName, prev);
else m_NameCursorMap.Remove(objectName);
}
if (next >= 0)
m_Slots[next].PrevByName = prev;
@ -589,12 +719,13 @@ namespace AlicizaX.ObjectPool
if (requireExpired && slot.LastUseTime > expireThreshold)
{
break;
current = next;
continue;
}
if (CanReleaseSlot(ref slot))
{
ReleaseSlot(current);
ReleaseSlot(current, false);
released++;
}
@ -606,6 +737,12 @@ namespace AlicizaX.ObjectPool
m_LastBudgetScanStart = current >= 0 ? current : m_UnusedHead;
}
if (released > 0)
{
TrimSlotCountTail();
ShrinkStorageIfEmpty();
}
return released;
}
@ -649,8 +786,7 @@ namespace AlicizaX.ObjectPool
m_Slots = SlotArrayPool<ObjectSlot>.Rent(InitSlotCapacity);
m_FreeStack = SlotArrayPool<int>.Rent(InitSlotCapacity);
m_AllNameHeadMap.Clear();
m_AvailableNameHeadMap.Clear();
m_AvailableNameTailMap.Clear();
m_NameCursorMap.Clear();
m_SlotCount = 0;
m_FreeTop = 0;
m_UnusedHead = -1;
@ -658,12 +794,10 @@ namespace AlicizaX.ObjectPool
m_LastBudgetScanStart = -1;
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
[Conditional("UNITY_EDITOR")]
private void ValidateState()
{
#if !ENABLE_OBJECTPOOL_VALIDATION
return;
#else
#if UNITY_EDITOR && ENABLE_OBJECTPOOL_VALIDATION
int aliveCount = 0;
int unusedCount = 0;
for (int i = 0; i < m_SlotCount; i++)
@ -699,7 +833,6 @@ namespace AlicizaX.ObjectPool
}
bool inUnusedList = m_UnusedHead == i || slot.PrevUnused >= 0 || slot.NextUnused >= 0;
bool inAvailableList = false;
if (slot.SpawnCount == 0)
{
@ -708,24 +841,6 @@ namespace AlicizaX.ObjectPool
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused list is inconsistent.");
}
if (!m_AvailableNameHeadMap.TryGetValue(objectName, out int availableHead))
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name head is missing.");
}
else
{
inAvailableList = availableHead == i || slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0;
if (!inAvailableList)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name chain is inconsistent.");
}
if (slot.NextAvailableByName >= 0 && m_Slots[slot.NextAvailableByName].PrevAvailableByName != i)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name link is inconsistent.");
}
}
}
else
{
@ -733,11 +848,6 @@ namespace AlicizaX.ObjectPool
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' spawned object exists in unused list.");
}
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' spawned object exists in available chain.");
}
}
}
@ -776,13 +886,14 @@ namespace AlicizaX.ObjectPool
private void MarkSlotAvailable(int idx)
{
AddToUnusedListTail(idx);
AddToAvailableNameChain(idx);
ref var slot = ref m_Slots[idx];
if (slot.IsAlive())
m_NameCursorMap.AddOrUpdate(slot.Obj.Name ?? string.Empty, idx);
}
private void MarkSlotUnavailable(int idx)
{
RemoveFromUnusedList(idx);
RemoveFromAvailableNameChain(idx);
}
private void AddToUnusedListTail(int idx)
@ -828,66 +939,6 @@ namespace AlicizaX.ObjectPool
m_LastBudgetScanStart = next >= 0 ? next : m_UnusedHead;
}
private void AddToAvailableNameChain(int idx)
{
ref var slot = ref m_Slots[idx];
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name chain is inconsistent.");
#endif
return;
}
string objectName = slot.Obj.Name ?? string.Empty;
if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail))
{
m_Slots[tail].NextAvailableByName = idx;
slot.PrevAvailableByName = tail;
slot.NextAvailableByName = -1;
m_AvailableNameTailMap.AddOrUpdate(objectName, idx);
}
else
{
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
m_AvailableNameHeadMap.AddOrUpdate(objectName, idx);
m_AvailableNameTailMap.AddOrUpdate(objectName, idx);
}
}
private void RemoveFromAvailableNameChain(int idx)
{
ref var slot = ref m_Slots[idx];
string objectName = slot.Obj.Name ?? string.Empty;
if (slot.PrevAvailableByName < 0
&& slot.NextAvailableByName < 0
&& (!m_AvailableNameHeadMap.TryGetValue(objectName, out int head) || head != idx))
{
return;
}
int prev = slot.PrevAvailableByName;
int next = slot.NextAvailableByName;
if (prev >= 0)
m_Slots[prev].NextAvailableByName = next;
else if (next >= 0)
m_AvailableNameHeadMap.AddOrUpdate(objectName, next);
else
m_AvailableNameHeadMap.Remove(objectName);
if (next >= 0)
m_Slots[next].PrevAvailableByName = prev;
else if (prev >= 0)
m_AvailableNameTailMap.AddOrUpdate(objectName, prev);
else
m_AvailableNameTailMap.Remove(objectName);
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
}
private T SpawnAny(string name)
{
if (!m_AllNameHeadMap.TryGetValue(name, out int head))
@ -902,6 +953,12 @@ namespace AlicizaX.ObjectPool
ValidateState();
return slot.Obj;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateActiveState()
{
IsActive = m_TargetMap.Count > 0 || m_PendingReleaseCount > 0;
}
}
}
}

View File

@ -1,60 +1,68 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX.ObjectPool
{
[UnityEngine.Scripting.Preserve]
internal sealed partial class ObjectPoolService : ServiceBase, IObjectPoolService, IObjectPoolServiceDebugView, IServiceTickable
internal sealed partial class ObjectPoolService : ServiceBase, IObjectPoolService, IServiceTickable
{
private const float DefaultAutoReleaseInterval = float.MaxValue;
private const int DefaultCapacity = int.MaxValue;
private const float DefaultExpireTime = float.MaxValue;
private const int InitPoolArrayCapacity = 8;
private readonly Dictionary<TypeNamePair, ObjectPoolBase> m_ObjectPools;
private readonly List<ObjectPoolBase> m_ObjectPoolList;
private readonly Dictionary<ObjectPoolBase, int> m_ObjectPoolIndexMap;
private readonly List<ObjectPoolBase> m_CachedSortedObjectPools;
private readonly Comparison<ObjectPoolBase> m_ObjectPoolComparer;
private TypeNamePairOpenHashMap m_PoolMap;
private ReferenceOpenHashMap m_PoolRefMap;
private ObjectPoolBase[] m_Pools;
private int m_PoolCount;
private ObjectPoolBase[] m_CachedSortedPools;
private int m_CachedSortedCount;
public ObjectPoolService()
{
m_ObjectPools = new Dictionary<TypeNamePair, ObjectPoolBase>();
m_ObjectPoolList = new List<ObjectPoolBase>();
m_ObjectPoolIndexMap = new Dictionary<ObjectPoolBase, int>(AlicizaX.ReferenceComparer<ObjectPoolBase>.Instance);
m_CachedSortedObjectPools = new List<ObjectPoolBase>();
m_ObjectPoolComparer = ObjectPoolComparer;
m_PoolMap = new TypeNamePairOpenHashMap(InitPoolArrayCapacity);
m_PoolRefMap = new ReferenceOpenHashMap(InitPoolArrayCapacity);
m_Pools = new ObjectPoolBase[InitPoolArrayCapacity];
m_PoolCount = 0;
m_CachedSortedPools = Array.Empty<ObjectPoolBase>();
m_CachedSortedCount = 0;
}
public int Priority => 1;
public int Count => m_ObjectPools.Count;
public int Count => m_PoolMap.Count;
void IServiceTickable.Tick(float deltaTime)
{
for (int i = 0; i < m_ObjectPoolList.Count; i++)
m_ObjectPoolList[i].Update(deltaTime, Time.unscaledDeltaTime);
float unscaled = Time.unscaledDeltaTime;
for (int i = 0; i < m_PoolCount; i++)
{
var pool = m_Pools[i];
if (pool.IsActive)
pool.Update(deltaTime, unscaled);
}
}
protected override void OnInitialize() { }
protected override void OnDestroyService()
{
for (int i = m_ObjectPoolList.Count - 1; i >= 0; i--)
m_ObjectPoolList[i].Shutdown();
m_ObjectPools.Clear();
m_ObjectPoolList.Clear();
m_ObjectPoolIndexMap.Clear();
m_CachedSortedObjectPools.Clear();
for (int i = m_PoolCount - 1; i >= 0; i--)
m_Pools[i].Shutdown();
m_PoolMap.Clear();
m_PoolRefMap.Clear();
Array.Clear(m_Pools, 0, m_PoolCount);
m_PoolCount = 0;
m_CachedSortedCount = 0;
}
// ========== Has ==========
public bool HasObjectPool<T>() where T : ObjectBase
=> m_ObjectPools.ContainsKey(new TypeNamePair(typeof(T)));
=> m_PoolMap.ContainsKey(new TypeNamePair(typeof(T)));
public bool HasObjectPool<T>(string name) where T : ObjectBase
=> m_ObjectPools.ContainsKey(new TypeNamePair(typeof(T), name));
=> m_PoolMap.ContainsKey(new TypeNamePair(typeof(T), name));
// ========== Get ==========
@ -66,7 +74,7 @@ namespace AlicizaX.ObjectPool
// ========== GetAll ==========
int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results)
internal int GetAllObjectPools(bool sort, ObjectPoolBase[] results)
{
if (results == null)
{
@ -76,18 +84,17 @@ namespace AlicizaX.ObjectPool
return 0;
}
List<ObjectPoolBase> source = m_ObjectPoolList;
if (sort)
{
CacheSortedObjectPools();
source = m_CachedSortedObjectPools;
int copyCount = results.Length < m_CachedSortedCount ? results.Length : m_CachedSortedCount;
Array.Copy(m_CachedSortedPools, 0, results, 0, copyCount);
return m_CachedSortedCount;
}
int count = source.Count;
int copyCount = results.Length < count ? results.Length : count;
for (int i = 0; i < copyCount; i++)
results[i] = source[i];
int count = m_PoolCount;
int copy = results.Length < count ? results.Length : count;
Array.Copy(m_Pools, 0, results, 0, copy);
return count;
}
@ -96,7 +103,7 @@ namespace AlicizaX.ObjectPool
public IObjectPool<T> CreatePool<T>(ObjectPoolCreateOptions options = default) where T : ObjectBase
{
var key = new TypeNamePair(typeof(T), options.Name);
if (m_ObjectPools.ContainsKey(key))
if (m_PoolMap.ContainsKey(key))
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Already exist object pool '{key}'.");
@ -112,9 +119,17 @@ namespace AlicizaX.ObjectPool
options.ExpireTime ?? DefaultExpireTime,
options.Priority);
m_ObjectPools.Add(key, pool);
m_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
m_ObjectPoolList.Add(pool);
int idx = m_PoolCount;
if (idx >= m_Pools.Length)
{
var newArr = new ObjectPoolBase[m_Pools.Length * 2];
Array.Copy(m_Pools, 0, newArr, 0, m_PoolCount);
m_Pools = newArr;
}
m_Pools[idx] = pool;
m_PoolCount++;
m_PoolMap.AddOrUpdate(key, idx);
m_PoolRefMap.AddOrUpdate(pool, idx);
return pool;
}
@ -143,67 +158,81 @@ namespace AlicizaX.ObjectPool
public void Release()
{
CacheSortedObjectPools();
for (int i = 0; i < m_CachedSortedObjectPools.Count; i++)
m_CachedSortedObjectPools[i].Release();
for (int i = 0; i < m_CachedSortedCount; i++)
m_CachedSortedPools[i].Release();
}
public void ReleaseAllUnused()
{
CacheSortedObjectPools();
for (int i = 0; i < m_CachedSortedObjectPools.Count; i++)
m_CachedSortedObjectPools[i].ReleaseAllUnused();
for (int i = 0; i < m_CachedSortedCount; i++)
m_CachedSortedPools[i].ReleaseAllUnused();
}
// ========== Low memory ==========
public void OnLowMemory()
{
for (int i = 0; i < m_ObjectPoolList.Count; i++)
m_ObjectPoolList[i].OnLowMemory();
for (int i = 0; i < m_PoolCount; i++)
m_Pools[i].OnLowMemory();
}
// ========== Internal ==========
private ObjectPoolBase InternalGet(TypeNamePair key)
{
m_ObjectPools.TryGetValue(key, out var pool);
return pool;
if (m_PoolMap.TryGetValue(key, out int idx))
return m_Pools[idx];
return null;
}
private bool InternalDestroy(TypeNamePair key)
{
if (m_ObjectPools.TryGetValue(key, out var pool))
if (!m_PoolMap.TryGetValue(key, out int idx))
return false;
var pool = m_Pools[idx];
pool.Shutdown();
int lastIndex = m_PoolCount - 1;
if (idx < lastIndex)
{
pool.Shutdown();
RemovePoolFromList(pool);
m_ObjectPoolIndexMap.Remove(pool);
return m_ObjectPools.Remove(key);
var lastPool = m_Pools[lastIndex];
m_Pools[idx] = lastPool;
m_PoolRefMap.AddOrUpdate(lastPool, idx);
var lastKey = new TypeNamePair(lastPool.ObjectType, lastPool.Name);
m_PoolMap.AddOrUpdate(lastKey, idx);
}
return false;
m_Pools[lastIndex] = null;
m_PoolCount--;
m_PoolMap.Remove(key);
m_PoolRefMap.Remove(pool);
return true;
}
private void CacheSortedObjectPools()
{
m_CachedSortedObjectPools.Clear();
m_CachedSortedObjectPools.AddRange(m_ObjectPoolList);
m_CachedSortedObjectPools.Sort(m_ObjectPoolComparer);
int count = m_PoolCount;
if (m_CachedSortedPools.Length < count)
m_CachedSortedPools = new ObjectPoolBase[Math.Max(count, 8)];
Array.Copy(m_Pools, 0, m_CachedSortedPools, 0, count);
m_CachedSortedCount = count;
for (int i = 1; i < count; i++)
{
var key = m_CachedSortedPools[i];
int keyPriority = key.Priority;
int j = i - 1;
while (j >= 0 && m_CachedSortedPools[j].Priority > keyPriority)
{
m_CachedSortedPools[j + 1] = m_CachedSortedPools[j];
j--;
}
m_CachedSortedPools[j + 1] = key;
}
}
private void RemovePoolFromList(ObjectPoolBase pool)
{
if (!m_ObjectPoolIndexMap.TryGetValue(pool, out int index))
return;
int lastIndex = m_ObjectPoolList.Count - 1;
ObjectPoolBase lastPool = m_ObjectPoolList[lastIndex];
m_ObjectPoolList[index] = lastPool;
m_ObjectPoolList.RemoveAt(lastIndex);
if (!ReferenceEquals(lastPool, pool))
m_ObjectPoolIndexMap[lastPool] = index;
}
private static int ObjectPoolComparer(ObjectPoolBase a, ObjectPoolBase b)
=> a.Priority.CompareTo(b.Priority);
}
}

View File

@ -8,17 +8,15 @@ namespace AlicizaX.ObjectPool
/// </summary>
internal static class SlotArrayPool<T>
{
private static readonly ArrayPool<T> s_Pool = ArrayPool<T>.Create(256, 50);
public static T[] Rent(int minimumLength)
{
return s_Pool.Rent(minimumLength);
return ArrayPool<T>.Shared.Rent(minimumLength);
}
public static void Return(T[] array, bool clearArray = false)
{
if (array != null)
s_Pool.Return(array, clearArray);
ArrayPool<T>.Shared.Return(array, clearArray);
}
}
}

View File

@ -47,7 +47,7 @@ namespace AlicizaX.ObjectPool
while (i > 0)
{
int idx = i - 1;
if (m_Keys[idx] == key) { value = m_Values[idx]; return true; }
if (string.Equals(m_Keys[idx], key, StringComparison.Ordinal)) { value = m_Values[idx]; return true; }
i = m_Next[idx];
}
value = -1;
@ -67,7 +67,7 @@ namespace AlicizaX.ObjectPool
while (i > 0)
{
int ei = i - 1;
if (m_Keys[ei] == key) { m_Values[ei] = value; return; }
if (string.Equals(m_Keys[ei], key, StringComparison.Ordinal)) { m_Values[ei] = value; return; }
i = m_Next[ei];
}
@ -101,7 +101,7 @@ namespace AlicizaX.ObjectPool
while (i > 0)
{
int idx = i - 1;
if (m_Keys[idx] == key)
if (string.Equals(m_Keys[idx], key, StringComparison.Ordinal))
{
if (prev == 0) m_Buckets[bucket] = m_Next[idx];
else m_Next[prev - 1] = m_Next[idx];

View File

@ -0,0 +1,186 @@
using System;
using System.Runtime.CompilerServices;
namespace AlicizaX.ObjectPool
{
internal struct TypeNamePairOpenHashMap
{
private int[] m_Buckets;
private TypeNamePair[] m_Keys;
private int[] m_Values;
private int[] m_Next;
private int m_Count;
private int m_FreeList;
private int m_Mask;
private int m_AllocCount;
private const int MinCapacity = 8;
public int Count => m_Count;
public TypeNamePairOpenHashMap(int capacity)
{
int cap = NextPowerOf2(Math.Max(capacity, MinCapacity));
m_Mask = cap - 1;
m_Buckets = SlotArrayPool<int>.Rent(cap);
m_Keys = SlotArrayPool<TypeNamePair>.Rent(cap);
m_Values = SlotArrayPool<int>.Rent(cap);
m_Next = SlotArrayPool<int>.Rent(cap);
Array.Clear(m_Buckets, 0, m_Buckets.Length);
Array.Clear(m_Keys, 0, m_Keys.Length);
Array.Clear(m_Values, 0, m_Values.Length);
Array.Clear(m_Next, 0, m_Next.Length);
m_Count = 0;
m_FreeList = 0;
m_AllocCount = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(TypeNamePair key, out int value)
{
if (m_Buckets == null) { value = -1; return false; }
int hash = key.GetHashCode() & 0x7FFFFFFF;
int i = m_Buckets[hash & m_Mask];
while (i > 0)
{
int idx = i - 1;
if (m_Keys[idx].Equals(key)) { value = m_Values[idx]; return true; }
i = m_Next[idx];
}
value = -1;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ContainsKey(TypeNamePair key) => TryGetValue(key, out _);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddOrUpdate(TypeNamePair key, int value)
{
if (m_Count >= ((m_Mask + 1) * 3 >> 2))
Grow();
int hash = key.GetHashCode() & 0x7FFFFFFF;
int bucket = hash & m_Mask;
int i = m_Buckets[bucket];
while (i > 0)
{
int ei = i - 1;
if (m_Keys[ei].Equals(key)) { m_Values[ei] = value; return; }
i = m_Next[ei];
}
int idx;
if (m_FreeList > 0)
{
idx = m_FreeList - 1;
m_FreeList = m_Next[idx];
}
else
{
if (m_AllocCount > m_Mask) { Grow(); bucket = hash & m_Mask; }
idx = m_AllocCount++;
}
m_Keys[idx] = key;
m_Values[idx] = value;
m_Next[idx] = m_Buckets[bucket];
m_Buckets[bucket] = idx + 1;
m_Count++;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Remove(TypeNamePair key)
{
if (m_Buckets == null) return false;
int hash = key.GetHashCode() & 0x7FFFFFFF;
int bucket = hash & m_Mask;
int prev = 0;
int i = m_Buckets[bucket];
while (i > 0)
{
int idx = i - 1;
if (m_Keys[idx].Equals(key))
{
if (prev == 0) m_Buckets[bucket] = m_Next[idx];
else m_Next[prev - 1] = m_Next[idx];
m_Keys[idx] = default;
m_Values[idx] = -1;
m_Next[idx] = m_FreeList;
m_FreeList = idx + 1;
m_Count--;
return true;
}
prev = i;
i = m_Next[idx];
}
return false;
}
public void Clear()
{
if (m_Buckets == null) return;
int cap = m_Mask + 1;
Array.Clear(m_Buckets, 0, cap);
Array.Clear(m_Keys, 0, cap);
Array.Clear(m_Values, 0, cap);
Array.Clear(m_Next, 0, cap);
m_Count = 0;
m_FreeList = 0;
m_AllocCount = 0;
}
private void Grow()
{
int newCap = (m_Mask + 1) << 1;
if (newCap < MinCapacity) newCap = MinCapacity;
int newMask = newCap - 1;
var newBuckets = SlotArrayPool<int>.Rent(newCap);
var newKeys = SlotArrayPool<TypeNamePair>.Rent(newCap);
var newValues = SlotArrayPool<int>.Rent(newCap);
var newNext = SlotArrayPool<int>.Rent(newCap);
Array.Clear(newBuckets, 0, newBuckets.Length);
Array.Clear(newNext, 0, newNext.Length);
int newAlloc = 0;
int oldCap = m_Mask + 1;
for (int b = 0; b < oldCap; b++)
{
int i = m_Buckets[b];
while (i > 0)
{
int old = i - 1;
int ni = newAlloc++;
newKeys[ni] = m_Keys[old];
newValues[ni] = m_Values[old];
int hash = newKeys[ni].GetHashCode() & 0x7FFFFFFF;
int nb = hash & newMask;
newNext[ni] = newBuckets[nb];
newBuckets[nb] = ni + 1;
i = m_Next[old];
}
}
SlotArrayPool<int>.Return(m_Buckets, true);
SlotArrayPool<TypeNamePair>.Return(m_Keys, true);
SlotArrayPool<int>.Return(m_Values, true);
SlotArrayPool<int>.Return(m_Next, true);
m_Buckets = newBuckets;
m_Keys = newKeys;
m_Values = newValues;
m_Next = newNext;
m_Mask = newMask;
m_AllocCount = newAlloc;
m_FreeList = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int NextPowerOf2(int v)
{
v--;
v |= v >> 1; v |= v >> 2; v |= v >> 4;
v |= v >> 8; v |= v >> 16;
return v + 1;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8e3fa6e0005d58a4eba0e005ee613c61
guid: 42dbc1a039373c04abf61e06846ce201
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,10 +1,9 @@
using System.Buffers;
using AlicizaX.ObjectPool;
using AlicizaX;
namespace AlicizaX.Resource.Runtime
{
public class AssetItemObject : ObjectBase
public class AssetItemObject : ObjectBase<UnityEngine.Object>
{
public static AssetItemObject Create(string location, UnityEngine.Object target)
{
@ -15,7 +14,6 @@ namespace AlicizaX.Resource.Runtime
protected internal override void Release(bool isShutdown)
{
// Asset handle cleanup is owned by ResourceExtComponent/ResourceService.
}
}
}

View File

@ -3,7 +3,7 @@ using AlicizaX.ObjectPool;
namespace AlicizaX.UI.Runtime
{
internal class UIMetadataObject : ObjectBase
internal class UIMetadataObject : ObjectBase<UIMetadata>
{
public static UIMetadataObject Create(UIMetadata target, string name)
{
@ -22,7 +22,7 @@ namespace AlicizaX.UI.Runtime
protected internal override void Release(bool isShutdown)
{
UIMetadata metadata = (UIMetadata)Target;
UIMetadata metadata = Target;
if (metadata != null)
{
}
@ -32,7 +32,7 @@ namespace AlicizaX.UI.Runtime
{
base.OnUnspawn();
UIMetadata metadata = (UIMetadata)Target;
UIMetadata metadata = Target;
if (metadata != null)
{
metadata.CancelAsyncOperations();