com.alicizax.unity.framework/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs
2026-04-24 14:33:26 +08:00

913 lines
32 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 PrevAvailableByName;
public int NextAvailableByName;
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_Slots;
private int m_SlotCount;
private int m_SlotCapacity;
private int[] m_FreeStack;
private int m_FreeTop;
private IntOpenHashMap m_TargetMap;
private StringOpenHashMap m_AllNameHeadMap;
private StringOpenHashMap m_AvailableNameHeadMap;
private StringOpenHashMap m_AvailableNameTailMap;
private readonly bool m_AllowMultiSpawn;
private float m_AutoReleaseInterval;
private int m_Capacity;
private float m_ExpireTime;
private int m_Priority;
private ReleaseStrategy m_ReleaseStrategy;
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;
public ObjectPool(string name, bool allowMultiSpawn,
float autoReleaseInterval, int capacity, float expireTime, int priority, ReleaseStrategy releaseStrategy)
: base(name)
{
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
m_SlotCapacity = initCap;
m_Slots = SlotArrayPool<ObjectSlot>.Rent(initCap);
m_FreeStack = SlotArrayPool<int>.Rent(initCap);
m_TargetMap = new IntOpenHashMap(initCap);
m_AllNameHeadMap = new StringOpenHashMap(initCap);
m_AvailableNameHeadMap = new StringOpenHashMap(initCap);
m_AvailableNameTailMap = new StringOpenHashMap(initCap);
m_AllowMultiSpawn = allowMultiSpawn;
m_AutoReleaseInterval = autoReleaseInterval;
m_Capacity = capacity;
m_ExpireTime = expireTime;
m_Priority = priority;
m_ReleaseStrategy = releaseStrategy;
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;
int targetHash = obj.Target.GetHashCode();
if (m_TargetMap.TryGetValue(targetHash, out int existingIdx) && m_Slots[existingIdx].IsAlive())
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
#endif
return;
}
int idx = AllocSlot();
ref var slot = ref m_Slots[idx];
slot.Obj = obj;
slot.SpawnCount = spawned ? 1 : 0;
slot.LastUseTime = Time.realtimeSinceStartup;
slot.PrevByName = -1;
slot.NextByName = -1;
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
slot.PrevUnused = -1;
slot.NextUnused = -1;
slot.SetAlive(true);
m_TargetMap.AddOrUpdate(targetHash, idx);
string objectName = obj.Name ?? string.Empty;
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
{
m_Slots[existingHead].PrevByName = idx;
slot.NextByName = existingHead;
}
m_AllNameHeadMap.AddOrUpdate(objectName, idx);
obj.LastUseTime = slot.LastUseTime;
if (spawned)
obj.OnSpawn();
else
MarkSlotAvailable(idx);
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
ValidateState();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Spawn() => Spawn(string.Empty);
public T Spawn(string name)
{
if (name == null) return null;
if (m_AllowMultiSpawn)
return SpawnAny(name);
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return null;
ref var slot = ref m_Slots[head];
if (!slot.IsAlive() || slot.SpawnCount != 0 || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name head is inconsistent.");
#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) return false;
if (m_AllowMultiSpawn)
return m_AllNameHeadMap.ContainsKey(name);
return m_AvailableNameHeadMap.ContainsKey(name);
}
public void Unspawn(T obj)
{
if (obj == null) return;
Unspawn(obj.Target);
}
public void Unspawn(object target)
{
if (target == null) return;
int targetHash = target.GetHashCode();
if (!m_TargetMap.TryGetValue(targetHash, 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 = m_Slots[current].NextUnused;
ref var slot = ref m_Slots[current];
if (CanReleaseSlot(ref slot))
{
ReleaseSlot(current);
released++;
}
current = next;
}
if (released > 0)
{
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
ShrinkStorageIfEmpty();
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();
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();
}
private void TryProgressiveShrink()
{
m_ShrinkCounter++;
if (m_ShrinkCounter < ShrinkCheckInterval)
return;
m_ShrinkCounter = 0;
if (m_TargetMap.Count == 0 || m_SlotCapacity <= InitSlotCapacity)
return;
float usageRatio = (float)m_TargetMap.Count / m_SlotCapacity;
if (usageRatio < 0.25f)
{
int targetCapacity = Math.Max(m_SlotCapacity / 2, InitSlotCapacity);
if (targetCapacity < m_SlotCapacity)
{
var newSlots = SlotArrayPool<ObjectSlot>.Rent(targetCapacity);
var newFreeStack = SlotArrayPool<int>.Rent(targetCapacity);
Array.Copy(m_Slots, 0, newSlots, 0, Math.Min(m_SlotCount, targetCapacity));
Array.Copy(m_FreeStack, 0, newFreeStack, 0, Math.Min(m_FreeTop, targetCapacity));
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true);
m_Slots = newSlots;
m_FreeStack = newFreeStack;
m_SlotCapacity = targetCapacity;
}
}
}
internal override void Shutdown()
{
m_IsShuttingDown = true;
for (int i = 0; i < m_SlotCount; i++)
{
ref var slot = ref m_Slots[i];
if (!slot.IsAlive()) continue;
slot.Obj.Release(true);
MemoryPool.Release(slot.Obj);
slot.Obj = null;
slot.SetAlive(false);
}
m_TargetMap.Clear();
m_AllNameHeadMap.Clear();
m_AvailableNameHeadMap.Clear();
m_AvailableNameTailMap.Clear();
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true);
m_Slots = null;
m_FreeStack = null;
m_SlotCount = 0;
m_SlotCapacity = 0;
m_FreeTop = 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 i = 0; i < m_SlotCount; i++)
{
ref var slot = ref m_Slots[i];
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 m_Slots[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 m_Slots[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()
{
if (m_FreeTop > 0)
return m_FreeStack[--m_FreeTop];
if (m_SlotCount >= m_Slots.Length)
GrowSlots();
return m_SlotCount++;
}
private void GrowSlots()
{
int newCap = Math.Max(m_SlotCapacity * 2, InitSlotCapacity);
var newSlots = SlotArrayPool<ObjectSlot>.Rent(newCap);
var newFreeStack = SlotArrayPool<int>.Rent(newCap);
Array.Copy(m_Slots, 0, newSlots, 0, m_SlotCount);
Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop);
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true);
m_Slots = newSlots;
m_FreeStack = newFreeStack;
m_SlotCapacity = newCap;
}
private void ReleaseSlot(int idx)
{
ref var slot = ref m_Slots[idx];
if (!slot.IsAlive()) return;
T obj = slot.Obj;
if (slot.SpawnCount == 0)
MarkSlotUnavailable(idx);
RemoveFromAllNameChain(idx);
int targetHash = obj.Target.GetHashCode();
m_TargetMap.Remove(targetHash);
obj.Release(false);
MemoryPool.Release(obj);
slot.Obj = null;
slot.SetAlive(false);
slot.SpawnCount = 0;
slot.PrevByName = -1;
slot.NextByName = -1;
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
slot.PrevUnused = -1;
slot.NextUnused = -1;
if (m_FreeTop >= m_SlotCapacity)
{
int newCap = m_SlotCapacity * 2;
var newFreeStack = SlotArrayPool<int>.Rent(newCap);
Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop);
SlotArrayPool<int>.Return(m_FreeStack, true);
m_FreeStack = newFreeStack;
m_SlotCapacity = newCap;
}
m_FreeStack[m_FreeTop++] = idx;
ShrinkStorageIfEmpty();
}
private void RemoveFromAllNameChain(int idx)
{
ref var slot = ref m_Slots[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)
{
m_Slots[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 (next >= 0)
m_Slots[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 m_Slots[current];
int next = slot.NextUnused;
if (requireExpired && slot.LastUseTime > expireThreshold)
{
break;
}
if (CanReleaseSlot(ref slot))
{
ReleaseSlot(current);
released++;
}
current = next;
}
if (!requireExpired)
{
m_LastBudgetScanStart = current >= 0 ? current : m_UnusedHead;
}
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 m_Slots[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;
}
private void ShrinkStorageIfEmpty()
{
if (m_TargetMap.Count > 0 || m_SlotCapacity <= InitSlotCapacity)
return;
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true);
m_SlotCapacity = InitSlotCapacity;
m_Slots = SlotArrayPool<ObjectSlot>.Rent(InitSlotCapacity);
m_FreeStack = SlotArrayPool<int>.Rent(InitSlotCapacity);
m_AllNameHeadMap.Clear();
m_AvailableNameHeadMap.Clear();
m_AvailableNameTailMap.Clear();
m_SlotCount = 0;
m_FreeTop = 0;
m_UnusedHead = -1;
m_UnusedTail = -1;
m_LastBudgetScanStart = -1;
}
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
private void ValidateState()
{
#if !ENABLE_OBJECTPOOL_VALIDATION
return;
#else
int aliveCount = 0;
int unusedCount = 0;
for (int i = 0; i < m_SlotCount; i++)
{
ref var slot = ref m_Slots[i];
if (!slot.IsAlive())
continue;
aliveCount++;
object target = slot.Obj.Target;
int targetHash = target.GetHashCode();
if (!m_TargetMap.TryGetValue(targetHash, out int mappedIdx) || mappedIdx != i)
{
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 != i)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain head is inconsistent.");
}
if (slot.NextByName >= 0 && m_Slots[slot.NextByName].PrevByName != i)
{
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain link is inconsistent.");
}
bool inUnusedList = m_UnusedHead == i || slot.PrevUnused >= 0 || slot.NextUnused >= 0;
bool inAvailableList = false;
if (slot.SpawnCount == 0)
{
unusedCount++;
if (!inUnusedList)
{
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
{
if (inUnusedList)
{
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.");
}
}
}
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 m_Slots[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);
AddToAvailableNameChain(idx);
}
private void MarkSlotUnavailable(int idx)
{
RemoveFromUnusedList(idx);
RemoveFromAvailableNameChain(idx);
}
private void AddToUnusedListTail(int idx)
{
ref var slot = ref m_Slots[idx];
if (m_UnusedHead == idx || slot.PrevUnused >= 0 || slot.NextUnused >= 0)
return;
slot.PrevUnused = m_UnusedTail;
slot.NextUnused = -1;
if (m_UnusedTail >= 0)
m_Slots[m_UnusedTail].NextUnused = idx;
else
m_UnusedHead = idx;
m_UnusedTail = idx;
}
private void RemoveFromUnusedList(int idx)
{
ref var slot = ref m_Slots[idx];
if (m_UnusedHead != idx && slot.PrevUnused < 0 && slot.NextUnused < 0)
return;
int prev = slot.PrevUnused;
int next = slot.NextUnused;
if (prev >= 0)
m_Slots[prev].NextUnused = next;
else
m_UnusedHead = next;
if (next >= 0)
m_Slots[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 void AddToAvailableNameChain(int idx)
{
ref var slot = ref m_Slots[idx];
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name chain is inconsistent.");
#endif
return;
}
string objectName = slot.Obj.Name ?? string.Empty;
if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail))
{
m_Slots[tail].NextAvailableByName = idx;
slot.PrevAvailableByName = tail;
slot.NextAvailableByName = -1;
m_AvailableNameTailMap.AddOrUpdate(objectName, idx);
}
else
{
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
m_AvailableNameHeadMap.AddOrUpdate(objectName, idx);
m_AvailableNameTailMap.AddOrUpdate(objectName, idx);
}
}
private void RemoveFromAvailableNameChain(int idx)
{
ref var slot = ref m_Slots[idx];
string objectName = slot.Obj.Name ?? string.Empty;
if (slot.PrevAvailableByName < 0
&& slot.NextAvailableByName < 0
&& (!m_AvailableNameHeadMap.TryGetValue(objectName, out int head) || head != idx))
{
return;
}
int prev = slot.PrevAvailableByName;
int next = slot.NextAvailableByName;
if (prev >= 0)
m_Slots[prev].NextAvailableByName = next;
else if (next >= 0)
m_AvailableNameHeadMap.AddOrUpdate(objectName, next);
else
m_AvailableNameHeadMap.Remove(objectName);
if (next >= 0)
m_Slots[next].PrevAvailableByName = prev;
else if (prev >= 0)
m_AvailableNameTailMap.AddOrUpdate(objectName, prev);
else
m_AvailableNameTailMap.Remove(objectName);
slot.PrevAvailableByName = -1;
slot.NextAvailableByName = -1;
}
private T SpawnAny(string name)
{
if (!m_AllNameHeadMap.TryGetValue(name, out int head))
return null;
float now = Time.realtimeSinceStartup;
ref var slot = ref m_Slots[head];
if (!slot.IsAlive() || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
return null;
SpawnSlot(head, now);
ValidateState();
return slot.Obj;
}
}
}
}