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

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

View File

@ -99,7 +99,7 @@ namespace AlicizaX
/// <returns>被比较的对象是否与自身相等。</returns> /// <returns>被比较的对象是否与自身相等。</returns>
public bool Equals(TypeNamePair value) 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> /// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ using System.Runtime.CompilerServices;
namespace AlicizaX namespace AlicizaX
{ {
public static class MemoryPool<T> where T : class, IMemory, new() public static class MemoryPool<T> where T : class, IMemory, new()
{ {
private sealed class ReferenceComparer : IEqualityComparer<T> 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 T[] s_Stack = Array.Empty<T>();
private static int s_Count; 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 Dictionary<T, byte> s_InPoolSet;
private static int s_StrictCheckVersion = -1; 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_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 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_AcquireCount;
private static int s_ReleaseCount; private static int s_ReleaseCount;
private static int s_CreateCount; private static int s_CreateCount;
static MemoryPool() static MemoryPool()
{ {
MemoryPoolRegistry.Register(typeof(T), new MemoryPoolRegistry.MemoryPoolHandle( s_Handle = new MemoryPoolRegistry.MemoryPoolHandle(
acquire: AcquireAsMemory, acquire: AcquireAsMemory,
release: ReleaseAsMemory, release: ReleaseAsMemory,
clear: ClearAll, clear: ClearAll,
prewarm: Prewarm, prewarm: Prewarm,
getInfo: GetInfo, getInfo: GetInfo,
tick: Tick, tick: Tick,
shrink: Shrink shrink: Shrink,
)); compact: Compact);
MemoryPoolRegistry.Register(typeof(T), s_Handle);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -75,14 +80,18 @@ namespace AlicizaX
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Acquire() public static T Acquire()
{ {
MemoryPoolRegistry.ScheduleTick(s_Handle);
s_AcquireCount++; s_AcquireCount++;
s_RecentAcquireCount++; s_AcquireThisFrame++;
s_CurrentInUse++; s_CurrentInUse++;
if (s_CurrentInUse > s_PeakInUse) if (s_CurrentInUse > s_PeakInUseShort)
s_PeakInUse = s_CurrentInUse; s_PeakInUseShort = s_CurrentInUse;
if (s_CurrentInUse > s_PeakInUseLong)
s_PeakInUseLong = s_CurrentInUse;
if (s_Count > 0) if (s_Count > 0)
{ {
s_ConsecutiveMiss = 0;
int idx = --s_Count; int idx = --s_Count;
T item = s_Stack[idx]; T item = s_Stack[idx];
s_Stack[idx] = null; s_Stack[idx] = null;
@ -90,6 +99,7 @@ namespace AlicizaX
return item; return item;
} }
s_ConsecutiveMiss++;
return CreateNew(); return CreateNew();
} }
@ -105,107 +115,139 @@ namespace AlicizaX
{ {
if (item == null) return; if (item == null) return;
MemoryPoolRegistry.ScheduleTick(s_Handle);
EnsureStrictCheckState(); EnsureStrictCheckState();
if (MemoryPool.EnableStrictCheck && s_InPoolSet.ContainsKey(item)) if (MemoryPool.EnableStrictCheck && s_InPoolSet.ContainsKey(item))
throw new InvalidOperationException($"MemoryPool<{typeof(T).Name}>: Double release detected."); throw new InvalidOperationException($"MemoryPool<{typeof(T).Name}>: Double release detected.");
s_ReleaseCount++; s_ReleaseCount++;
s_ReleaseThisFrame++;
if (s_CurrentInUse > 0) if (s_CurrentInUse > 0)
s_CurrentInUse--; s_CurrentInUse--;
item.Clear(); item.Clear();
if (s_Count >= s_MaxCapacity) if (s_Count >= s_HardCapacity)
return; return;
if (s_Count == s_Stack.Length) EnsureStackCapacity(s_Count + 1);
Grow();
AddToStrictCheckSet(item); AddToStrictCheckSet(item);
s_Stack[s_Count++] = 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; s_LastTickFrame = frameCount;
if (s_PeakInUse > s_HighWaterMark) bool active = s_AcquireThisFrame > 0 || s_ReleaseThisFrame > 0 || s_CurrentInUse > 0;
s_HighWaterMark = s_PeakInUse; if (active)
{
if (s_RecentAcquireCount == 0) s_HotFrames++;
s_IdleFrames++;
else
s_IdleFrames = 0; s_IdleFrames = 0;
}
s_RecentAcquireCount = 0; else
if (s_Count <= MIN_KEEP) return;
if (s_IdleFrames >= IDLE_THRESHOLD)
{ {
int target = Math.Max((int)(s_HighWaterMark * 1.5f), MIN_KEEP); s_IdleFrames++;
s_HotFrames = 0;
}
if (s_Count > target) UpdateTargetReserve();
FillReserveBudgeted();
ReleaseExcessBudgeted();
s_AcquireThisFrame = 0;
s_ReleaseThisFrame = 0;
return s_IdleFrames < UNSCHEDULE_IDLE_FRAMES || s_Count > s_TargetReserve;
}
private static void UpdateTargetReserve()
{ {
int excess = s_Count - target; if (s_IdleFrames >= SHORT_DECAY_START && s_PeakInUseShort > 0)
s_PeakInUseShort -= Math.Max(1, s_PeakInUseShort >> 4);
float ratio = s_IdleFrames < IDLE_AGGRESSIVE ? 0.25f : 0.5f; if (s_IdleFrames >= LONG_DECAY_START && s_PeakInUseLong > 0)
int removeCount = Math.Max((int)(excess * ratio), 1); 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; int newCount = s_Count - removeCount;
RemoveRangeFromStrictCheckSet(newCount, removeCount); RemoveRangeFromStrictCheckSet(newCount, removeCount);
Array.Clear(s_Stack, newCount, removeCount); Array.Clear(s_Stack, newCount, removeCount);
s_Count = newCount; s_Count = newCount;
TryShrinkArray();
} }
if (s_IdleFrames >= IDLE_AGGRESSIVE)
{
s_HighWaterMark = Math.Max(s_HighWaterMark >> 1, MIN_KEEP);
s_PeakInUse = s_CurrentInUse;
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)] [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 (s_Stack.Length >= required)
if (newLen > s_MaxCapacity) newLen = s_MaxCapacity; 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]; var newStack = new T[newLen];
Array.Copy(s_Stack, 0, newStack, 0, s_Count); Array.Copy(s_Stack, 0, newStack, 0, s_Count);
s_Stack = newStack; 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) 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_Count) return;
if (count > s_Stack.Length) EnsureStackCapacity(count);
{
var newStack = new T[count];
Array.Copy(s_Stack, 0, newStack, 0, s_Count);
s_Stack = newStack;
}
while (s_Count < count) while (s_Count < count)
{ {
@ -214,6 +256,9 @@ namespace AlicizaX
AddToStrictCheckSet(item); AddToStrictCheckSet(item);
s_CreateCount++; s_CreateCount++;
} }
if (count > s_TargetReserve)
s_TargetReserve = Math.Min(count, s_SoftCapacity);
} }
public static void Shrink(int keepCount) public static void Shrink(int keepCount)
@ -224,23 +269,57 @@ namespace AlicizaX
RemoveRangeFromStrictCheckSet(keepCount, s_Count - keepCount); RemoveRangeFromStrictCheckSet(keepCount, s_Count - keepCount);
Array.Clear(s_Stack, keepCount, s_Count - keepCount); Array.Clear(s_Stack, keepCount, s_Count - 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) 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() public static void ClearAll()
{ {
MemoryPoolRegistry.UnscheduleTick(s_Handle);
ResetStrictCheckSet(); ResetStrictCheckSet();
Array.Clear(s_Stack, 0, s_Count); Array.Clear(s_Stack, 0, s_Count);
s_Count = 0; s_Count = 0;
s_HighWaterMark = s_CurrentInUse; s_CurrentInUse = 0;
s_PeakInUse = s_CurrentInUse; s_PeakInUseShort = 0;
s_PeakInUseLong = 0;
s_AcquireThisFrame = 0;
s_ReleaseThisFrame = 0;
s_TargetReserve = MIN_KEEP;
s_IdleFrames = 0; s_IdleFrames = 0;
s_RecentAcquireCount = 0; s_HotFrames = 0;
s_ConsecutiveMiss = 0;
s_Stack = Array.Empty<T>(); s_Stack = Array.Empty<T>();
} }
@ -257,7 +336,7 @@ namespace AlicizaX
s_CurrentInUse, s_CurrentInUse,
s_AcquireCount, s_ReleaseCount, s_AcquireCount, s_ReleaseCount,
s_CreateCount, s_CreateCount,
s_HighWaterMark, s_MaxCapacity, s_TargetReserve, s_SoftCapacity,
s_IdleFrames, s_Stack.Length); s_IdleFrames, s_Stack.Length);
} }
@ -338,5 +417,16 @@ namespace AlicizaX
s_InPoolSet.Clear(); s_InPoolSet.Clear();
} }
private static int NextPowerOfTwo(int value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@ namespace AlicizaX.ObjectPool
while (i > 0) while (i > 0)
{ {
int idx = i - 1; 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]; i = m_Next[idx];
} }
value = -1; value = -1;
@ -67,7 +67,7 @@ namespace AlicizaX.ObjectPool
while (i > 0) while (i > 0)
{ {
int ei = i - 1; 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]; i = m_Next[ei];
} }
@ -101,7 +101,7 @@ namespace AlicizaX.ObjectPool
while (i > 0) while (i > 0)
{ {
int idx = i - 1; 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]; if (prev == 0) m_Buckets[bucket] = m_Next[idx];
else m_Next[prev - 1] = m_Next[idx]; else m_Next[prev - 1] = m_Next[idx];

View File

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

View File

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

View File

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

View File

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