[Opt] ObjectPoolService && MemoryPool [Add]Benchmark Example
优化ObjcetPoolService 优化MemoryPoolService 增加ObjectPool 和MemoryPool的Benchmark
This commit is contained in:
parent
58fb685792
commit
9afd5d9ff9
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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++)
|
||||
{
|
||||
|
||||
3
Runtime/MemoryPool/Benchmark.meta
Normal file
3
Runtime/MemoryPool/Benchmark.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cd45b8170524028b8e9d569c3bb5b9d
|
||||
timeCreated: 1777270000
|
||||
814
Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs
Normal file
814
Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs
Normal 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
|
||||
|
||||
11
Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs.meta
Normal file
11
Runtime/MemoryPool/Benchmark/MemoryPoolBenchmark.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 80a2bc6a495f4a1cb6ffb632fda36376
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AlicizaX
|
||||
{
|
||||
/// <summary>
|
||||
/// 内存池。保留旧 API 签名,内部转发到 MemoryPool<T> / 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);
|
||||
|
||||
@ -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)
|
||||
|
||||
3
Runtime/ObjectPool/Benchmark.meta
Normal file
3
Runtime/ObjectPool/Benchmark.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0a235d2743b4ec68a4c0bfd5dc2eae6
|
||||
timeCreated: 1777269624
|
||||
739
Runtime/ObjectPool/Benchmark/ObjectPoolBenchmark.cs
Normal file
739
Runtime/ObjectPool/Benchmark/ObjectPoolBenchmark.cs
Normal 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
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b99f3fc658c4a3d4f80218ab7113341e
|
||||
guid: 472607d053c8822499f9960bb6ef21bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@ -1,7 +0,0 @@
|
||||
namespace AlicizaX.ObjectPool
|
||||
{
|
||||
internal interface IObjectPoolDebugView
|
||||
{
|
||||
int GetAllObjectInfos(ObjectInfo[] results);
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace AlicizaX.ObjectPool
|
||||
{
|
||||
internal interface IObjectPoolServiceDebugView
|
||||
{
|
||||
int GetAllObjectPools(bool sort, ObjectPoolBase[] results);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) { }
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
186
Runtime/ObjectPool/TypeNamePairOpenHashMap.cs
Normal file
186
Runtime/ObjectPool/TypeNamePairOpenHashMap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e3fa6e0005d58a4eba0e005ee613c61
|
||||
guid: 42dbc1a039373c04abf61e06846ce201
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user