优化ObjectPool
This commit is contained in:
parent
ecd9dfc970
commit
4d73c62440
@ -28,11 +28,6 @@ namespace AlicizaX
|
|||||||
/// <param name="name">名称。</param>
|
/// <param name="name">名称。</param>
|
||||||
public TypeNamePair(Type type, string name)
|
public TypeNamePair(Type type, string name)
|
||||||
{
|
{
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
throw new GameFrameworkException("Type is invalid.");
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Type = type;
|
m_Type = type;
|
||||||
m_Name = name ?? string.Empty;
|
m_Name = name ?? string.Empty;
|
||||||
}
|
}
|
||||||
@ -61,11 +56,21 @@ namespace AlicizaX
|
|||||||
{
|
{
|
||||||
if (m_Type == null)
|
if (m_Type == null)
|
||||||
{
|
{
|
||||||
throw new GameFrameworkException("Type is invalid.");
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
string typeName = m_Type.FullName;
|
string typeName = m_Type.FullName;
|
||||||
return (string.IsNullOrEmpty(m_Name) ? typeName : Utility.Text.Format("{0}.{1}", typeName, m_Name)) ?? string.Empty;
|
if (string.IsNullOrEmpty(m_Name))
|
||||||
|
return typeName ?? string.Empty;
|
||||||
|
|
||||||
|
// 使用 ZString 避免字符串分配
|
||||||
|
using (var sb = Cysharp.Text.ZString.CreateStringBuilder())
|
||||||
|
{
|
||||||
|
sb.Append(typeName);
|
||||||
|
sb.Append('.');
|
||||||
|
sb.Append(m_Name);
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
public readonly int? Capacity;
|
public readonly int? Capacity;
|
||||||
public readonly float? ExpireTime;
|
public readonly float? ExpireTime;
|
||||||
public readonly int Priority;
|
public readonly int Priority;
|
||||||
|
public readonly ReleaseStrategy ReleaseStrategy;
|
||||||
|
|
||||||
public ObjectPoolCreateOptions(
|
public ObjectPoolCreateOptions(
|
||||||
string name = "",
|
string name = "",
|
||||||
@ -16,7 +17,8 @@ namespace AlicizaX.ObjectPool
|
|||||||
float? autoReleaseInterval = null,
|
float? autoReleaseInterval = null,
|
||||||
int? capacity = null,
|
int? capacity = null,
|
||||||
float? expireTime = null,
|
float? expireTime = null,
|
||||||
int priority = 0)
|
int priority = 0,
|
||||||
|
ReleaseStrategy releaseStrategy = ReleaseStrategy.LRU)
|
||||||
{
|
{
|
||||||
Name = name ?? string.Empty;
|
Name = name ?? string.Empty;
|
||||||
AllowMultiSpawn = allowMultiSpawn;
|
AllowMultiSpawn = allowMultiSpawn;
|
||||||
@ -24,10 +26,11 @@ namespace AlicizaX.ObjectPool
|
|||||||
Capacity = capacity;
|
Capacity = capacity;
|
||||||
ExpireTime = expireTime;
|
ExpireTime = expireTime;
|
||||||
Priority = priority;
|
Priority = priority;
|
||||||
|
ReleaseStrategy = releaseStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectPoolCreateOptions WithName(string name)
|
public ObjectPoolCreateOptions WithName(string name)
|
||||||
=> new ObjectPoolCreateOptions(name, AllowMultiSpawn, AutoReleaseInterval, Capacity, ExpireTime, Priority);
|
=> new ObjectPoolCreateOptions(name, AllowMultiSpawn, AutoReleaseInterval, Capacity, ExpireTime, Priority, ReleaseStrategy);
|
||||||
|
|
||||||
public static ObjectPoolCreateOptions Single(string name = "")
|
public static ObjectPoolCreateOptions Single(string name = "")
|
||||||
=> new ObjectPoolCreateOptions(name: name);
|
=> new ObjectPoolCreateOptions(name: name);
|
||||||
|
|||||||
18
Runtime/ObjectPool/IPoolableObject.cs
Normal file
18
Runtime/ObjectPool/IPoolableObject.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace AlicizaX.ObjectPool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 可池化对象接口,支持自定义回收和重用逻辑
|
||||||
|
/// </summary>
|
||||||
|
public interface IPoolableObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对象被回收到池中时调用(重置状态)
|
||||||
|
/// </summary>
|
||||||
|
void OnRecycle();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对象从池中取出时调用(初始化状态)
|
||||||
|
/// </summary>
|
||||||
|
void OnReuse();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/ObjectPool/IPoolableObject.cs.meta
Normal file
11
Runtime/ObjectPool/IPoolableObject.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0c4c7354bf5820e408151d8ecbd1bab1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -54,4 +54,71 @@ namespace AlicizaX.ObjectPool
|
|||||||
m_LastUseTime = 0f;
|
m_LastUseTime = 0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 泛型对象池基类,消除装箱开销
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ObjectBase<T> : IMemory where T : class
|
||||||
|
{
|
||||||
|
private string m_Name;
|
||||||
|
private T m_Target;
|
||||||
|
private bool m_Locked;
|
||||||
|
private float m_LastUseTime;
|
||||||
|
|
||||||
|
public string Name => m_Name;
|
||||||
|
public T Target => m_Target;
|
||||||
|
|
||||||
|
public bool Locked
|
||||||
|
{
|
||||||
|
get => m_Locked;
|
||||||
|
set => m_Locked = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float LastUseTime
|
||||||
|
{
|
||||||
|
get => m_LastUseTime;
|
||||||
|
internal set => m_LastUseTime = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool CustomCanReleaseFlag => true;
|
||||||
|
|
||||||
|
protected void Initialize(T target)
|
||||||
|
{
|
||||||
|
Initialize(string.Empty, target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Initialize(string name, T target)
|
||||||
|
{
|
||||||
|
Initialize(name, target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Initialize(string name, T target, bool locked)
|
||||||
|
{
|
||||||
|
m_Name = name ?? string.Empty;
|
||||||
|
m_Target = target;
|
||||||
|
m_Locked = locked;
|
||||||
|
m_LastUseTime = 0f;
|
||||||
|
|
||||||
|
if (target is IPoolableObject poolable)
|
||||||
|
poolable.OnReuse();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void OnSpawn() { }
|
||||||
|
|
||||||
|
protected internal virtual void OnUnspawn()
|
||||||
|
{
|
||||||
|
if (m_Target is IPoolableObject poolable)
|
||||||
|
poolable.OnRecycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal abstract void Release(bool isShutdown);
|
||||||
|
|
||||||
|
public virtual void Clear()
|
||||||
|
{
|
||||||
|
m_Name = null;
|
||||||
|
m_Target = null;
|
||||||
|
m_Locked = false;
|
||||||
|
m_LastUseTime = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,18 @@ namespace AlicizaX.ObjectPool
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (m_FullName == null)
|
if (m_FullName == null)
|
||||||
m_FullName = new TypeNamePair(ObjectType, m_Name).ToString();
|
{
|
||||||
|
using (var sb = Cysharp.Text.ZString.CreateStringBuilder())
|
||||||
|
{
|
||||||
|
sb.Append(ObjectType.FullName);
|
||||||
|
if (!string.IsNullOrEmpty(m_Name))
|
||||||
|
{
|
||||||
|
sb.Append('.');
|
||||||
|
sb.Append(m_Name);
|
||||||
|
}
|
||||||
|
m_FullName = sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
return m_FullName;
|
return m_FullName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,19 +36,21 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private ObjectSlot[] m_Slots;
|
private ObjectSlot[] m_Slots;
|
||||||
private int m_SlotCount;
|
private int m_SlotCount;
|
||||||
|
private int m_SlotCapacity;
|
||||||
private int[] m_FreeStack;
|
private int[] m_FreeStack;
|
||||||
private int m_FreeTop;
|
private int m_FreeTop;
|
||||||
|
|
||||||
private readonly Dictionary<object, int> m_TargetMap;
|
private IntOpenHashMap m_TargetMap;
|
||||||
private readonly Dictionary<string, int> m_AllNameHeadMap;
|
private StringOpenHashMap m_AllNameHeadMap;
|
||||||
private readonly Dictionary<string, int> m_AvailableNameHeadMap;
|
private StringOpenHashMap m_AvailableNameHeadMap;
|
||||||
private readonly Dictionary<string, int> m_AvailableNameTailMap;
|
private StringOpenHashMap m_AvailableNameTailMap;
|
||||||
|
|
||||||
private readonly bool m_AllowMultiSpawn;
|
private readonly bool m_AllowMultiSpawn;
|
||||||
private float m_AutoReleaseInterval;
|
private float m_AutoReleaseInterval;
|
||||||
private int m_Capacity;
|
private int m_Capacity;
|
||||||
private float m_ExpireTime;
|
private float m_ExpireTime;
|
||||||
private int m_Priority;
|
private int m_Priority;
|
||||||
|
private ReleaseStrategy m_ReleaseStrategy;
|
||||||
private float m_AutoReleaseTime;
|
private float m_AutoReleaseTime;
|
||||||
|
|
||||||
private int m_PendingReleaseCount;
|
private int m_PendingReleaseCount;
|
||||||
@ -57,26 +59,30 @@ namespace AlicizaX.ObjectPool
|
|||||||
private int m_UnusedTail;
|
private int m_UnusedTail;
|
||||||
private int m_LastBudgetScanStart;
|
private int m_LastBudgetScanStart;
|
||||||
private bool m_IsShuttingDown;
|
private bool m_IsShuttingDown;
|
||||||
|
private int m_ShrinkCounter;
|
||||||
|
private const int ShrinkCheckInterval = 60;
|
||||||
|
|
||||||
private const int DefaultReleasePerFrame = 8;
|
private const int DefaultReleasePerFrame = 8;
|
||||||
private const int InitSlotCapacity = 16;
|
private const int InitSlotCapacity = 16;
|
||||||
|
|
||||||
public ObjectPool(string name, bool allowMultiSpawn,
|
public ObjectPool(string name, bool allowMultiSpawn,
|
||||||
float autoReleaseInterval, int capacity, float expireTime, int priority)
|
float autoReleaseInterval, int capacity, float expireTime, int priority, ReleaseStrategy releaseStrategy)
|
||||||
: base(name)
|
: base(name)
|
||||||
{
|
{
|
||||||
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
|
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
|
||||||
m_Slots = new ObjectSlot[initCap];
|
m_SlotCapacity = initCap;
|
||||||
m_FreeStack = new int[initCap];
|
m_Slots = SlotArrayPool<ObjectSlot>.Rent(initCap);
|
||||||
m_TargetMap = new Dictionary<object, int>(initCap, AlicizaX.ReferenceComparer<object>.Instance);
|
m_FreeStack = SlotArrayPool<int>.Rent(initCap);
|
||||||
m_AllNameHeadMap = new Dictionary<string, int>(initCap, StringComparer.Ordinal);
|
m_TargetMap = new IntOpenHashMap(initCap);
|
||||||
m_AvailableNameHeadMap = new Dictionary<string, int>(initCap, StringComparer.Ordinal);
|
m_AllNameHeadMap = new StringOpenHashMap(initCap);
|
||||||
m_AvailableNameTailMap = new Dictionary<string, int>(initCap, StringComparer.Ordinal);
|
m_AvailableNameHeadMap = 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;
|
||||||
m_ExpireTime = expireTime;
|
m_ExpireTime = expireTime;
|
||||||
m_Priority = priority;
|
m_Priority = priority;
|
||||||
|
m_ReleaseStrategy = releaseStrategy;
|
||||||
m_AutoReleaseTime = 0f;
|
m_AutoReleaseTime = 0f;
|
||||||
m_PendingReleaseCount = 0;
|
m_PendingReleaseCount = 0;
|
||||||
m_ReleasePerFrameBudget = DefaultReleasePerFrame;
|
m_ReleasePerFrameBudget = DefaultReleasePerFrame;
|
||||||
@ -84,6 +90,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
m_UnusedTail = -1;
|
m_UnusedTail = -1;
|
||||||
m_LastBudgetScanStart = -1;
|
m_LastBudgetScanStart = -1;
|
||||||
m_IsShuttingDown = false;
|
m_IsShuttingDown = false;
|
||||||
|
m_ShrinkCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Type ObjectType => typeof(T);
|
public override Type ObjectType => typeof(T);
|
||||||
@ -95,7 +102,13 @@ namespace AlicizaX.ObjectPool
|
|||||||
get => m_AutoReleaseInterval;
|
get => m_AutoReleaseInterval;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value < 0f) throw new GameFrameworkException("AutoReleaseInterval is invalid.");
|
if (value < 0f)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("AutoReleaseInterval is invalid.");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_AutoReleaseInterval = value;
|
m_AutoReleaseInterval = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +118,13 @@ namespace AlicizaX.ObjectPool
|
|||||||
get => m_Capacity;
|
get => m_Capacity;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value < 0) throw new GameFrameworkException("Capacity is invalid.");
|
if (value < 0)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("Capacity is invalid.");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_Capacity = value;
|
m_Capacity = value;
|
||||||
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
|
if (Count > m_Capacity) MarkRelease(Count - m_Capacity);
|
||||||
}
|
}
|
||||||
@ -116,7 +135,13 @@ namespace AlicizaX.ObjectPool
|
|||||||
get => m_ExpireTime;
|
get => m_ExpireTime;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value < 0f) throw new GameFrameworkException("ExpireTime is invalid.");
|
if (value < 0f)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("ExpireTime is invalid.");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_ExpireTime = value;
|
m_ExpireTime = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,11 +160,17 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
public void Register(T obj, bool spawned)
|
public void Register(T obj, bool spawned)
|
||||||
{
|
{
|
||||||
if (obj == null) throw new GameFrameworkException("Object is invalid.");
|
if (obj == null) return;
|
||||||
if (obj.Target == null) throw new GameFrameworkException("Object target is invalid.");
|
if (obj.Target == null) return;
|
||||||
if (m_TargetMap.TryGetValue(obj.Target, out int existingIdx) && m_Slots[existingIdx].IsAlive())
|
|
||||||
throw new GameFrameworkException(
|
int targetHash = obj.Target.GetHashCode();
|
||||||
$"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
|
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();
|
int idx = AllocSlot();
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref m_Slots[idx];
|
||||||
@ -154,7 +185,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
slot.NextUnused = -1;
|
slot.NextUnused = -1;
|
||||||
slot.SetAlive(true);
|
slot.SetAlive(true);
|
||||||
|
|
||||||
m_TargetMap[obj.Target] = idx;
|
m_TargetMap.AddOrUpdate(targetHash, idx);
|
||||||
|
|
||||||
string objectName = obj.Name ?? string.Empty;
|
string objectName = obj.Name ?? string.Empty;
|
||||||
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
|
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
|
||||||
@ -162,7 +193,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
m_Slots[existingHead].PrevByName = idx;
|
m_Slots[existingHead].PrevByName = idx;
|
||||||
slot.NextByName = existingHead;
|
slot.NextByName = existingHead;
|
||||||
}
|
}
|
||||||
m_AllNameHeadMap[objectName] = idx;
|
m_AllNameHeadMap.AddOrUpdate(objectName, idx);
|
||||||
|
|
||||||
obj.LastUseTime = slot.LastUseTime;
|
obj.LastUseTime = slot.LastUseTime;
|
||||||
if (spawned)
|
if (spawned)
|
||||||
@ -179,14 +210,19 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
public T Spawn(string name)
|
public T Spawn(string name)
|
||||||
{
|
{
|
||||||
if (name == null) throw new GameFrameworkException("Name is invalid.");
|
if (name == null) return null;
|
||||||
if (m_AllowMultiSpawn)
|
if (m_AllowMultiSpawn)
|
||||||
return SpawnAny(name);
|
return SpawnAny(name);
|
||||||
|
|
||||||
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) return null;
|
if (!m_AvailableNameHeadMap.TryGetValue(name, out int head)) 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))
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' available-name head is inconsistent.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name head is inconsistent.");
|
||||||
|
#endif
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
float now = Time.realtimeSinceStartup;
|
float now = Time.realtimeSinceStartup;
|
||||||
SpawnSlot(head, now);
|
SpawnSlot(head, now);
|
||||||
@ -199,7 +235,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
public bool CanSpawn(string name)
|
public bool CanSpawn(string name)
|
||||||
{
|
{
|
||||||
if (name == null) throw new GameFrameworkException("Name is invalid.");
|
if (name == null) return false;
|
||||||
if (m_AllowMultiSpawn)
|
if (m_AllowMultiSpawn)
|
||||||
return m_AllNameHeadMap.ContainsKey(name);
|
return m_AllNameHeadMap.ContainsKey(name);
|
||||||
|
|
||||||
@ -208,18 +244,21 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
public void Unspawn(T obj)
|
public void Unspawn(T obj)
|
||||||
{
|
{
|
||||||
if (obj == null) throw new GameFrameworkException("Object is invalid.");
|
if (obj == null) return;
|
||||||
Unspawn(obj.Target);
|
Unspawn(obj.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Unspawn(object target)
|
public void Unspawn(object target)
|
||||||
{
|
{
|
||||||
if (target == null) throw new GameFrameworkException("Target is invalid.");
|
if (target == null) return;
|
||||||
if (!m_TargetMap.TryGetValue(target, out int idx))
|
int targetHash = target.GetHashCode();
|
||||||
|
if (!m_TargetMap.TryGetValue(targetHash, out int idx))
|
||||||
{
|
{
|
||||||
if (m_IsShuttingDown) return;
|
if (m_IsShuttingDown) return;
|
||||||
throw new GameFrameworkException(
|
#if UNITY_EDITOR
|
||||||
$"Cannot find target in pool '{Name}', type='{target.GetType().FullName}'");
|
UnityEngine.Debug.LogError($"Cannot find target in pool '{Name}', type='{target.GetType().FullName}'");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UnspawnSlot(idx);
|
UnspawnSlot(idx);
|
||||||
@ -270,7 +309,11 @@ namespace AlicizaX.ObjectPool
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool checkExpire = m_ExpireTime < float.MaxValue;
|
bool checkExpire = m_ExpireTime < float.MaxValue;
|
||||||
if (m_PendingReleaseCount <= 0 && !checkExpire) return;
|
if (m_PendingReleaseCount <= 0 && !checkExpire)
|
||||||
|
{
|
||||||
|
TryProgressiveShrink();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float now = Time.realtimeSinceStartup;
|
float now = Time.realtimeSinceStartup;
|
||||||
float expireThreshold = checkExpire ? now - m_ExpireTime : float.MinValue;
|
float expireThreshold = checkExpire ? now - m_ExpireTime : float.MinValue;
|
||||||
@ -284,6 +327,41 @@ namespace AlicizaX.ObjectPool
|
|||||||
{
|
{
|
||||||
ReleaseExpired(m_ReleasePerFrameBudget, expireThreshold);
|
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()
|
internal override void Shutdown()
|
||||||
@ -303,7 +381,14 @@ namespace AlicizaX.ObjectPool
|
|||||||
m_AllNameHeadMap.Clear();
|
m_AllNameHeadMap.Clear();
|
||||||
m_AvailableNameHeadMap.Clear();
|
m_AvailableNameHeadMap.Clear();
|
||||||
m_AvailableNameTailMap.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_SlotCount = 0;
|
||||||
|
m_SlotCapacity = 0;
|
||||||
m_FreeTop = 0;
|
m_FreeTop = 0;
|
||||||
m_PendingReleaseCount = 0;
|
m_PendingReleaseCount = 0;
|
||||||
m_UnusedHead = -1;
|
m_UnusedHead = -1;
|
||||||
@ -315,7 +400,13 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
public override int GetAllObjectInfos(ObjectInfo[] results)
|
public override int GetAllObjectInfos(ObjectInfo[] results)
|
||||||
{
|
{
|
||||||
if (results == null) throw new GameFrameworkException("Results is invalid.");
|
if (results == null)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("Results is invalid.");
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int write = 0;
|
int write = 0;
|
||||||
int capacity = results.Length;
|
int capacity = results.Length;
|
||||||
@ -365,7 +456,12 @@ namespace AlicizaX.ObjectPool
|
|||||||
slot.Obj.OnUnspawn();
|
slot.Obj.OnUnspawn();
|
||||||
slot.SpawnCount--;
|
slot.SpawnCount--;
|
||||||
if (slot.SpawnCount < 0)
|
if (slot.SpawnCount < 0)
|
||||||
throw new GameFrameworkException($"Object '{slot.Obj.Name}' spawn count < 0.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Object '{slot.Obj.Name}' spawn count < 0.");
|
||||||
|
#endif
|
||||||
|
slot.SpawnCount = 0;
|
||||||
|
}
|
||||||
if (slot.SpawnCount == 0)
|
if (slot.SpawnCount == 0)
|
||||||
MarkSlotAvailable(idx);
|
MarkSlotAvailable(idx);
|
||||||
if (Count > m_Capacity && slot.SpawnCount == 0)
|
if (Count > m_Capacity && slot.SpawnCount == 0)
|
||||||
@ -393,9 +489,19 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private void GrowSlots()
|
private void GrowSlots()
|
||||||
{
|
{
|
||||||
int newCap = Math.Max(m_Slots.Length * 2, InitSlotCapacity);
|
int newCap = Math.Max(m_SlotCapacity * 2, InitSlotCapacity);
|
||||||
Array.Resize(ref m_Slots, newCap);
|
var newSlots = SlotArrayPool<ObjectSlot>.Rent(newCap);
|
||||||
Array.Resize(ref m_FreeStack, 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)
|
private void ReleaseSlot(int idx)
|
||||||
@ -408,7 +514,8 @@ namespace AlicizaX.ObjectPool
|
|||||||
MarkSlotUnavailable(idx);
|
MarkSlotUnavailable(idx);
|
||||||
|
|
||||||
RemoveFromAllNameChain(idx);
|
RemoveFromAllNameChain(idx);
|
||||||
m_TargetMap.Remove(obj.Target);
|
int targetHash = obj.Target.GetHashCode();
|
||||||
|
m_TargetMap.Remove(targetHash);
|
||||||
|
|
||||||
obj.Release(false);
|
obj.Release(false);
|
||||||
MemoryPool.Release(obj);
|
MemoryPool.Release(obj);
|
||||||
@ -423,8 +530,15 @@ namespace AlicizaX.ObjectPool
|
|||||||
slot.PrevUnused = -1;
|
slot.PrevUnused = -1;
|
||||||
slot.NextUnused = -1;
|
slot.NextUnused = -1;
|
||||||
|
|
||||||
if (m_FreeTop >= m_FreeStack.Length)
|
if (m_FreeTop >= m_SlotCapacity)
|
||||||
Array.Resize(ref m_FreeStack, m_FreeStack.Length * 2);
|
{
|
||||||
|
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;
|
m_FreeStack[m_FreeTop++] = idx;
|
||||||
|
|
||||||
ShrinkStorageIfEmpty();
|
ShrinkStorageIfEmpty();
|
||||||
@ -446,10 +560,15 @@ namespace AlicizaX.ObjectPool
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (head != idx)
|
if (head != idx)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' all-name chain is inconsistent.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain is inconsistent.");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (next >= 0)
|
if (next >= 0)
|
||||||
m_AllNameHeadMap[objectName] = next;
|
m_AllNameHeadMap.AddOrUpdate(objectName, next);
|
||||||
else
|
else
|
||||||
m_AllNameHeadMap.Remove(objectName);
|
m_AllNameHeadMap.Remove(objectName);
|
||||||
}
|
}
|
||||||
@ -524,11 +643,15 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private void ShrinkStorageIfEmpty()
|
private void ShrinkStorageIfEmpty()
|
||||||
{
|
{
|
||||||
if (m_TargetMap.Count > 0 || m_Slots.Length <= InitSlotCapacity)
|
if (m_TargetMap.Count > 0 || m_SlotCapacity <= InitSlotCapacity)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_Slots = new ObjectSlot[InitSlotCapacity];
|
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
|
||||||
m_FreeStack = new int[InitSlotCapacity];
|
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_AllNameHeadMap.Clear();
|
||||||
m_AvailableNameHeadMap.Clear();
|
m_AvailableNameHeadMap.Clear();
|
||||||
m_AvailableNameTailMap.Clear();
|
m_AvailableNameTailMap.Clear();
|
||||||
@ -542,6 +665,9 @@ namespace AlicizaX.ObjectPool
|
|||||||
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
|
[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
|
||||||
private void ValidateState()
|
private void ValidateState()
|
||||||
{
|
{
|
||||||
|
#if !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++)
|
||||||
@ -553,18 +679,29 @@ namespace AlicizaX.ObjectPool
|
|||||||
aliveCount++;
|
aliveCount++;
|
||||||
|
|
||||||
object target = slot.Obj.Target;
|
object target = slot.Obj.Target;
|
||||||
if (!m_TargetMap.TryGetValue(target, out int mappedIdx) || mappedIdx != i)
|
int targetHash = target.GetHashCode();
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' target index map is inconsistent.");
|
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;
|
string objectName = slot.Obj.Name ?? string.Empty;
|
||||||
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' all-name head is missing.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name head is missing.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.PrevByName < 0 && head != i)
|
if (slot.PrevByName < 0 && head != i)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' all-name chain head is inconsistent.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain head is inconsistent.");
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.NextByName >= 0 && m_Slots[slot.NextByName].PrevByName != i)
|
if (slot.NextByName >= 0 && m_Slots[slot.NextByName].PrevByName != i)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' all-name chain link is inconsistent.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain link is inconsistent.");
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
bool inAvailableList = false;
|
||||||
@ -573,30 +710,46 @@ namespace AlicizaX.ObjectPool
|
|||||||
{
|
{
|
||||||
unusedCount++;
|
unusedCount++;
|
||||||
if (!inUnusedList)
|
if (!inUnusedList)
|
||||||
throw new GameFrameworkException($"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))
|
if (!m_AvailableNameHeadMap.TryGetValue(objectName, out int availableHead))
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' available-name head is missing.");
|
{
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
inAvailableList = availableHead == i || slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0;
|
if (slot.NextAvailableByName >= 0 && m_Slots[slot.NextAvailableByName].PrevAvailableByName != i)
|
||||||
if (!inAvailableList)
|
{
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' available-name chain is inconsistent.");
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name link is inconsistent.");
|
||||||
|
}
|
||||||
if (slot.NextAvailableByName >= 0 && m_Slots[slot.NextAvailableByName].PrevAvailableByName != i)
|
}
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' available-name link is inconsistent.");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (inUnusedList)
|
if (inUnusedList)
|
||||||
throw new GameFrameworkException($"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)
|
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' spawned object exists in available chain.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' spawned object exists in available chain.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aliveCount != m_TargetMap.Count)
|
if (aliveCount != m_TargetMap.Count)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' alive count is inconsistent.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' alive count is inconsistent.");
|
||||||
|
}
|
||||||
|
|
||||||
int walkUnusedCount = 0;
|
int walkUnusedCount = 0;
|
||||||
int current = m_UnusedHead;
|
int current = m_UnusedHead;
|
||||||
@ -605,9 +758,13 @@ namespace AlicizaX.ObjectPool
|
|||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[current];
|
ref var slot = ref m_Slots[current];
|
||||||
if (!slot.IsAlive() || slot.SpawnCount != 0)
|
if (!slot.IsAlive() || slot.SpawnCount != 0)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' unused chain contains invalid slot.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain contains invalid slot.");
|
||||||
|
}
|
||||||
if (slot.PrevUnused != prevUnused)
|
if (slot.PrevUnused != prevUnused)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' unused chain linkage is inconsistent.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain linkage is inconsistent.");
|
||||||
|
}
|
||||||
|
|
||||||
walkUnusedCount++;
|
walkUnusedCount++;
|
||||||
prevUnused = current;
|
prevUnused = current;
|
||||||
@ -615,7 +772,10 @@ namespace AlicizaX.ObjectPool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (walkUnusedCount != unusedCount)
|
if (walkUnusedCount != unusedCount)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' unused chain count is inconsistent.");
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain count is inconsistent.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MarkSlotAvailable(int idx)
|
private void MarkSlotAvailable(int idx)
|
||||||
@ -677,7 +837,12 @@ namespace AlicizaX.ObjectPool
|
|||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref m_Slots[idx];
|
||||||
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
|
if (slot.PrevAvailableByName >= 0 || slot.NextAvailableByName >= 0)
|
||||||
throw new GameFrameworkException($"Object pool '{FullName}' available-name chain is inconsistent.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' available-name chain is inconsistent.");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string objectName = slot.Obj.Name ?? string.Empty;
|
string objectName = slot.Obj.Name ?? string.Empty;
|
||||||
if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail))
|
if (m_AvailableNameTailMap.TryGetValue(objectName, out int tail))
|
||||||
@ -685,14 +850,14 @@ namespace AlicizaX.ObjectPool
|
|||||||
m_Slots[tail].NextAvailableByName = idx;
|
m_Slots[tail].NextAvailableByName = idx;
|
||||||
slot.PrevAvailableByName = tail;
|
slot.PrevAvailableByName = tail;
|
||||||
slot.NextAvailableByName = -1;
|
slot.NextAvailableByName = -1;
|
||||||
m_AvailableNameTailMap[objectName] = idx;
|
m_AvailableNameTailMap.AddOrUpdate(objectName, idx);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
slot.PrevAvailableByName = -1;
|
slot.PrevAvailableByName = -1;
|
||||||
slot.NextAvailableByName = -1;
|
slot.NextAvailableByName = -1;
|
||||||
m_AvailableNameHeadMap[objectName] = idx;
|
m_AvailableNameHeadMap.AddOrUpdate(objectName, idx);
|
||||||
m_AvailableNameTailMap[objectName] = idx;
|
m_AvailableNameTailMap.AddOrUpdate(objectName, idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,14 +878,14 @@ namespace AlicizaX.ObjectPool
|
|||||||
if (prev >= 0)
|
if (prev >= 0)
|
||||||
m_Slots[prev].NextAvailableByName = next;
|
m_Slots[prev].NextAvailableByName = next;
|
||||||
else if (next >= 0)
|
else if (next >= 0)
|
||||||
m_AvailableNameHeadMap[objectName] = next;
|
m_AvailableNameHeadMap.AddOrUpdate(objectName, next);
|
||||||
else
|
else
|
||||||
m_AvailableNameHeadMap.Remove(objectName);
|
m_AvailableNameHeadMap.Remove(objectName);
|
||||||
|
|
||||||
if (next >= 0)
|
if (next >= 0)
|
||||||
m_Slots[next].PrevAvailableByName = prev;
|
m_Slots[next].PrevAvailableByName = prev;
|
||||||
else if (prev >= 0)
|
else if (prev >= 0)
|
||||||
m_AvailableNameTailMap[objectName] = prev;
|
m_AvailableNameTailMap.AddOrUpdate(objectName, prev);
|
||||||
else
|
else
|
||||||
m_AvailableNameTailMap.Remove(objectName);
|
m_AvailableNameTailMap.Remove(objectName);
|
||||||
|
|
||||||
|
|||||||
@ -92,7 +92,13 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results)
|
int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results)
|
||||||
{
|
{
|
||||||
if (results == null) throw new GameFrameworkException("Results is invalid.");
|
if (results == null)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("Results is invalid.");
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
List<ObjectPoolBase> source = m_ObjectPoolList;
|
List<ObjectPoolBase> source = m_ObjectPoolList;
|
||||||
if (sort)
|
if (sort)
|
||||||
@ -115,7 +121,12 @@ namespace AlicizaX.ObjectPool
|
|||||||
{
|
{
|
||||||
var key = new TypeNamePair(typeof(T), options.Name);
|
var key = new TypeNamePair(typeof(T), options.Name);
|
||||||
if (m_ObjectPools.ContainsKey(key))
|
if (m_ObjectPools.ContainsKey(key))
|
||||||
throw new GameFrameworkException($"Already exist object pool '{key}'.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Already exist object pool '{key}'.");
|
||||||
|
#endif
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var pool = new ObjectPool<T>(
|
var pool = new ObjectPool<T>(
|
||||||
options.Name ?? string.Empty,
|
options.Name ?? string.Empty,
|
||||||
@ -123,7 +134,8 @@ namespace AlicizaX.ObjectPool
|
|||||||
options.AutoReleaseInterval ?? DefaultAutoReleaseInterval,
|
options.AutoReleaseInterval ?? DefaultAutoReleaseInterval,
|
||||||
options.Capacity ?? DefaultCapacity,
|
options.Capacity ?? DefaultCapacity,
|
||||||
options.ExpireTime ?? DefaultExpireTime,
|
options.ExpireTime ?? DefaultExpireTime,
|
||||||
options.Priority);
|
options.Priority,
|
||||||
|
options.ReleaseStrategy);
|
||||||
|
|
||||||
m_ObjectPools.Add(key, pool);
|
m_ObjectPools.Add(key, pool);
|
||||||
m_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
|
m_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
|
||||||
@ -136,7 +148,12 @@ namespace AlicizaX.ObjectPool
|
|||||||
ValidateObjectType(objectType);
|
ValidateObjectType(objectType);
|
||||||
var key = new TypeNamePair(objectType, options.Name);
|
var key = new TypeNamePair(objectType, options.Name);
|
||||||
if (m_ObjectPools.ContainsKey(key))
|
if (m_ObjectPools.ContainsKey(key))
|
||||||
throw new GameFrameworkException($"Already exist object pool '{key}'.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Already exist object pool '{key}'.");
|
||||||
|
#endif
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var poolType = typeof(ObjectPool<>).MakeGenericType(objectType);
|
var poolType = typeof(ObjectPool<>).MakeGenericType(objectType);
|
||||||
var pool = (ObjectPoolBase)Activator.CreateInstance(poolType,
|
var pool = (ObjectPoolBase)Activator.CreateInstance(poolType,
|
||||||
@ -145,7 +162,8 @@ namespace AlicizaX.ObjectPool
|
|||||||
options.AutoReleaseInterval ?? DefaultAutoReleaseInterval,
|
options.AutoReleaseInterval ?? DefaultAutoReleaseInterval,
|
||||||
options.Capacity ?? DefaultCapacity,
|
options.Capacity ?? DefaultCapacity,
|
||||||
options.ExpireTime ?? DefaultExpireTime,
|
options.ExpireTime ?? DefaultExpireTime,
|
||||||
options.Priority);
|
options.Priority,
|
||||||
|
options.ReleaseStrategy);
|
||||||
|
|
||||||
m_ObjectPools.Add(key, pool);
|
m_ObjectPools.Add(key, pool);
|
||||||
m_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
|
m_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
|
||||||
@ -175,13 +193,25 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
public bool DestroyObjectPool<T>(IObjectPool<T> objectPool) where T : ObjectBase
|
public bool DestroyObjectPool<T>(IObjectPool<T> objectPool) where T : ObjectBase
|
||||||
{
|
{
|
||||||
if (objectPool == null) throw new GameFrameworkException("Object pool is invalid.");
|
if (objectPool == null)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("Object pool is invalid.");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return InternalDestroy(new TypeNamePair(typeof(T), objectPool.Name));
|
return InternalDestroy(new TypeNamePair(typeof(T), objectPool.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DestroyObjectPool(ObjectPoolBase objectPool)
|
public bool DestroyObjectPool(ObjectPoolBase objectPool)
|
||||||
{
|
{
|
||||||
if (objectPool == null) throw new GameFrameworkException("Object pool is invalid.");
|
if (objectPool == null)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("Object pool is invalid.");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return InternalDestroy(new TypeNamePair(objectPool.ObjectType, objectPool.Name));
|
return InternalDestroy(new TypeNamePair(objectPool.ObjectType, objectPool.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,9 +283,18 @@ namespace AlicizaX.ObjectPool
|
|||||||
private static void ValidateObjectType(Type objectType)
|
private static void ValidateObjectType(Type objectType)
|
||||||
{
|
{
|
||||||
if (objectType == null)
|
if (objectType == null)
|
||||||
throw new GameFrameworkException("Object type is invalid.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError("Object type is invalid.");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!typeof(ObjectBase).IsAssignableFrom(objectType))
|
if (!typeof(ObjectBase).IsAssignableFrom(objectType))
|
||||||
throw new GameFrameworkException($"Object type '{objectType.FullName}' is invalid.");
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEngine.Debug.LogError($"Object type '{objectType.FullName}' is invalid.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ObjectPoolComparer(ObjectPoolBase a, ObjectPoolBase b)
|
private static int ObjectPoolComparer(ObjectPoolBase a, ObjectPoolBase b)
|
||||||
|
|||||||
28
Runtime/ObjectPool/ReleaseStrategy.cs
Normal file
28
Runtime/ObjectPool/ReleaseStrategy.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
namespace AlicizaX.ObjectPool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对象池释放策略
|
||||||
|
/// </summary>
|
||||||
|
public enum ReleaseStrategy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// LRU (Least Recently Used) - 最近最少使用
|
||||||
|
/// </summary>
|
||||||
|
LRU = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LFU (Least Frequently Used) - 最不经常使用
|
||||||
|
/// </summary>
|
||||||
|
LFU = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Priority - 基于优先级
|
||||||
|
/// </summary>
|
||||||
|
Priority = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hybrid - 混合策略 (LRU + Priority)
|
||||||
|
/// </summary>
|
||||||
|
Hybrid = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/ObjectPool/ReleaseStrategy.cs.meta
Normal file
11
Runtime/ObjectPool/ReleaseStrategy.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 311d5e5b578ed15428d9565ab237becc
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
24
Runtime/ObjectPool/SlotArrayPool.cs
Normal file
24
Runtime/ObjectPool/SlotArrayPool.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace AlicizaX.ObjectPool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 数组池管理器,避免频繁分配数组
|
||||||
|
/// </summary>
|
||||||
|
internal static class SlotArrayPool<T>
|
||||||
|
{
|
||||||
|
private static readonly ArrayPool<T> s_Pool = ArrayPool<T>.Create(256, 50);
|
||||||
|
|
||||||
|
public static T[] Rent(int minimumLength)
|
||||||
|
{
|
||||||
|
return s_Pool.Rent(minimumLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Return(T[] array, bool clearArray = false)
|
||||||
|
{
|
||||||
|
if (array != null)
|
||||||
|
s_Pool.Return(array, clearArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/ObjectPool/SlotArrayPool.cs.meta
Normal file
11
Runtime/ObjectPool/SlotArrayPool.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ec27a420ef8eea14fade722558971daa
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
183
Runtime/ObjectPool/StringOpenHashMap.cs
Normal file
183
Runtime/ObjectPool/StringOpenHashMap.cs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace AlicizaX.ObjectPool
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 字符串键的开放寻址哈希表,零GC实现
|
||||||
|
/// </summary>
|
||||||
|
internal struct StringOpenHashMap
|
||||||
|
{
|
||||||
|
private int[] m_Buckets;
|
||||||
|
private string[] 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 StringOpenHashMap(int capacity)
|
||||||
|
{
|
||||||
|
int cap = NextPowerOf2(Math.Max(capacity, MinCapacity));
|
||||||
|
m_Mask = cap - 1;
|
||||||
|
m_Buckets = new int[cap];
|
||||||
|
m_Keys = new string[cap];
|
||||||
|
m_Values = new int[cap];
|
||||||
|
m_Next = new int[cap];
|
||||||
|
m_Count = 0;
|
||||||
|
m_FreeList = 0;
|
||||||
|
m_AllocCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TryGetValue(string key, out int value)
|
||||||
|
{
|
||||||
|
if (m_Buckets == null || key == 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] == key) { value = m_Values[idx]; return true; }
|
||||||
|
i = m_Next[idx];
|
||||||
|
}
|
||||||
|
value = -1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AddOrUpdate(string key, int value)
|
||||||
|
{
|
||||||
|
if (key == null) return;
|
||||||
|
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] == 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(string key)
|
||||||
|
{
|
||||||
|
if (m_Buckets == null || key == 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] == key)
|
||||||
|
{
|
||||||
|
if (prev == 0) m_Buckets[bucket] = m_Next[idx];
|
||||||
|
else m_Next[prev - 1] = m_Next[idx];
|
||||||
|
m_Keys[idx] = null;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool ContainsKey(string key)
|
||||||
|
{
|
||||||
|
return TryGetValue(key, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = new int[newCap];
|
||||||
|
var newKeys = new string[newCap];
|
||||||
|
var newValues = new int[newCap];
|
||||||
|
var newNext = new int[newCap];
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/ObjectPool/StringOpenHashMap.cs.meta
Normal file
11
Runtime/ObjectPool/StringOpenHashMap.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ac1ce9399b68c57439691aae9aec01ca
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Loading…
Reference in New Issue
Block a user