236 lines
6.7 KiB
C#
236 lines
6.7 KiB
C#
using System;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace AlicizaX
|
|
{
|
|
|
|
public static class MemoryPool<T> where T : class, IMemory, new()
|
|
{
|
|
private static T[] s_Stack = Array.Empty<T>();
|
|
private static int s_Count;
|
|
private static int s_MaxCapacity = 2048;
|
|
|
|
// ---- 回收策略状态 ----
|
|
private static int s_HighWaterMark;
|
|
private static int s_RecentAcquireCount;
|
|
private static int s_IdleFrames;
|
|
private static int s_LastTickFrame;
|
|
private static int s_PeakInUse;
|
|
private static int s_CurrentInUse;
|
|
|
|
private const int IDLE_THRESHOLD = 300; // ~5s @60fps
|
|
private const int IDLE_AGGRESSIVE = 900; // ~15s @60fps
|
|
private const int MIN_KEEP = 4;
|
|
|
|
// ---- 统计计数器 ----
|
|
private static int s_AcquireCount;
|
|
private static int s_ReleaseCount;
|
|
private static int s_CreateCount;
|
|
|
|
static MemoryPool()
|
|
{
|
|
MemoryPoolRegistry.Register(typeof(T), new MemoryPoolRegistry.MemoryPoolHandle(
|
|
acquire: () => Acquire(),
|
|
release: obj => Release((T)obj),
|
|
clear: ClearAll,
|
|
prewarm: Prewarm,
|
|
getInfo: GetInfo,
|
|
tick: Tick,
|
|
shrink: Shrink
|
|
));
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static T Acquire()
|
|
{
|
|
s_AcquireCount++;
|
|
s_RecentAcquireCount++;
|
|
s_CurrentInUse++;
|
|
if (s_CurrentInUse > s_PeakInUse)
|
|
s_PeakInUse = s_CurrentInUse;
|
|
|
|
if (s_Count > 0)
|
|
{
|
|
int idx = --s_Count;
|
|
T item = s_Stack[idx];
|
|
s_Stack[idx] = null;
|
|
return item;
|
|
}
|
|
|
|
return CreateNew();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static T CreateNew()
|
|
{
|
|
s_CreateCount++;
|
|
return new T();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static bool ContainsInPool(T item)
|
|
{
|
|
for (int i = 0; i < s_Count; i++)
|
|
{
|
|
if (ReferenceEquals(s_Stack[i], item))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void Release(T item)
|
|
{
|
|
if (item == null) return;
|
|
|
|
if (MemoryPool.EnableStrictCheck && ContainsInPool(item))
|
|
throw new InvalidOperationException($"MemoryPool<{typeof(T).Name}>: Double release detected.");
|
|
|
|
s_ReleaseCount++;
|
|
|
|
if (s_CurrentInUse > 0)
|
|
s_CurrentInUse--;
|
|
|
|
item.Clear();
|
|
|
|
if (s_Count >= s_MaxCapacity)
|
|
return;
|
|
|
|
if (s_Count == s_Stack.Length)
|
|
Grow();
|
|
|
|
s_Stack[s_Count++] = item;
|
|
}
|
|
|
|
internal static void Tick(int frameCount)
|
|
{
|
|
if (frameCount == s_LastTickFrame) return;
|
|
s_LastTickFrame = frameCount;
|
|
|
|
if (s_PeakInUse > s_HighWaterMark)
|
|
s_HighWaterMark = s_PeakInUse;
|
|
|
|
if (s_RecentAcquireCount == 0)
|
|
s_IdleFrames++;
|
|
else
|
|
s_IdleFrames = 0;
|
|
|
|
s_RecentAcquireCount = 0;
|
|
|
|
if (s_Count <= MIN_KEEP) return;
|
|
|
|
if (s_IdleFrames >= IDLE_THRESHOLD)
|
|
{
|
|
int target = Math.Max((int)(s_HighWaterMark * 1.5f), MIN_KEEP);
|
|
|
|
if (s_Count > target)
|
|
{
|
|
int excess = s_Count - target;
|
|
|
|
float ratio = s_IdleFrames < IDLE_AGGRESSIVE ? 0.25f : 0.5f;
|
|
int removeCount = Math.Max((int)(excess * ratio), 1);
|
|
|
|
int newCount = s_Count - removeCount;
|
|
Array.Clear(s_Stack, newCount, removeCount);
|
|
s_Count = newCount;
|
|
|
|
TryShrinkArray();
|
|
}
|
|
|
|
if (s_IdleFrames >= IDLE_AGGRESSIVE)
|
|
{
|
|
s_HighWaterMark = Math.Max(s_HighWaterMark >> 1, MIN_KEEP);
|
|
s_PeakInUse = s_CurrentInUse;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static void Grow()
|
|
{
|
|
int newLen = s_Stack.Length == 0 ? 8 : s_Stack.Length << 1;
|
|
if (newLen > s_MaxCapacity) newLen = s_MaxCapacity;
|
|
var newStack = new T[newLen];
|
|
Array.Copy(s_Stack, 0, newStack, 0, s_Count);
|
|
s_Stack = newStack;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private static void TryShrinkArray()
|
|
{
|
|
if (s_Stack.Length > 32 && s_Count < s_Stack.Length >> 2)
|
|
{
|
|
int newLen = Math.Max(s_Count << 1, 8);
|
|
var newStack = new T[newLen];
|
|
Array.Copy(s_Stack, 0, newStack, 0, s_Count);
|
|
s_Stack = newStack;
|
|
}
|
|
}
|
|
|
|
|
|
public static void Prewarm(int count)
|
|
{
|
|
count = Math.Min(count, s_MaxCapacity);
|
|
if (count <= s_Count) return;
|
|
|
|
if (count > s_Stack.Length)
|
|
{
|
|
var newStack = new T[count];
|
|
Array.Copy(s_Stack, 0, newStack, 0, s_Count);
|
|
s_Stack = newStack;
|
|
}
|
|
|
|
while (s_Count < count)
|
|
{
|
|
s_Stack[s_Count++] = new T();
|
|
s_CreateCount++;
|
|
}
|
|
}
|
|
|
|
public static void Shrink(int keepCount)
|
|
{
|
|
if (keepCount >= s_Count) return;
|
|
keepCount = Math.Max(keepCount, 0);
|
|
|
|
Array.Clear(s_Stack, keepCount, s_Count - keepCount);
|
|
s_Count = keepCount;
|
|
TryShrinkArray();
|
|
}
|
|
|
|
public static void SetMaxCapacity(int max)
|
|
{
|
|
s_MaxCapacity = Math.Max(max, MIN_KEEP);
|
|
}
|
|
|
|
public static void ClearAll()
|
|
{
|
|
Array.Clear(s_Stack, 0, s_Count);
|
|
s_Count = 0;
|
|
s_HighWaterMark = s_CurrentInUse;
|
|
s_PeakInUse = s_CurrentInUse;
|
|
s_IdleFrames = 0;
|
|
s_RecentAcquireCount = 0;
|
|
s_Stack = Array.Empty<T>();
|
|
}
|
|
|
|
public static int UnusedCount
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => s_Count;
|
|
}
|
|
|
|
internal static MemoryPoolInfo GetInfo()
|
|
{
|
|
return new MemoryPoolInfo(
|
|
typeof(T), s_Count,
|
|
s_CurrentInUse,
|
|
s_AcquireCount, s_ReleaseCount,
|
|
s_CreateCount,
|
|
s_HighWaterMark, s_MaxCapacity,
|
|
s_IdleFrames, s_Stack.Length);
|
|
}
|
|
}
|
|
}
|