2026-03-26 16:14:05 +08:00
|
|
|
using System;
|
2025-10-11 15:18:09 +08:00
|
|
|
using System.Collections.Generic;
|
2026-04-23 17:50:53 +08:00
|
|
|
using System.Diagnostics;
|
2026-04-22 13:04:31 +08:00
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
using UnityEngine;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
|
|
|
|
namespace AlicizaX.ObjectPool
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
internal sealed partial class ObjectPoolService
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
|
|
|
|
private sealed class ObjectPool<T> : ObjectPoolBase, IObjectPool<T> where T : ObjectBase
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
private struct ObjectSlot
|
|
|
|
|
{
|
|
|
|
|
public T Obj;
|
|
|
|
|
public int SpawnCount;
|
|
|
|
|
public float LastUseTime;
|
2026-04-23 17:50:53 +08:00
|
|
|
public int PrevByName;
|
|
|
|
|
public int NextByName;
|
|
|
|
|
public int PrevAvailableByName;
|
|
|
|
|
public int NextAvailableByName;
|
2026-04-22 13:04:31 +08:00
|
|
|
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_FreeStack;
|
|
|
|
|
private int m_FreeTop;
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<object, int> m_TargetMap;
|
2026-04-23 17:50:53 +08:00
|
|
|
private readonly Dictionary<string, int> m_AllNameHeadMap;
|
|
|
|
|
private readonly Dictionary<string, int> m_AvailableNameHeadMap;
|
|
|
|
|
private readonly Dictionary<string, int> m_AvailableNameTailMap;
|
2026-04-22 13:04:31 +08:00
|
|
|
|
2025-10-11 15:18:09 +08:00
|
|
|
private readonly bool m_AllowMultiSpawn;
|
|
|
|
|
private float m_AutoReleaseInterval;
|
|
|
|
|
private int m_Capacity;
|
|
|
|
|
private float m_ExpireTime;
|
|
|
|
|
private int m_Priority;
|
|
|
|
|
private float m_AutoReleaseTime;
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
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 const int DefaultReleasePerFrame = 8;
|
|
|
|
|
private const int InitSlotCapacity = 16;
|
|
|
|
|
|
|
|
|
|
public ObjectPool(string name, bool allowMultiSpawn,
|
|
|
|
|
float autoReleaseInterval, int capacity, float expireTime, int priority)
|
2025-10-11 15:18:09 +08:00
|
|
|
: base(name)
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
|
|
|
|
|
m_Slots = new ObjectSlot[initCap];
|
|
|
|
|
m_FreeStack = new int[initCap];
|
|
|
|
|
m_TargetMap = new Dictionary<object, int>(initCap, AlicizaX.ReferenceComparer<object>.Instance);
|
2026-04-23 17:50:53 +08:00
|
|
|
m_AllNameHeadMap = new Dictionary<string, int>(initCap, StringComparer.Ordinal);
|
|
|
|
|
m_AvailableNameHeadMap = new Dictionary<string, int>(initCap, StringComparer.Ordinal);
|
|
|
|
|
m_AvailableNameTailMap = new Dictionary<string, int>(initCap, StringComparer.Ordinal);
|
2025-10-11 15:18:09 +08:00
|
|
|
m_AllowMultiSpawn = allowMultiSpawn;
|
|
|
|
|
m_AutoReleaseInterval = autoReleaseInterval;
|
2026-04-22 13:04:31 +08:00
|
|
|
m_Capacity = capacity;
|
|
|
|
|
m_ExpireTime = expireTime;
|
2025-10-11 15:18:09 +08:00
|
|
|
m_Priority = priority;
|
|
|
|
|
m_AutoReleaseTime = 0f;
|
2026-04-22 13:04:31 +08:00
|
|
|
m_PendingReleaseCount = 0;
|
|
|
|
|
m_ReleasePerFrameBudget = DefaultReleasePerFrame;
|
|
|
|
|
m_UnusedHead = -1;
|
|
|
|
|
m_UnusedTail = -1;
|
|
|
|
|
m_LastBudgetScanStart = -1;
|
|
|
|
|
m_IsShuttingDown = false;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public override Type ObjectType => typeof(T);
|
|
|
|
|
public override int Count => m_TargetMap.Count;
|
|
|
|
|
public override bool AllowMultiSpawn => m_AllowMultiSpawn;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public override float AutoReleaseInterval
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
get => m_AutoReleaseInterval;
|
|
|
|
|
set
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (value < 0f) throw new GameFrameworkException("AutoReleaseInterval is invalid.");
|
|
|
|
|
m_AutoReleaseInterval = value;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int Capacity
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
get => m_Capacity;
|
2025-10-11 15:18:09 +08:00
|
|
|
set
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (value < 0) throw new GameFrameworkException("Capacity is invalid.");
|
2025-10-11 15:18:09 +08:00
|
|
|
m_Capacity = value;
|
2026-04-22 13:04:31 +08:00
|
|
|
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override float ExpireTime
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
get => m_ExpireTime;
|
2025-10-11 15:18:09 +08:00
|
|
|
set
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (value < 0f) throw new GameFrameworkException("ExpireTime is invalid.");
|
2025-10-11 15:18:09 +08:00
|
|
|
m_ExpireTime = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int Priority
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
get => m_Priority;
|
|
|
|
|
set => m_Priority = value;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public override int ReleasePerFrameBudget
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
get => m_ReleasePerFrameBudget;
|
|
|
|
|
set => m_ReleasePerFrameBudget = Math.Max(1, value);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public void Register(T obj, bool spawned)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (obj == null) throw new GameFrameworkException("Object is invalid.");
|
|
|
|
|
if (obj.Target == null) throw new GameFrameworkException("Object target is invalid.");
|
|
|
|
|
if (m_TargetMap.TryGetValue(obj.Target, out int existingIdx) && m_Slots[existingIdx].IsAlive())
|
|
|
|
|
throw new GameFrameworkException(
|
|
|
|
|
$"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
|
|
|
|
|
|
|
|
|
|
int idx = AllocSlot();
|
|
|
|
|
ref var slot = ref m_Slots[idx];
|
|
|
|
|
slot.Obj = obj;
|
|
|
|
|
slot.SpawnCount = spawned ? 1 : 0;
|
|
|
|
|
slot.LastUseTime = Time.realtimeSinceStartup;
|
2026-04-23 17:50:53 +08:00
|
|
|
slot.PrevByName = -1;
|
|
|
|
|
slot.NextByName = -1;
|
|
|
|
|
slot.PrevAvailableByName = -1;
|
|
|
|
|
slot.NextAvailableByName = -1;
|
2026-04-22 13:04:31 +08:00
|
|
|
slot.PrevUnused = -1;
|
|
|
|
|
slot.NextUnused = -1;
|
|
|
|
|
slot.SetAlive(true);
|
|
|
|
|
|
|
|
|
|
m_TargetMap[obj.Target] = idx;
|
|
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
string objectName = obj.Name ?? string.Empty;
|
|
|
|
|
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
|
|
|
|
|
{
|
|
|
|
|
m_Slots[existingHead].PrevByName = idx;
|
|
|
|
|
slot.NextByName = existingHead;
|
|
|
|
|
}
|
|
|
|
|
m_AllNameHeadMap[objectName] = idx;
|
2026-04-22 13:04:31 +08:00
|
|
|
|
|
|
|
|
obj.LastUseTime = slot.LastUseTime;
|
|
|
|
|
if (spawned)
|
|
|
|
|
obj.OnSpawn();
|
|
|
|
|
else
|
2026-04-23 17:50:53 +08:00
|
|
|
MarkSlotAvailable(idx);
|
2026-04-22 13:04:31 +08:00
|
|
|
|
|
|
|
|
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
|
2026-04-23 17:50:53 +08:00
|
|
|
ValidateState();
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
public T Spawn() => Spawn(string.Empty);
|
|
|
|
|
|
|
|
|
|
public T Spawn(string name)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (name == null) throw new GameFrameworkException("Name is invalid.");
|
2026-04-23 17:50:53 +08:00
|
|
|
if (m_AllowMultiSpawn)
|
|
|
|
|
return SpawnAny(name);
|
|
|
|
|
|
|
|
|
|
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return null;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
float now = Time.realtimeSinceStartup;
|
|
|
|
|
int current = head;
|
|
|
|
|
while (current >= 0)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[current];
|
|
|
|
|
if (slot.IsAlive() && string.Equals(slot.Obj.Name, name)
|
2026-04-23 17:50:53 +08:00
|
|
|
&& slot.SpawnCount == 0)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
SpawnSlot(current, now);
|
2026-04-23 17:50:53 +08:00
|
|
|
ValidateState();
|
2026-04-22 13:04:31 +08:00
|
|
|
return slot.Obj;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
2026-04-22 13:04:31 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
current = slot.NextAvailableByName;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
return null;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
public bool CanSpawn() => CanSpawn(string.Empty);
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public bool CanSpawn(string name)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (name == null) throw new GameFrameworkException("Name is invalid.");
|
2026-04-23 17:50:53 +08:00
|
|
|
if (m_AllowMultiSpawn)
|
|
|
|
|
return m_AllNameHeadMap.ContainsKey(name);
|
|
|
|
|
|
|
|
|
|
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return false;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
int current = head;
|
|
|
|
|
while (current >= 0)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[current];
|
2026-04-23 17:50:53 +08:00
|
|
|
if (slot.IsAlive() && slot.SpawnCount == 0 && string.Equals(slot.Obj.Name, name))
|
2026-04-22 13:04:31 +08:00
|
|
|
return true;
|
2026-04-23 17:50:53 +08:00
|
|
|
current = slot.NextAvailableByName;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
return false;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Unspawn(T obj)
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (obj == null) throw new GameFrameworkException("Object is invalid.");
|
2025-10-11 15:18:09 +08:00
|
|
|
Unspawn(obj.Target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Unspawn(object target)
|
|
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (target == null) throw new GameFrameworkException("Target is invalid.");
|
|
|
|
|
if (!m_TargetMap.TryGetValue(target, out int idx))
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (m_IsShuttingDown) return;
|
|
|
|
|
throw new GameFrameworkException(
|
|
|
|
|
$"Cannot find target in pool '{Name}', type='{target.GetType().FullName}'");
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
UnspawnSlot(idx);
|
2026-04-23 17:50:53 +08:00
|
|
|
ValidateState();
|
2026-04-22 13:04:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
int next = m_Slots[current].NextUnused;
|
|
|
|
|
ref var slot = ref m_Slots[current];
|
|
|
|
|
if (CanReleaseSlot(ref slot))
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ReleaseSlot(current);
|
|
|
|
|
released++;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
2026-04-22 13:04:31 +08:00
|
|
|
current = next;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (released > 0)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
|
|
|
|
|
ShrinkStorageIfEmpty();
|
2026-04-23 17:50:53 +08:00
|
|
|
ValidateState();
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
internal override void Update(float elapseSeconds, float realElapseSeconds)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
m_AutoReleaseTime += realElapseSeconds;
|
|
|
|
|
if (m_AutoReleaseTime >= m_AutoReleaseInterval)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
m_AutoReleaseTime = 0f;
|
|
|
|
|
MarkRelease(Count - m_Capacity);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
bool checkExpire = m_ExpireTime < float.MaxValue;
|
|
|
|
|
if (m_PendingReleaseCount <= 0 && !checkExpire) return;
|
|
|
|
|
|
|
|
|
|
float now = Time.realtimeSinceStartup;
|
|
|
|
|
float expireThreshold = checkExpire ? now - m_ExpireTime : float.MinValue;
|
|
|
|
|
if (m_PendingReleaseCount > 0)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
int releaseBudget = Math.Min(m_ReleasePerFrameBudget, m_PendingReleaseCount);
|
|
|
|
|
int releasedByBudget = ReleaseUnused(releaseBudget, false, float.MinValue);
|
|
|
|
|
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - releasedByBudget);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
2026-04-22 13:04:31 +08:00
|
|
|
else if (checkExpire)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ReleaseExpired(m_ReleasePerFrameBudget, expireThreshold);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
internal override void Shutdown()
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
m_IsShuttingDown = true;
|
|
|
|
|
for (int i = 0; i < m_SlotCount; i++)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
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);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
m_TargetMap.Clear();
|
2026-04-23 17:50:53 +08:00
|
|
|
m_AllNameHeadMap.Clear();
|
|
|
|
|
m_AvailableNameHeadMap.Clear();
|
|
|
|
|
m_AvailableNameTailMap.Clear();
|
2026-04-22 13:04:31 +08:00
|
|
|
m_SlotCount = 0;
|
|
|
|
|
m_FreeTop = 0;
|
|
|
|
|
m_PendingReleaseCount = 0;
|
|
|
|
|
m_UnusedHead = -1;
|
|
|
|
|
m_UnusedTail = -1;
|
|
|
|
|
m_LastBudgetScanStart = -1;
|
|
|
|
|
m_IsShuttingDown = false;
|
2026-04-23 17:50:53 +08:00
|
|
|
ValidateState();
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public override ObjectInfo[] GetAllObjectInfos()
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
var list = new ObjectInfo[m_TargetMap.Count];
|
|
|
|
|
int write = 0;
|
|
|
|
|
for (int i = 0; i < m_SlotCount && write < list.Length; i++)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[i];
|
|
|
|
|
if (!slot.IsAlive()) continue;
|
|
|
|
|
list[write++] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
|
|
|
|
|
slot.Obj.CustomCanReleaseFlag,
|
|
|
|
|
slot.Obj.LastUseTime, slot.SpawnCount);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
return list;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
public override void GetAllObjectInfos(List<ObjectInfo> results)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (results == null) throw new GameFrameworkException("Results is invalid.");
|
|
|
|
|
results.Clear();
|
|
|
|
|
for (int i = 0; i < m_SlotCount; i++)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[i];
|
|
|
|
|
if (!slot.IsAlive()) continue;
|
|
|
|
|
results.Add(new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
|
|
|
|
|
slot.Obj.CustomCanReleaseFlag,
|
|
|
|
|
slot.Obj.LastUseTime, slot.SpawnCount));
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
2026-04-22 13:04:31 +08:00
|
|
|
}
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
internal override void OnLowMemory()
|
|
|
|
|
{
|
|
|
|
|
ReleaseAllUnused();
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private void SpawnSlot(int idx, float now)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[idx];
|
|
|
|
|
if (slot.SpawnCount == 0)
|
2026-04-23 17:50:53 +08:00
|
|
|
MarkSlotUnavailable(idx);
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
slot.SpawnCount++;
|
|
|
|
|
slot.LastUseTime = now;
|
|
|
|
|
slot.Obj.LastUseTime = now;
|
|
|
|
|
slot.Obj.OnSpawn();
|
|
|
|
|
}
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[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)
|
|
|
|
|
throw new GameFrameworkException($"Object '{slot.Obj.Name}' spawn count < 0.");
|
|
|
|
|
if (slot.SpawnCount == 0)
|
2026-04-23 17:50:53 +08:00
|
|
|
MarkSlotAvailable(idx);
|
2026-04-22 13:04:31 +08:00
|
|
|
if (Count > m_Capacity && slot.SpawnCount == 0)
|
|
|
|
|
MarkRelease(Count - m_Capacity);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private void MarkRelease(int count)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (count > 0)
|
|
|
|
|
m_PendingReleaseCount = Math.Max(m_PendingReleaseCount, count);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private int AllocSlot()
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (m_FreeTop > 0)
|
|
|
|
|
return m_FreeStack[--m_FreeTop];
|
|
|
|
|
|
|
|
|
|
if (m_SlotCount >= m_Slots.Length)
|
|
|
|
|
GrowSlots();
|
|
|
|
|
|
|
|
|
|
return m_SlotCount++;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
private void GrowSlots()
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
int newCap = Math.Max(m_Slots.Length * 2, InitSlotCapacity);
|
|
|
|
|
Array.Resize(ref m_Slots, newCap);
|
|
|
|
|
Array.Resize(ref m_FreeStack, newCap);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
private void ReleaseSlot(int idx)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[idx];
|
|
|
|
|
if (!slot.IsAlive()) return;
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
T obj = slot.Obj;
|
|
|
|
|
if (slot.SpawnCount == 0)
|
2026-04-23 17:50:53 +08:00
|
|
|
MarkSlotUnavailable(idx);
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
RemoveFromAllNameChain(idx);
|
2026-04-22 13:04:31 +08:00
|
|
|
m_TargetMap.Remove(obj.Target);
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
obj.Release(false);
|
|
|
|
|
MemoryPool.Release(obj);
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
slot.Obj = null;
|
|
|
|
|
slot.SetAlive(false);
|
|
|
|
|
slot.SpawnCount = 0;
|
2026-04-23 17:50:53 +08:00
|
|
|
slot.PrevByName = -1;
|
|
|
|
|
slot.NextByName = -1;
|
|
|
|
|
slot.PrevAvailableByName = -1;
|
|
|
|
|
slot.NextAvailableByName = -1;
|
2026-04-22 13:04:31 +08:00
|
|
|
slot.PrevUnused = -1;
|
|
|
|
|
slot.NextUnused = -1;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (m_FreeTop >= m_FreeStack.Length)
|
|
|
|
|
Array.Resize(ref m_FreeStack, m_FreeStack.Length * 2);
|
|
|
|
|
m_FreeStack[m_FreeTop++] = idx;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
ShrinkStorageIfEmpty();
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
private void RemoveFromAllNameChain(int idx)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[idx];
|
2026-04-23 17:50:53 +08:00
|
|
|
string objectName = slot.Obj.Name ?? string.Empty;
|
|
|
|
|
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
|
|
|
|
return;
|
2026-03-26 17:53:07 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
int prev = slot.PrevByName;
|
|
|
|
|
int next = slot.NextByName;
|
|
|
|
|
if (prev >= 0)
|
2026-03-26 17:53:07 +08:00
|
|
|
{
|
2026-04-23 17:50:53 +08:00
|
|
|
m_Slots[prev].NextByName = next;
|
2026-03-26 17:53:07 +08:00
|
|
|
}
|
2026-04-22 13:04:31 +08:00
|
|
|
else
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-23 17:50:53 +08:00
|
|
|
if (head != idx)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' all-name chain is inconsistent.");
|
|
|
|
|
|
|
|
|
|
if (next >= 0)
|
|
|
|
|
m_AllNameHeadMap[objectName] = next;
|
|
|
|
|
else
|
|
|
|
|
m_AllNameHeadMap.Remove(objectName);
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
if (next >= 0)
|
|
|
|
|
m_Slots[next].PrevByName = prev;
|
|
|
|
|
|
|
|
|
|
slot.PrevByName = -1;
|
|
|
|
|
slot.NextByName = -1;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
private int ReleaseUnused(int maxReleaseCount, bool requireExpired, float expireThreshold)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
int released = 0;
|
|
|
|
|
int current = requireExpired ? m_UnusedHead : GetBudgetScanStart();
|
|
|
|
|
|
|
|
|
|
while (current >= 0 && released < maxReleaseCount)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[current];
|
|
|
|
|
int next = slot.NextUnused;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (requireExpired && slot.LastUseTime > expireThreshold)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (CanReleaseSlot(ref slot))
|
|
|
|
|
{
|
|
|
|
|
ReleaseSlot(current);
|
|
|
|
|
released++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current = next;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (!requireExpired)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
m_LastBudgetScanStart = current >= 0 ? current : m_UnusedHead;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
return released;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
private void ReleaseExpired(int maxReleaseCount, float expireThreshold)
|
2026-03-23 20:50:11 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ReleaseUnused(maxReleaseCount, true, expireThreshold);
|
|
|
|
|
}
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private int GetBudgetScanStart()
|
|
|
|
|
{
|
|
|
|
|
if (m_LastBudgetScanStart >= 0)
|
2026-03-23 20:50:11 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[m_LastBudgetScanStart];
|
|
|
|
|
if (slot.IsAlive() && slot.SpawnCount == 0)
|
|
|
|
|
{
|
|
|
|
|
return m_LastBudgetScanStart;
|
|
|
|
|
}
|
2026-03-23 20:50:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
return m_UnusedHead;
|
|
|
|
|
}
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private static bool CanReleaseSlot(ref ObjectSlot slot)
|
|
|
|
|
{
|
|
|
|
|
return slot.IsAlive()
|
|
|
|
|
&& slot.SpawnCount == 0
|
|
|
|
|
&& !slot.Obj.Locked
|
|
|
|
|
&& slot.Obj.CustomCanReleaseFlag;
|
2026-03-23 20:50:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
private void ShrinkStorageIfEmpty()
|
2026-03-23 20:50:11 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
if (m_TargetMap.Count > 0 || m_Slots.Length <= InitSlotCapacity)
|
|
|
|
|
return;
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
m_Slots = new ObjectSlot[InitSlotCapacity];
|
|
|
|
|
m_FreeStack = new int[InitSlotCapacity];
|
2026-04-23 17:50:53 +08:00
|
|
|
m_AllNameHeadMap.Clear();
|
|
|
|
|
m_AvailableNameHeadMap.Clear();
|
|
|
|
|
m_AvailableNameTailMap.Clear();
|
2026-04-22 13:04:31 +08:00
|
|
|
m_SlotCount = 0;
|
|
|
|
|
m_FreeTop = 0;
|
|
|
|
|
m_UnusedHead = -1;
|
|
|
|
|
m_UnusedTail = -1;
|
|
|
|
|
m_LastBudgetScanStart = -1;
|
|
|
|
|
}
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
|
|
|
|
|
private void ValidateState()
|
2026-04-22 13:04:31 +08:00
|
|
|
{
|
2026-04-23 17:50:53 +08:00
|
|
|
int aliveCount = 0;
|
|
|
|
|
int unusedCount = 0;
|
|
|
|
|
for (int i = 0; i < m_SlotCount; i++)
|
2026-03-23 20:50:11 +08:00
|
|
|
{
|
2026-04-23 17:50:53 +08:00
|
|
|
ref var slot = ref m_Slots[i];
|
|
|
|
|
if (!slot.IsAlive())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
aliveCount++;
|
|
|
|
|
|
|
|
|
|
object target = slot.Obj.Target;
|
|
|
|
|
if (!m_TargetMap.TryGetValue(target, out int mappedIdx) || mappedIdx != i)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' target index map is inconsistent.");
|
|
|
|
|
|
|
|
|
|
string objectName = slot.Obj.Name ?? string.Empty;
|
|
|
|
|
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' all-name head is missing.");
|
|
|
|
|
|
|
|
|
|
if (slot.PrevByName < 0 && head != i)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' all-name chain head is inconsistent.");
|
|
|
|
|
|
|
|
|
|
if (slot.NextByName >= 0 && m_Slots[slot.NextByName].PrevByName != i)
|
|
|
|
|
throw new GameFrameworkException($"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)
|
2026-03-23 20:50:11 +08:00
|
|
|
{
|
2026-04-23 17:50:53 +08:00
|
|
|
unusedCount++;
|
|
|
|
|
if (!inUnusedList)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' unused list is inconsistent.");
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
if (!m_AvailableNameHeadMap.TryGetValue(objectName, out int availableHead))
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' available-name head is missing.");
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
inAvailableList = availableHead == i || slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0;
|
|
|
|
|
if (!inAvailableList)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' available-name chain is inconsistent.");
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
if (slot.NextAvailableByName >= 0 && m_Slots[slot.NextAvailableByName].PrevAvailableByName != i)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' available-name link is inconsistent.");
|
|
|
|
|
}
|
2026-04-22 13:04:31 +08:00
|
|
|
else
|
2026-04-23 17:50:53 +08:00
|
|
|
{
|
|
|
|
|
if (inUnusedList)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' spawned object exists in unused list.");
|
2026-04-22 13:04:31 +08:00
|
|
|
|
2026-04-23 17:50:53 +08:00
|
|
|
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' spawned object exists in available chain.");
|
|
|
|
|
}
|
2026-03-23 20:50:11 +08:00
|
|
|
}
|
2026-04-23 17:50:53 +08:00
|
|
|
|
|
|
|
|
if (aliveCount != m_TargetMap.Count)
|
|
|
|
|
throw new GameFrameworkException($"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)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' unused chain contains invalid slot.");
|
|
|
|
|
if (slot.PrevUnused != prevUnused)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' unused chain linkage is inconsistent.");
|
|
|
|
|
|
|
|
|
|
walkUnusedCount++;
|
|
|
|
|
prevUnused = current;
|
|
|
|
|
current = slot.NextUnused;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (walkUnusedCount != unusedCount)
|
|
|
|
|
throw new GameFrameworkException($"Object pool '{FullName}' unused chain count is inconsistent.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2026-03-23 20:50:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
private void RemoveFromUnusedList(int idx)
|
2025-10-11 15:18:09 +08:00
|
|
|
{
|
2026-04-22 13:04:31 +08:00
|
|
|
ref var slot = ref m_Slots[idx];
|
|
|
|
|
if (m_UnusedHead != idx && slot.PrevUnused < 0 && slot.NextUnused < 0)
|
|
|
|
|
return;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
int prev = slot.PrevUnused;
|
|
|
|
|
int next = slot.NextUnused;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (prev >= 0)
|
|
|
|
|
m_Slots[prev].NextUnused = next;
|
|
|
|
|
else
|
|
|
|
|
m_UnusedHead = next;
|
2025-10-11 15:18:09 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (next >= 0)
|
|
|
|
|
m_Slots[next].PrevUnused = prev;
|
|
|
|
|
else
|
|
|
|
|
m_UnusedTail = prev;
|
|
|
|
|
|
|
|
|
|
slot.PrevUnused = -1;
|
|
|
|
|
slot.NextUnused = -1;
|
2026-03-23 20:50:11 +08:00
|
|
|
|
2026-04-22 13:04:31 +08:00
|
|
|
if (m_LastBudgetScanStart == idx)
|
|
|
|
|
m_LastBudgetScanStart = next >= 0 ? next : m_UnusedHead;
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
2026-04-23 17:50:53 +08:00
|
|
|
|
|
|
|
|
private void AddToAvailableNameChain(int idx)
|
|
|
|
|
{
|
|
|
|
|
ref var slot = ref m_Slots[idx];
|
|
|
|
|
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
|
|
|
|
|
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[objectName] = idx;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
slot.PrevAvailableByName = -1;
|
|
|
|
|
slot.NextAvailableByName = -1;
|
|
|
|
|
m_AvailableNameHeadMap[objectName] = idx;
|
|
|
|
|
m_AvailableNameTailMap[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[objectName] = next;
|
|
|
|
|
else
|
|
|
|
|
m_AvailableNameHeadMap.Remove(objectName);
|
|
|
|
|
|
|
|
|
|
if (next >= 0)
|
|
|
|
|
m_Slots[next].PrevAvailableByName = prev;
|
|
|
|
|
else if (prev >= 0)
|
|
|
|
|
m_AvailableNameTailMap[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);
|
|
|
|
|
return slot.Obj;
|
|
|
|
|
}
|
2025-10-11 15:18:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|