com.alicizax.unity.framework/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs

1140 lines
41 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace AlicizaX.ObjectPool
{
internal sealed partial class ObjectPoolService
{
private sealed class ObjectPool<T> : ObjectPoolBase, IObjectPool<T> where T : ObjectBase
{
private struct ObjectSlot
{
public T Obj;
public int SpawnCount;
public float LastUseTime;
public int PrevByName;
public int NextByName;
public int PrevUnused;
public int NextUnused;
public byte Flags;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsAlive() => (Flags & 1) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetAlive(bool alive)
{
if (alive) Flags |= 1;
else Flags = 0;
}
}
private ObjectSlot[][] m_Pages;
private int[][] m_PageFreeStacks;
private int[] m_PageAliveCounts;
private int[] m_PageFreeTops;
private byte[] m_PageFlags;
private int m_PageCount;
private int[] m_FreePageStack;
private int m_FreePageTop;
private int[] m_EmptyPageStack;
private int m_EmptyPageTop;
private int[] m_ReleasedPageStack;
private int m_ReleasedPageTop;
private ReferenceOpenHashMap m_TargetMap;
private StringOpenHashMap m_AllNameHeadMap;
private StringOpenHashMap m_NameCursorMap;
private readonly bool m_AllowMultiSpawn;
private readonly MemoryPoolHandle m_ObjectMemoryPoolHandle;
private float m_AutoReleaseInterval;
private int m_Capacity;
private float m_ExpireTime;
private int m_Priority;
private float m_AutoReleaseTime;
private int m_PendingReleaseCount;
private int m_ReleasePerFrameBudget;
private int m_UnusedHead;
private int m_UnusedTail;
private int m_LastBudgetScanStart;
private bool m_IsShuttingDown;
private int m_ShrinkCounter;
private const int ShrinkCheckInterval = 60;
private const int DefaultReleasePerFrame = 8;
private const int InitSlotCapacity = 16;
private const int InitPageCapacity = 4;
private const int PageBits = 8;
private const int PageSize = 1 << PageBits;
private const int PageMask = PageSize - 1;
private const byte PageAllocated = 1;
private const byte PageInFreeStack = 2;
private const byte PageInEmptyStack = 4;
private const int EmptyPageReleaseBudget = 1;
public ObjectPool(string name, bool allowMultiSpawn,
float autoReleaseInterval, int capacity, float expireTime, int priority)
: base(name)
{
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
m_Pages = SlotArrayPool<ObjectSlot[]>.Rent(InitPageCapacity);
m_PageFreeStacks = SlotArrayPool<int[]>.Rent(InitPageCapacity);
m_PageAliveCounts = SlotArrayPool<int>.Rent(InitPageCapacity);
m_PageFreeTops = SlotArrayPool<int>.Rent(InitPageCapacity);
m_PageFlags = SlotArrayPool<byte>.Rent(InitPageCapacity);
m_FreePageStack = SlotArrayPool<int>.Rent(InitPageCapacity);
m_EmptyPageStack = SlotArrayPool<int>.Rent(InitPageCapacity);
m_ReleasedPageStack = SlotArrayPool<int>.Rent(InitPageCapacity);
m_TargetMap = new ReferenceOpenHashMap(initCap);
m_AllNameHeadMap = new StringOpenHashMap(initCap);
m_NameCursorMap = new StringOpenHashMap(initCap);
m_PageCount = 0;
m_FreePageTop = 0;
m_EmptyPageTop = 0;
m_ReleasedPageTop = 0;
m_AllowMultiSpawn = allowMultiSpawn;
m_ObjectMemoryPoolHandle = MemoryPool.GetHandle(typeof(T));
m_AutoReleaseInterval = autoReleaseInterval;
m_Capacity = capacity;
m_ExpireTime = expireTime;
m_Priority = priority;
m_AutoReleaseTime = 0f;
m_PendingReleaseCount = 0;
m_ReleasePerFrameBudget = DefaultReleasePerFrame;
m_UnusedHead = -1;
m_UnusedTail = -1;
m_LastBudgetScanStart = -1;
m_IsShuttingDown = false;
m_ShrinkCounter = 0;
}
public override Type ObjectType => typeof(T);
public override int Count => m_TargetMap.Count;
public override bool AllowMultiSpawn => m_AllowMultiSpawn;
public override float AutoReleaseInterval
{
get => m_AutoReleaseInterval;
set
{
if (value < 0f)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError("AutoReleaseInterval is invalid.");
#endif
return;
}
m_AutoReleaseInterval = value;
}
}
public override int Capacity
{
get => m_Capacity;
set
{
if (value < 0)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError("Capacity is invalid.");
#endif
return;
}
m_Capacity = value;
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
}
}
public override float ExpireTime
{
get => m_ExpireTime;
set
{
if (value < 0f)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError("ExpireTime is invalid.");
#endif
return;
}
m_ExpireTime = value;
}
}
public override int Priority
{
get => m_Priority;
set => m_Priority = value;
}
public override int ReleasePerFrameBudget
{
get => m_ReleasePerFrameBudget;
set => m_ReleasePerFrameBudget = Math.Max(1, value);
}
public void Register(T obj, bool spawned)
{
if (obj == null) return;
if (obj.Target == null) return;
if (m_TargetMap.TryGetValue(obj.Target, out int existingIdx) && GetSlotRef(existingIdx).IsAlive())
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
#endif
return;
}
if (!EnsureRegisterCapacity())
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' capacity is full.");
#endif
return;
}
int idx = AllocSlot();
if (idx < 0)
return;
ref var slot = ref GetSlotRef(idx);
slot.Obj = obj;
slot.SpawnCount = spawned ? 1 : 0;
slot.LastUseTime = Time.realtimeSinceStartup;
slot.PrevByName = -1;
slot.NextByName = -1;
slot.PrevUnused = -1;
slot.NextUnused = -1;
slot.SetAlive(true);
m_TargetMap.AddOrUpdate(obj.Target, idx);
string objectName = obj.Name ?? string.Empty;
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
{
GetSlotRef(existingHead).PrevByName = idx;
slot.NextByName = existingHead;
}
else
{
m_NameCursorMap.AddOrUpdate(objectName, idx);
}
m_AllNameHeadMap.AddOrUpdate(objectName, idx);
obj.LastUseTime = slot.LastUseTime;
if (spawned)
obj.OnSpawn();
else
MarkSlotAvailable(idx);
UpdateActiveState();
ValidateState();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Spawn() => Spawn(string.Empty);
public T Spawn(string name)
{
if (name == null) name = string.Empty;
if (m_AllowMultiSpawn)
return SpawnAny(name);
int head = FindAvailableByName(name);
if (head < 0) return null;
ref var slot = ref GetSlotRef(head);
if (!slot.IsAlive() || slot.SpawnCount != 0 || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain is inconsistent.");
#endif
return null;
}
float now = Time.realtimeSinceStartup;
SpawnSlot(head, now);
ValidateState();
return slot.Obj;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanSpawn() => CanSpawn(string.Empty);
public bool CanSpawn(string name)
{
if (name == null) name = string.Empty;
if (m_AllowMultiSpawn)
return m_AllNameHeadMap.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 GetSlotRef(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 GetSlotRef(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) && IsValidIndex(cursor))
{
ref var slot = ref GetSlotRef(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)
{
if (obj == null) return;
Unspawn(obj.Target);
}
public void Unspawn(object target)
{
if (target == null) return;
if (!m_TargetMap.TryGetValue(target, out int idx))
{
if (m_IsShuttingDown) return;
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Cannot find target in pool '{Name}', type='{target.GetType().FullName}'");
#endif
return;
}
UnspawnSlot(idx);
ValidateState();
}
public override void Release()
{
MarkRelease(Count - m_Capacity);
}
public override void Release(int toReleaseCount)
{
MarkRelease(toReleaseCount);
}
public override void ReleaseAllUnused()
{
int released = 0;
int current = m_UnusedHead;
while (current >= 0)
{
int next = GetSlotRef(current).NextUnused;
ref var slot = ref GetSlotRef(current);
if (CanReleaseSlot(ref slot))
{
ReleaseSlot(current, false);
released++;
}
current = next;
}
if (released > 0)
{
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
ReleaseEmptyPages(EmptyPageReleaseBudget);
UpdateActiveState();
ValidateState();
}
}
internal override void Update(float elapseSeconds, float realElapseSeconds)
{
m_AutoReleaseTime += realElapseSeconds;
if (m_AutoReleaseTime >= m_AutoReleaseInterval)
{
m_AutoReleaseTime = 0f;
MarkRelease(Count - m_Capacity);
}
bool checkExpire = m_ExpireTime < float.MaxValue;
if (m_PendingReleaseCount <= 0 && !checkExpire)
{
TryProgressiveShrink();
UpdateActiveState();
return;
}
float now = Time.realtimeSinceStartup;
float expireThreshold = checkExpire ? now - m_ExpireTime : float.MinValue;
if (m_PendingReleaseCount > 0)
{
int releaseBudget = Math.Min(m_ReleasePerFrameBudget, m_PendingReleaseCount);
int releasedByBudget = ReleaseUnused(releaseBudget, false, float.MinValue);
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - releasedByBudget);
}
else if (checkExpire)
{
ReleaseExpired(m_ReleasePerFrameBudget, expireThreshold);
}
TryProgressiveShrink();
UpdateActiveState();
}
private void TryProgressiveShrink()
{
m_ShrinkCounter++;
if (m_ShrinkCounter < ShrinkCheckInterval)
return;
m_ShrinkCounter = 0;
ReleaseEmptyPages(EmptyPageReleaseBudget);
}
internal override void Shutdown()
{
m_IsShuttingDown = true;
for (int page = 0; page < m_PageCount; page++)
{
ObjectSlot[] slots = m_Pages[page];
if (slots == null) continue;
for (int offset = 0; offset < PageSize; offset++)
{
ref var slot = ref slots[offset];
if (!slot.IsAlive()) continue;
slot.Obj.Release(true);
m_ObjectMemoryPoolHandle.Release(slot.Obj);
slot.Obj = null;
slot.SetAlive(false);
}
}
m_TargetMap.Clear();
m_AllNameHeadMap.Clear();
m_NameCursorMap.Clear();
ReleaseAllPages();
ReturnPageStorage();
m_PageCount = 0;
m_FreePageTop = 0;
m_EmptyPageTop = 0;
m_ReleasedPageTop = 0;
m_PendingReleaseCount = 0;
m_UnusedHead = -1;
m_UnusedTail = -1;
m_LastBudgetScanStart = -1;
m_IsShuttingDown = false;
ValidateState();
}
public override int GetAllObjectInfos(ObjectInfo[] results)
{
if (results == null)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError("Results is invalid.");
#endif
return 0;
}
int write = 0;
int capacity = results.Length;
for (int page = 0; page < m_PageCount; page++)
{
ObjectSlot[] slots = m_Pages[page];
if (slots == null) continue;
for (int offset = 0; offset < PageSize; offset++)
{
ref var slot = ref slots[offset];
if (!slot.IsAlive()) continue;
if (write < capacity)
{
results[write] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
slot.Obj.CustomCanReleaseFlag,
slot.Obj.LastUseTime, slot.SpawnCount);
}
write++;
}
}
return write;
}
internal override void OnLowMemory()
{
ReleaseAllUnused();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SpawnSlot(int idx, float now)
{
ref var slot = ref GetSlotRef(idx);
if (slot.SpawnCount == 0)
MarkSlotUnavailable(idx);
slot.SpawnCount++;
slot.LastUseTime = now;
slot.Obj.LastUseTime = now;
slot.Obj.OnSpawn();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UnspawnSlot(int idx)
{
ref var slot = ref GetSlotRef(idx);
float now = Time.realtimeSinceStartup;
slot.LastUseTime = now;
slot.Obj.LastUseTime = now;
slot.Obj.OnUnspawn();
slot.SpawnCount--;
if (slot.SpawnCount < 0)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object '{slot.Obj.Name}' spawn count < 0.");
#endif
slot.SpawnCount = 0;
}
if (slot.SpawnCount == 0)
MarkSlotAvailable(idx);
if (Count > m_Capacity && slot.SpawnCount == 0)
MarkRelease(Count - m_Capacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MarkRelease(int count)
{
if (count > 0)
m_PendingReleaseCount = Math.Max(m_PendingReleaseCount, count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AllocSlot()
{
int page = GetWritablePage();
if (page < 0)
return -1;
int freeTop = m_PageFreeTops[page] - 1;
int offset = m_PageFreeStacks[page][freeTop];
m_PageFreeTops[page] = freeTop;
m_PageAliveCounts[page]++;
if (freeTop == 0)
RemoveFreePage(page);
return MakeIndex(page, offset);
}
private int GetWritablePage()
{
while (m_FreePageTop > 0)
{
int page = m_FreePageStack[m_FreePageTop - 1];
if (IsAllocatedPage(page) && m_PageFreeTops[page] > 0)
return page;
m_FreePageTop--;
if (page >= 0 && page < m_PageFlags.Length)
m_PageFlags[page] = (byte)(m_PageFlags[page] & ~PageInFreeStack);
}
return AllocatePage();
}
private int AllocatePage()
{
int page;
if (m_ReleasedPageTop > 0)
{
page = m_ReleasedPageStack[--m_ReleasedPageTop];
}
else
{
page = m_PageCount++;
EnsurePageStorageCapacity(m_PageCount);
}
m_Pages[page] = SlotArrayPool<ObjectSlot>.Rent(PageSize);
m_PageFreeStacks[page] = SlotArrayPool<int>.Rent(PageSize);
for (int offset = 0; offset < PageSize; offset++)
m_PageFreeStacks[page][offset] = offset;
m_PageAliveCounts[page] = 0;
m_PageFreeTops[page] = PageSize;
m_PageFlags[page] = PageAllocated;
AddFreePage(page);
return page;
}
private void ReleaseSlot(int idx, bool compactStorage = true)
{
ref var slot = ref GetSlotRef(idx);
if (!slot.IsAlive()) return;
if (slot.SpawnCount > 0) return;
T obj = slot.Obj;
MarkSlotUnavailable(idx);
RemoveFromAllNameChain(idx);
m_TargetMap.Remove(obj.Target);
obj.Release(false);
m_ObjectMemoryPoolHandle.Release(obj);
slot.Obj = null;
slot.SetAlive(false);
slot.SpawnCount = 0;
slot.PrevByName = -1;
slot.NextByName = -1;
slot.PrevUnused = -1;
slot.NextUnused = -1;
int page = PageOf(idx);
int offset = OffsetOf(idx);
m_PageFreeStacks[page][m_PageFreeTops[page]++] = offset;
m_PageAliveCounts[page]--;
if (m_PageFreeTops[page] == 1)
AddFreePage(page);
if (m_PageAliveCounts[page] == 0)
AddEmptyPageCandidate(page);
if (compactStorage)
ReleaseEmptyPages(EmptyPageReleaseBudget);
}
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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int MakeIndex(int page, int offset)
{
return (page << PageBits) | offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int PageOf(int index)
{
return index >> PageBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int OffsetOf(int index)
{
return index & PageMask;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref ObjectSlot GetSlotRef(int index)
{
return ref m_Pages[index >> PageBits][index & PageMask];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsAllocatedPage(int page)
{
return page >= 0
&& page < m_PageCount
&& (m_PageFlags[page] & PageAllocated) != 0
&& m_Pages[page] != null;
}
private void EnsurePageStorageCapacity(int required)
{
if (required <= m_Pages.Length)
return;
int newCap = Math.Max(required, m_Pages.Length * 2);
var newPages = SlotArrayPool<ObjectSlot[]>.Rent(newCap);
var newPageFreeStacks = SlotArrayPool<int[]>.Rent(newCap);
var newPageAliveCounts = SlotArrayPool<int>.Rent(newCap);
var newPageFreeTops = SlotArrayPool<int>.Rent(newCap);
var newPageFlags = SlotArrayPool<byte>.Rent(newCap);
var newFreePageStack = SlotArrayPool<int>.Rent(newCap);
var newEmptyPageStack = SlotArrayPool<int>.Rent(newCap);
var newReleasedPageStack = SlotArrayPool<int>.Rent(newCap);
Array.Copy(m_Pages, 0, newPages, 0, m_PageCount);
Array.Copy(m_PageFreeStacks, 0, newPageFreeStacks, 0, m_PageCount);
Array.Copy(m_PageAliveCounts, 0, newPageAliveCounts, 0, m_PageCount);
Array.Copy(m_PageFreeTops, 0, newPageFreeTops, 0, m_PageCount);
Array.Copy(m_PageFlags, 0, newPageFlags, 0, m_PageCount);
Array.Copy(m_FreePageStack, 0, newFreePageStack, 0, m_FreePageTop);
Array.Copy(m_EmptyPageStack, 0, newEmptyPageStack, 0, m_EmptyPageTop);
Array.Copy(m_ReleasedPageStack, 0, newReleasedPageStack, 0, m_ReleasedPageTop);
SlotArrayPool<ObjectSlot[]>.Return(m_Pages, true);
SlotArrayPool<int[]>.Return(m_PageFreeStacks, true);
SlotArrayPool<int>.Return(m_PageAliveCounts, true);
SlotArrayPool<int>.Return(m_PageFreeTops, true);
SlotArrayPool<byte>.Return(m_PageFlags, true);
SlotArrayPool<int>.Return(m_FreePageStack, true);
SlotArrayPool<int>.Return(m_EmptyPageStack, true);
SlotArrayPool<int>.Return(m_ReleasedPageStack, true);
m_Pages = newPages;
m_PageFreeStacks = newPageFreeStacks;
m_PageAliveCounts = newPageAliveCounts;
m_PageFreeTops = newPageFreeTops;
m_PageFlags = newPageFlags;
m_FreePageStack = newFreePageStack;
m_EmptyPageStack = newEmptyPageStack;
m_ReleasedPageStack = newReleasedPageStack;
}
private static void EnsurePageIndexStackCapacity(ref int[] stack, int required)
{
if (required <= stack.Length)
return;
int newCap = Math.Max(required, stack.Length * 2);
var newStack = SlotArrayPool<int>.Rent(newCap);
Array.Copy(stack, 0, newStack, 0, stack.Length);
SlotArrayPool<int>.Return(stack, true);
stack = newStack;
}
private void AddFreePage(int page)
{
if ((m_PageFlags[page] & PageInFreeStack) != 0)
return;
m_PageFlags[page] = (byte)(m_PageFlags[page] | PageInFreeStack);
EnsurePageIndexStackCapacity(ref m_FreePageStack, m_FreePageTop + 1);
m_FreePageStack[m_FreePageTop++] = page;
}
private void RemoveFreePage(int page)
{
if ((m_PageFlags[page] & PageInFreeStack) == 0)
return;
m_PageFlags[page] = (byte)(m_PageFlags[page] & ~PageInFreeStack);
}
private void AddEmptyPageCandidate(int page)
{
if ((m_PageFlags[page] & PageInEmptyStack) != 0)
return;
m_PageFlags[page] = (byte)(m_PageFlags[page] | PageInEmptyStack);
EnsurePageIndexStackCapacity(ref m_EmptyPageStack, m_EmptyPageTop + 1);
m_EmptyPageStack[m_EmptyPageTop++] = page;
}
private void ReleaseEmptyPages(int budget)
{
while (budget > 0 && m_EmptyPageTop > 0)
{
int page = m_EmptyPageStack[--m_EmptyPageTop];
m_PageFlags[page] = (byte)(m_PageFlags[page] & ~PageInEmptyStack);
if (!IsAllocatedPage(page) || m_PageAliveCounts[page] != 0)
continue;
ReleasePage(page);
AddReleasedPage(page);
budget--;
}
}
private void AddReleasedPage(int page)
{
EnsurePageIndexStackCapacity(ref m_ReleasedPageStack, m_ReleasedPageTop + 1);
m_ReleasedPageStack[m_ReleasedPageTop++] = page;
}
private void ReleasePage(int page)
{
SlotArrayPool<ObjectSlot>.Return(m_Pages[page], true);
SlotArrayPool<int>.Return(m_PageFreeStacks[page], true);
m_Pages[page] = null;
m_PageFreeStacks[page] = null;
m_PageAliveCounts[page] = 0;
m_PageFreeTops[page] = 0;
m_PageFlags[page] = 0;
}
private void ReleaseAllPages()
{
for (int page = 0; page < m_PageCount; page++)
{
if (m_Pages[page] != null)
SlotArrayPool<ObjectSlot>.Return(m_Pages[page], true);
if (m_PageFreeStacks[page] != null)
SlotArrayPool<int>.Return(m_PageFreeStacks[page], true);
m_Pages[page] = null;
m_PageFreeStacks[page] = null;
m_PageAliveCounts[page] = 0;
m_PageFreeTops[page] = 0;
m_PageFlags[page] = 0;
}
}
private void ReturnPageStorage()
{
SlotArrayPool<ObjectSlot[]>.Return(m_Pages, true);
SlotArrayPool<int[]>.Return(m_PageFreeStacks, true);
SlotArrayPool<int>.Return(m_PageAliveCounts, true);
SlotArrayPool<int>.Return(m_PageFreeTops, true);
SlotArrayPool<byte>.Return(m_PageFlags, true);
SlotArrayPool<int>.Return(m_FreePageStack, true);
SlotArrayPool<int>.Return(m_EmptyPageStack, true);
SlotArrayPool<int>.Return(m_ReleasedPageStack, true);
m_Pages = null;
m_PageFreeStacks = null;
m_PageAliveCounts = null;
m_PageFreeTops = null;
m_PageFlags = null;
m_FreePageStack = null;
m_EmptyPageStack = null;
m_ReleasedPageStack = null;
}
private void RemoveFromAllNameChain(int idx)
{
ref var slot = ref GetSlotRef(idx);
string objectName = slot.Obj.Name ?? string.Empty;
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
return;
int prev = slot.PrevByName;
int next = slot.NextByName;
if (prev >= 0)
{
GetSlotRef(prev).NextByName = next;
}
else
{
if (head != idx)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain is inconsistent.");
#endif
return;
}
if (next >= 0)
m_AllNameHeadMap.AddOrUpdate(objectName, next);
else
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)
GetSlotRef(next).PrevByName = prev;
slot.PrevByName = -1;
slot.NextByName = -1;
}
private int ReleaseUnused(int maxReleaseCount, bool requireExpired, float expireThreshold)
{
int released = 0;
int current = requireExpired ? m_UnusedHead : GetBudgetScanStart();
while (current >= 0 && released < maxReleaseCount)
{
ref var slot = ref GetSlotRef(current);
int next = slot.NextUnused;
if (requireExpired && slot.LastUseTime > expireThreshold)
{
current = next;
continue;
}
if (CanReleaseSlot(ref slot))
{
ReleaseSlot(current, false);
released++;
}
current = next;
}
if (!requireExpired)
{
m_LastBudgetScanStart = current >= 0 ? current : m_UnusedHead;
}
if (released > 0)
ReleaseEmptyPages(EmptyPageReleaseBudget);
return released;
}
private void ReleaseExpired(int maxReleaseCount, float expireThreshold)
{
ReleaseUnused(maxReleaseCount, true, expireThreshold);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBudgetScanStart()
{
if (m_LastBudgetScanStart >= 0)
{
ref var slot = ref GetSlotRef(m_LastBudgetScanStart);
if (slot.IsAlive() && slot.SpawnCount == 0)
{
return m_LastBudgetScanStart;
}
}
return m_UnusedHead;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanReleaseSlot(ref ObjectSlot slot)
{
return slot.IsAlive()
&& slot.SpawnCount == 0
&& !slot.Obj.Locked
&& slot.Obj.CustomCanReleaseFlag;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsValidIndex(int index)
{
int page = index >> PageBits;
int offset = index & PageMask;
return offset < PageSize && IsAllocatedPage(page);
}
[Conditional("UNITY_EDITOR")]
private void ValidateState()
{
#if UNITY_EDITOR && ENABLE_OBJECTPOOL_VALIDATION
int aliveCount = 0;
int unusedCount = 0;
for (int page = 0; page < m_PageCount; page++)
{
ObjectSlot[] slots = m_Pages[page];
if (slots == null) continue;
for (int offset = 0; offset < PageSize; offset++)
{
int idx = MakeIndex(page, offset);
ref var slot = ref slots[offset];
if (!slot.IsAlive())
continue;
aliveCount++;
object target = slot.Obj.Target;
if (!m_TargetMap.TryGetValue(target, out int mappedIdx) || mappedIdx != idx)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' target index map is inconsistent.");
continue;
}
string objectName = slot.Obj.Name ?? string.Empty;
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name head is missing.");
continue;
}
if (slot.PrevByName < 0 && head != idx)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain head is inconsistent.");
}
if (slot.NextByName >= 0 && GetSlotRef(slot.NextByName).PrevByName != idx)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain link is inconsistent.");
}
bool inUnusedList = m_UnusedHead == idx || slot.PrevUnused >= 0 || slot.NextUnused >= 0;
if (slot.SpawnCount == 0)
{
unusedCount++;
if (!inUnusedList)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused list is inconsistent.");
}
}
else
{
if (inUnusedList)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' spawned object exists in unused list.");
}
}
}
}
if (aliveCount != m_TargetMap.Count)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' alive count is inconsistent.");
}
int walkUnusedCount = 0;
int current = m_UnusedHead;
int prevUnused = -1;
while (current >= 0)
{
ref var slot = ref GetSlotRef(current);
if (!slot.IsAlive() || slot.SpawnCount != 0)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain contains invalid slot.");
}
if (slot.PrevUnused != prevUnused)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain linkage is inconsistent.");
}
walkUnusedCount++;
prevUnused = current;
current = slot.NextUnused;
}
if (walkUnusedCount != unusedCount)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain count is inconsistent.");
}
#endif
}
private void MarkSlotAvailable(int idx)
{
AddToUnusedListTail(idx);
ref var slot = ref GetSlotRef(idx);
if (slot.IsAlive())
m_NameCursorMap.AddOrUpdate(slot.Obj.Name ?? string.Empty, idx);
}
private void MarkSlotUnavailable(int idx)
{
RemoveFromUnusedList(idx);
}
private void AddToUnusedListTail(int idx)
{
ref var slot = ref GetSlotRef(idx);
if (m_UnusedHead == idx || slot.PrevUnused >= 0 || slot.NextUnused >= 0)
return;
slot.PrevUnused = m_UnusedTail;
slot.NextUnused = -1;
if (m_UnusedTail >= 0)
GetSlotRef(m_UnusedTail).NextUnused = idx;
else
m_UnusedHead = idx;
m_UnusedTail = idx;
}
private void RemoveFromUnusedList(int idx)
{
ref var slot = ref GetSlotRef(idx);
if (m_UnusedHead != idx && slot.PrevUnused < 0 && slot.NextUnused < 0)
return;
int prev = slot.PrevUnused;
int next = slot.NextUnused;
if (prev >= 0)
GetSlotRef(prev).NextUnused = next;
else
m_UnusedHead = next;
if (next >= 0)
GetSlotRef(next).PrevUnused = prev;
else
m_UnusedTail = prev;
slot.PrevUnused = -1;
slot.NextUnused = -1;
if (m_LastBudgetScanStart == idx)
m_LastBudgetScanStart = next >= 0 ? next : m_UnusedHead;
}
private T SpawnAny(string name)
{
if (!m_AllNameHeadMap.TryGetValue(name, out int head))
return null;
float now = Time.realtimeSinceStartup;
ref var slot = ref GetSlotRef(head);
if (!slot.IsAlive() || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
return null;
SpawnSlot(head, now);
ValidateState();
return slot.Obj;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateActiveState()
{
IsActive = m_TargetMap.Count > 0 || m_PendingReleaseCount > 0;
}
}
}
}