[Opt] ObjectPool&&Timer

This commit is contained in:
陈思海 2026-04-27 12:06:09 +08:00
parent 4c134af495
commit 58fb685792
20 changed files with 937 additions and 785 deletions

View File

@ -8,7 +8,7 @@ namespace AlicizaX.Timer.Editor
[CustomEditor(typeof(TimerComponent))] [CustomEditor(typeof(TimerComponent))]
internal sealed class TimerComponentInspector : GameFrameworkInspector internal sealed class TimerComponentInspector : GameFrameworkInspector
{ {
private const double UPDATE_INTERVAL = 0.001d; private const double UPDATE_INTERVAL = 0.25d;
private const int MAX_DISPLAY_COUNT = 20; private const int MAX_DISPLAY_COUNT = 20;
private TimerDebugInfo[] _timerBuffer; private TimerDebugInfo[] _timerBuffer;
@ -95,20 +95,23 @@ namespace AlicizaX.Timer.Editor
int timerCount = debug.GetAllTimers(_timerBuffer); int timerCount = debug.GetAllTimers(_timerBuffer);
int displayCount = Mathf.Min(timerCount, MAX_DISPLAY_COUNT); int displayCount = Mathf.Min(timerCount, MAX_DISPLAY_COUNT);
if (displayCount < timerCount) if (displayCount < activeCount)
{ {
EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, timerCount), MessageType.Info); EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, activeCount), MessageType.Info);
} }
for (int i = 0; i < displayCount; i++) for (int i = 0; i < displayCount; i++)
{ {
TimerDebugInfo timer = _timerBuffer[i]; TimerDebugInfo timer = _timerBuffer[i];
bool isLoop = (timer.Flags & TimerDebugFlags.Loop) != 0;
bool isRunning = (timer.Flags & TimerDebugFlags.Running) != 0;
bool isUnscaled = (timer.Flags & TimerDebugFlags.Unscaled) != 0;
string label = Utility.Text.Format( string label = Utility.Text.Format(
"ID {0} | {1} | {2} | {3}", "ID {0} | {1} | {2} | {3}",
timer.TimerId, timer.TimerHandle,
timer.IsLoop ? "Loop" : "Once", isLoop ? "Loop" : "Once",
timer.IsUnscaled ? "Unscaled" : "Scaled", isUnscaled ? "Unscaled" : "Scaled",
timer.IsRunning ? "Running" : "Paused"); isRunning ? "Running" : "Paused");
string value = Utility.Text.Format( string value = Utility.Text.Format(
"Left {0:F2}s | Duration {1:F2}s", "Left {0:F2}s | Duration {1:F2}s",
@ -142,26 +145,28 @@ namespace AlicizaX.Timer.Editor
{ {
TimerDebugInfo staleTimer = _leakBuffer[i]; TimerDebugInfo staleTimer = _leakBuffer[i];
EditorGUILayout.LabelField( EditorGUILayout.LabelField(
Utility.Text.Format("ID {0}", staleTimer.TimerId), Utility.Text.Format("ID {0}", staleTimer.TimerHandle),
Utility.Text.Format("Created {0:F1}s ago", staleTimer.CreationTime)); Utility.Text.Format("Created {0:F1}s ago", staleTimer.Age));
} }
} }
#endif #endif
private void EnsureTimerBuffer(int count) private void EnsureTimerBuffer(int count)
{ {
if (_timerBuffer == null || _timerBuffer.Length < count) int capacity = count > 0 ? count : 1;
if (_timerBuffer == null || _timerBuffer.Length < capacity)
{ {
_timerBuffer = new TimerDebugInfo[count]; _timerBuffer = new TimerDebugInfo[capacity];
} }
} }
#if UNITY_EDITOR #if UNITY_EDITOR
private void EnsureLeakBuffer(int count) private void EnsureLeakBuffer(int count)
{ {
if (_leakBuffer == null || _leakBuffer.Length < count) int capacity = count > 0 ? count : 1;
if (_leakBuffer == null || _leakBuffer.Length < capacity)
{ {
_leakBuffer = new TimerDebugInfo[count]; _leakBuffer = new TimerDebugInfo[capacity];
} }
} }
#endif #endif

View File

@ -9,31 +9,53 @@ namespace AlicizaX.Debugger.Runtime
private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase
{ {
private const int MAX_DISPLAY_COUNT = 50; private const int MAX_DISPLAY_COUNT = 50;
private const float REFRESH_INTERVAL = 0.25f;
private const string OVERFLOW_NOTE = "Showing first 50 active timers.";
private const string EMPTY_NOTE = "No active timers.";
private const string SAMPLE_NOTE = "Zero-allocation runtime sample view.";
private struct RowView private struct RowView
{ {
public VisualElement Root; public VisualElement Root;
public Label Title; public VisualElement LoopIndicator;
public Label Value; public VisualElement ScaleIndicator;
public VisualElement StateIndicator;
public VisualElement Fill;
} }
private ITimerServiceDebug _mTimerDebug; private ITimerServiceDebug _mTimerDebug;
private TimerDebugInfo[] m_TimerInfos; private readonly TimerDebugInfo[] m_TimerInfos = new TimerDebugInfo[MAX_DISPLAY_COUNT];
private Label m_SectionTitleLabel;
private Label m_ActiveCountLabel;
private Label m_PoolCapacityLabel;
private Label m_PeakCountLabel;
private Label m_FreeCountLabel;
private Label m_UsageLabel;
private Label m_WarningLabel;
private RowView m_EmptyRow;
private readonly RowView[] m_TimerRows = new RowView[MAX_DISPLAY_COUNT]; private readonly RowView[] m_TimerRows = new RowView[MAX_DISPLAY_COUNT];
private VisualElement m_ActiveUsageFill;
private VisualElement m_PeakUsageFill;
private VisualElement m_FreeUsageFill;
private VisualElement m_OverflowNote;
private VisualElement m_EmptyNote;
private float m_RefreshCountdown;
public override void Initialize(params object[] args) public override void Initialize(params object[] args)
{ {
_mTimerDebug = AppServices.Require<ITimerService>() as ITimerServiceDebug; _mTimerDebug = AppServices.Require<ITimerService>() as ITimerServiceDebug;
} }
public override void OnEnter()
{
m_RefreshCountdown = 0f;
RefreshContent();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
m_RefreshCountdown -= realElapseSeconds;
if (m_RefreshCountdown > 0f)
{
return;
}
m_RefreshCountdown = REFRESH_INTERVAL;
RefreshContent();
}
protected override void BuildWindow(VisualElement root) protected override void BuildWindow(VisualElement root)
{ {
if (_mTimerDebug == null) if (_mTimerDebug == null)
@ -44,91 +66,61 @@ namespace AlicizaX.Debugger.Runtime
root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText)); root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText));
VisualElement overview = CreateSection("Timer Pool Overview", out VisualElement overviewCard); VisualElement overview = CreateSection("Timer Pool Overview", out VisualElement overviewCard);
m_ActiveCountLabel = AddTextRow(overviewCard, "Active Timer Count").Value; overviewCard.Add(CreateUsageRow("Active Usage", DebuggerTheme.Accent, out m_ActiveUsageFill));
m_PoolCapacityLabel = AddTextRow(overviewCard, "Pool Capacity").Value; overviewCard.Add(CreateUsageRow("Peak Usage", DebuggerTheme.Warning, out m_PeakUsageFill));
m_PeakCountLabel = AddTextRow(overviewCard, "Peak Active Count").Value; overviewCard.Add(CreateUsageRow("Free Capacity", DebuggerTheme.Positive, out m_FreeUsageFill));
m_FreeCountLabel = AddTextRow(overviewCard, "Free Count").Value;
m_UsageLabel = AddTextRow(overviewCard, "Pool Usage").Value;
root.Add(overview); root.Add(overview);
VisualElement section = CreateSection("Active Timers", out VisualElement timerCard); VisualElement sample = CreateSection("Timer Sample", out VisualElement sampleCard);
m_SectionTitleLabel = section.ElementAt(0) as Label; sampleCard.Add(CreateNoteLabel(SAMPLE_NOTE, DebuggerTheme.SecondaryText));
m_WarningLabel = new Label(); m_OverflowNote = CreateNoteLabel(OVERFLOW_NOTE, new Color(1f, 0.5f, 0f));
m_WarningLabel.style.color = new Color(1f, 0.5f, 0f); m_OverflowNote.style.display = DisplayStyle.None;
m_WarningLabel.style.display = DisplayStyle.None; sampleCard.Add(m_OverflowNote);
m_WarningLabel.style.marginBottom = 4f;
timerCard.Add(m_WarningLabel);
m_EmptyRow = AddTextRow(timerCard, string.Empty); m_EmptyNote = CreateNoteLabel(EMPTY_NOTE, DebuggerTheme.SecondaryText);
m_EmptyRow.Root.style.display = DisplayStyle.None; m_EmptyNote.style.display = DisplayStyle.None;
sampleCard.Add(m_EmptyNote);
for (int i = 0; i < MAX_DISPLAY_COUNT; i++) for (int i = 0; i < MAX_DISPLAY_COUNT; i++)
{ {
m_TimerRows[i] = AddTextRow(timerCard, string.Empty); m_TimerRows[i] = CreateTimerRow(sampleCard);
m_TimerRows[i].Root.style.display = DisplayStyle.None; m_TimerRows[i].Root.style.display = DisplayStyle.None;
} }
root.Add(section); root.Add(sample);
RefreshContent(); RefreshContent();
} }
private void RefreshContent() private void RefreshContent()
{ {
if (_mTimerDebug == null) if (_mTimerDebug == null || m_ActiveUsageFill == null)
{ {
return; return;
} }
_mTimerDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount); _mTimerDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
float poolUsage = poolCapacity > 0 ? (float)activeCount / poolCapacity : 0f; float capacity = poolCapacity > 0 ? poolCapacity : 1f;
SetFillRatio(m_ActiveUsageFill, activeCount / capacity);
m_ActiveCountLabel.text = activeCount.ToString(); SetFillRatio(m_PeakUsageFill, peakActiveCount / capacity);
m_PoolCapacityLabel.text = poolCapacity.ToString(); SetFillRatio(m_FreeUsageFill, freeCount / capacity);
m_PeakCountLabel.text = peakActiveCount.ToString();
m_FreeCountLabel.text = freeCount.ToString();
m_UsageLabel.text = Utility.Text.Format("{0:P1}", poolUsage);
if (activeCount <= 0) if (activeCount <= 0)
{ {
m_SectionTitleLabel.text = "Active Timers"; m_EmptyNote.style.display = DisplayStyle.Flex;
m_WarningLabel.style.display = DisplayStyle.None; m_OverflowNote.style.display = DisplayStyle.None;
m_EmptyRow.Root.style.display = DisplayStyle.Flex;
m_EmptyRow.Title.text = "Status";
m_EmptyRow.Value.text = "No active timers";
SetTimerRowsVisible(0); SetTimerRowsVisible(0);
return; return;
} }
EnsureTimerInfoBuffer(activeCount); m_EmptyNote.style.display = DisplayStyle.None;
int timerCount = _mTimerDebug.GetAllTimers(m_TimerInfos); int timerCount = _mTimerDebug.GetAllTimers(m_TimerInfos);
int displayCount = timerCount > MAX_DISPLAY_COUNT ? MAX_DISPLAY_COUNT : timerCount; int displayCount = timerCount > MAX_DISPLAY_COUNT ? MAX_DISPLAY_COUNT : timerCount;
m_OverflowNote.style.display = activeCount > MAX_DISPLAY_COUNT ? DisplayStyle.Flex : DisplayStyle.None;
m_SectionTitleLabel.text = Utility.Text.Format("Active Timers ({0})", timerCount);
m_EmptyRow.Root.style.display = DisplayStyle.None;
if (displayCount < timerCount)
{
m_WarningLabel.text = Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, timerCount);
m_WarningLabel.style.display = DisplayStyle.Flex;
}
else
{
m_WarningLabel.style.display = DisplayStyle.None;
}
for (int i = 0; i < displayCount; i++) for (int i = 0; i < displayCount; i++)
{ {
ref RowView row = ref m_TimerRows[i]; UpdateTimerRow(ref m_TimerRows[i], ref m_TimerInfos[i]);
TimerDebugInfo info = m_TimerInfos[i];
row.Title.text = Utility.Text.Format("Timer #{0}", info.TimerId);
row.Value.text = Utility.Text.Format(
"{0} | {1} | {2} | Remaining: {3:F2}s | Duration: {4:F2}s",
info.IsLoop ? "Loop" : "Once",
info.IsRunning ? "Running" : "Paused",
info.IsUnscaled ? "Unscaled" : "Scaled",
info.LeftTime,
info.Duration);
row.Root.style.display = DisplayStyle.Flex;
} }
SetTimerRowsVisible(displayCount); SetTimerRowsVisible(displayCount);
@ -142,58 +134,146 @@ namespace AlicizaX.Debugger.Runtime
} }
} }
private RowView AddTextRow(VisualElement parent, string title) private static VisualElement CreateUsageRow(string title, Color fillColor, out VisualElement fill)
{
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
VisualElement row = new VisualElement();
row.style.flexDirection = FlexDirection.Column;
row.style.marginBottom = 8f * scale;
Label titleLabel = new Label(title);
titleLabel.style.color = DebuggerTheme.SecondaryText;
titleLabel.style.fontSize = 16f * scale;
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
titleLabel.style.marginBottom = 4f * scale;
row.Add(titleLabel);
VisualElement track = new VisualElement();
track.style.height = 14f * scale;
track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong;
track.style.borderTopLeftRadius = 4f * scale;
track.style.borderTopRightRadius = 4f * scale;
track.style.borderBottomLeftRadius = 4f * scale;
track.style.borderBottomRightRadius = 4f * scale;
track.style.overflow = Overflow.Hidden;
fill = new VisualElement();
fill.style.height = Length.Percent(100f);
fill.style.width = Length.Percent(0f);
fill.style.backgroundColor = fillColor;
track.Add(fill);
row.Add(track);
return row;
}
private static VisualElement CreateNoteLabel(string text, Color color)
{
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
Label label = new Label(text);
label.style.color = color;
label.style.fontSize = 15f * scale;
label.style.marginBottom = 6f * scale;
label.style.whiteSpace = WhiteSpace.Normal;
return label;
}
private static RowView CreateTimerRow(VisualElement parent)
{ {
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
VisualElement row = new VisualElement(); VisualElement row = new VisualElement();
row.style.flexDirection = FlexDirection.Row; row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center; row.style.alignItems = Align.Center;
row.style.minHeight = 36f * scale; row.style.height = 20f * scale;
row.style.marginBottom = 4f * scale; row.style.marginBottom = 4f * scale;
Label titleLabel = new Label(title); VisualElement loopIndicator = CreateIndicator(6f * scale, DebuggerTheme.Accent);
titleLabel.style.minWidth = 280f * scale; VisualElement scaleIndicator = CreateIndicator(6f * scale, DebuggerTheme.Warning);
titleLabel.style.maxWidth = 280f * scale; VisualElement stateIndicator = CreateIndicator(6f * scale, DebuggerTheme.Positive);
titleLabel.style.color = DebuggerTheme.SecondaryText;
titleLabel.style.fontSize = 18f * scale;
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
titleLabel.style.flexShrink = 0f;
titleLabel.style.whiteSpace = WhiteSpace.Normal;
Label valueLabel = new Label(); row.Add(loopIndicator);
valueLabel.style.flexGrow = 1f; row.Add(scaleIndicator);
valueLabel.style.color = DebuggerTheme.PrimaryText; row.Add(stateIndicator);
valueLabel.style.fontSize = 18f * scale;
valueLabel.style.whiteSpace = WhiteSpace.Normal;
row.Add(titleLabel); VisualElement track = new VisualElement();
row.Add(valueLabel); track.style.flexGrow = 1f;
track.style.height = 14f * scale;
track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong;
track.style.borderTopLeftRadius = 4f * scale;
track.style.borderTopRightRadius = 4f * scale;
track.style.borderBottomLeftRadius = 4f * scale;
track.style.borderBottomRightRadius = 4f * scale;
track.style.overflow = Overflow.Hidden;
VisualElement fill = new VisualElement();
fill.style.height = Length.Percent(100f);
fill.style.width = Length.Percent(0f);
fill.style.backgroundColor = DebuggerTheme.Positive;
track.Add(fill);
row.Add(track);
parent.Add(row); parent.Add(row);
RowView view; RowView view;
view.Root = row; view.Root = row;
view.Title = titleLabel; view.LoopIndicator = loopIndicator;
view.Value = valueLabel; view.ScaleIndicator = scaleIndicator;
view.StateIndicator = stateIndicator;
view.Fill = fill;
return view; return view;
} }
private int EnsureTimerInfoBuffer(int count) private static VisualElement CreateIndicator(float size, Color color)
{ {
if (count <= 0) VisualElement indicator = new VisualElement();
indicator.style.width = size;
indicator.style.height = size;
indicator.style.marginRight = size;
indicator.style.backgroundColor = color;
indicator.style.opacity = 0.2f;
return indicator;
}
private static void UpdateTimerRow(ref RowView row, ref TimerDebugInfo info)
{
byte flags = info.Flags;
bool isRunning = (flags & TimerDebugFlags.Running) != 0;
bool isLoop = (flags & TimerDebugFlags.Loop) != 0;
bool isUnscaled = (flags & TimerDebugFlags.Unscaled) != 0;
float duration = info.Duration;
float ratio = duration > 0f ? info.LeftTime / duration : 0f;
if (ratio < 0f)
{ {
if (m_TimerInfos == null || m_TimerInfos.Length == 0) ratio = 0f;
{ }
m_TimerInfos = new TimerDebugInfo[1]; else if (ratio > 1f)
} {
return 0; ratio = 1f;
} }
if (m_TimerInfos == null || m_TimerInfos.Length < count) row.Root.style.display = DisplayStyle.Flex;
row.LoopIndicator.style.opacity = isLoop ? 1f : 0.2f;
row.ScaleIndicator.style.opacity = isUnscaled ? 1f : 0.35f;
row.ScaleIndicator.style.backgroundColor = isUnscaled ? DebuggerTheme.Warning : DebuggerTheme.Accent;
row.StateIndicator.style.opacity = isRunning ? 1f : 0.45f;
row.StateIndicator.style.backgroundColor = isRunning ? DebuggerTheme.Positive : DebuggerTheme.Warning;
row.Fill.style.width = Length.Percent(ratio * 100f);
row.Fill.style.backgroundColor = isRunning
? ratio <= 0.2f ? DebuggerTheme.Warning : DebuggerTheme.Positive
: DebuggerTheme.SecondaryText;
}
private static void SetFillRatio(VisualElement fill, float ratio)
{
if (ratio < 0f)
{ {
m_TimerInfos = new TimerDebugInfo[count]; ratio = 0f;
}
else if (ratio > 1f)
{
ratio = 1f;
} }
return count; fill.style.width = Length.Percent(ratio * 100f);
} }
} }
} }

View File

@ -1,4 +1,3 @@
using System;
namespace AlicizaX.ObjectPool namespace AlicizaX.ObjectPool
{ {
public readonly struct ObjectPoolCreateOptions public readonly struct ObjectPoolCreateOptions
@ -9,7 +8,6 @@ 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 = "",
@ -17,8 +15,7 @@ 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;
@ -26,11 +23,10 @@ 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, ReleaseStrategy); => new ObjectPoolCreateOptions(name, AllowMultiSpawn, AutoReleaseInterval, Capacity, ExpireTime, Priority);
public static ObjectPoolCreateOptions Single(string name = "") public static ObjectPoolCreateOptions Single(string name = "")
=> new ObjectPoolCreateOptions(name: name); => new ObjectPoolCreateOptions(name: name);
@ -46,23 +42,15 @@ namespace AlicizaX.ObjectPool
bool HasObjectPool<T>() where T : ObjectBase; bool HasObjectPool<T>() where T : ObjectBase;
bool HasObjectPool<T>(string name) where T : ObjectBase; bool HasObjectPool<T>(string name) where T : ObjectBase;
bool HasObjectPool(Type objectType);
bool HasObjectPool(Type objectType, string name);
IObjectPool<T> GetObjectPool<T>() where T : ObjectBase; IObjectPool<T> GetObjectPool<T>() where T : ObjectBase;
IObjectPool<T> GetObjectPool<T>(string name) where T : ObjectBase; IObjectPool<T> GetObjectPool<T>(string name) where T : ObjectBase;
ObjectPoolBase GetObjectPool(Type objectType);
ObjectPoolBase GetObjectPool(Type objectType, string name);
IObjectPool<T> CreatePool<T>(ObjectPoolCreateOptions options = default) where T : ObjectBase; IObjectPool<T> CreatePool<T>(ObjectPoolCreateOptions options = default) where T : ObjectBase;
ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default);
bool DestroyObjectPool<T>() where T : ObjectBase; bool DestroyObjectPool<T>() where T : ObjectBase;
bool DestroyObjectPool<T>(string name) where T : ObjectBase; bool DestroyObjectPool<T>(string name) where T : ObjectBase;
bool DestroyObjectPool(Type objectType);
bool DestroyObjectPool(Type objectType, string name);
bool DestroyObjectPool<T>(IObjectPool<T> objectPool) where T : ObjectBase; bool DestroyObjectPool<T>(IObjectPool<T> objectPool) where T : ObjectBase;
bool DestroyObjectPool(ObjectPoolBase objectPool);
void Release(); void Release();
void ReleaseAllUnused(); void ReleaseAllUnused();

View File

@ -1,18 +0,0 @@
namespace AlicizaX.ObjectPool
{
/// <summary>
/// 可池化对象接口,支持自定义回收和重用逻辑
/// </summary>
public interface IPoolableObject
{
/// <summary>
/// 对象被回收到池中时调用(重置状态)
/// </summary>
void OnRecycle();
/// <summary>
/// 对象从池中取出时调用(初始化状态)
/// </summary>
void OnReuse();
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 0c4c7354bf5820e408151d8ecbd1bab1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6e20a3f64bc47ae4dbf407342897a015
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -54,71 +54,4 @@ 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;
}
}
} }

View File

@ -3,7 +3,9 @@ using UnityEngine;
namespace AlicizaX namespace AlicizaX
{ {
[DisallowMultipleComponent]
[AddComponentMenu("Game Framework/ObjectPool")]
[UnityEngine.Scripting.Preserve]
public sealed class ObjectPoolComponent : MonoBehaviour public sealed class ObjectPoolComponent : MonoBehaviour
{ {
private IObjectPoolService _mObjectPoolService; private IObjectPoolService _mObjectPoolService;

View File

@ -36,11 +36,10 @@ 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 IntOpenHashMap m_TargetMap; private ReferenceOpenHashMap m_TargetMap;
private StringOpenHashMap m_AllNameHeadMap; private StringOpenHashMap m_AllNameHeadMap;
private StringOpenHashMap m_AvailableNameHeadMap; private StringOpenHashMap m_AvailableNameHeadMap;
private StringOpenHashMap m_AvailableNameTailMap; private StringOpenHashMap m_AvailableNameTailMap;
@ -50,7 +49,6 @@ namespace AlicizaX.ObjectPool
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;
@ -66,14 +64,13 @@ namespace AlicizaX.ObjectPool
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, ReleaseStrategy releaseStrategy) float autoReleaseInterval, int capacity, float expireTime, int priority)
: base(name) : base(name)
{ {
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity); int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
m_SlotCapacity = initCap;
m_Slots = SlotArrayPool<ObjectSlot>.Rent(initCap); m_Slots = SlotArrayPool<ObjectSlot>.Rent(initCap);
m_FreeStack = SlotArrayPool<int>.Rent(initCap); m_FreeStack = SlotArrayPool<int>.Rent(initCap);
m_TargetMap = new IntOpenHashMap(initCap); m_TargetMap = new ReferenceOpenHashMap(initCap);
m_AllNameHeadMap = new StringOpenHashMap(initCap); m_AllNameHeadMap = new StringOpenHashMap(initCap);
m_AvailableNameHeadMap = new StringOpenHashMap(initCap); m_AvailableNameHeadMap = new StringOpenHashMap(initCap);
m_AvailableNameTailMap = new StringOpenHashMap(initCap); m_AvailableNameTailMap = new StringOpenHashMap(initCap);
@ -82,7 +79,6 @@ namespace AlicizaX.ObjectPool
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;
@ -163,8 +159,7 @@ namespace AlicizaX.ObjectPool
if (obj == null) return; if (obj == null) return;
if (obj.Target == null) return; if (obj.Target == null) return;
int targetHash = obj.Target.GetHashCode(); if (m_TargetMap.TryGetValue(obj.Target, out int existingIdx) && m_Slots[existingIdx].IsAlive())
if (m_TargetMap.TryGetValue(targetHash, out int existingIdx) && m_Slots[existingIdx].IsAlive())
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
UnityEngine.Debug.LogError($"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'."); UnityEngine.Debug.LogError($"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
@ -185,7 +180,7 @@ namespace AlicizaX.ObjectPool
slot.NextUnused = -1; slot.NextUnused = -1;
slot.SetAlive(true); slot.SetAlive(true);
m_TargetMap.AddOrUpdate(targetHash, idx); m_TargetMap.AddOrUpdate(obj.Target, 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))
@ -251,8 +246,7 @@ namespace AlicizaX.ObjectPool
public void Unspawn(object target) public void Unspawn(object target)
{ {
if (target == null) return; if (target == null) return;
int targetHash = target.GetHashCode(); if (!m_TargetMap.TryGetValue(target, out int idx))
if (!m_TargetMap.TryGetValue(targetHash, out int idx))
{ {
if (m_IsShuttingDown) return; if (m_IsShuttingDown) return;
#if UNITY_EDITOR #if UNITY_EDITOR
@ -339,27 +333,34 @@ namespace AlicizaX.ObjectPool
m_ShrinkCounter = 0; m_ShrinkCounter = 0;
if (m_TargetMap.Count == 0 || m_SlotCapacity <= InitSlotCapacity) int slotArrayLen = m_Slots.Length;
if (m_TargetMap.Count == 0 || slotArrayLen <= InitSlotCapacity)
return; return;
float usageRatio = (float)m_TargetMap.Count / m_SlotCapacity; float usageRatio = (float)m_TargetMap.Count / slotArrayLen;
if (usageRatio < 0.25f) if (usageRatio < 0.25f)
{ {
int targetCapacity = Math.Max(m_SlotCapacity / 2, InitSlotCapacity); int targetCapacity = Math.Max(slotArrayLen / 2, InitSlotCapacity);
if (targetCapacity < m_SlotCapacity) if (targetCapacity < slotArrayLen && m_SlotCount <= targetCapacity)
{ {
var newSlots = SlotArrayPool<ObjectSlot>.Rent(targetCapacity); var newSlots = SlotArrayPool<ObjectSlot>.Rent(targetCapacity);
var newFreeStack = SlotArrayPool<int>.Rent(targetCapacity); var newFreeStack = SlotArrayPool<int>.Rent(targetCapacity);
Array.Copy(m_Slots, 0, newSlots, 0, Math.Min(m_SlotCount, targetCapacity)); Array.Copy(m_Slots, 0, newSlots, 0, m_SlotCount);
Array.Copy(m_FreeStack, 0, newFreeStack, 0, Math.Min(m_FreeTop, targetCapacity));
int newFreeTop = 0;
for (int i = 0; i < m_FreeTop; i++)
{
if (m_FreeStack[i] < targetCapacity)
newFreeStack[newFreeTop++] = m_FreeStack[i];
}
SlotArrayPool<ObjectSlot>.Return(m_Slots, true); SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true); SlotArrayPool<int>.Return(m_FreeStack, true);
m_Slots = newSlots; m_Slots = newSlots;
m_FreeStack = newFreeStack; m_FreeStack = newFreeStack;
m_SlotCapacity = targetCapacity; m_FreeTop = newFreeTop;
} }
} }
} }
@ -388,7 +389,6 @@ namespace AlicizaX.ObjectPool
m_FreeStack = 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;
@ -489,7 +489,7 @@ namespace AlicizaX.ObjectPool
private void GrowSlots() private void GrowSlots()
{ {
int newCap = Math.Max(m_SlotCapacity * 2, InitSlotCapacity); int newCap = Math.Max(m_Slots.Length * 2, InitSlotCapacity);
var newSlots = SlotArrayPool<ObjectSlot>.Rent(newCap); var newSlots = SlotArrayPool<ObjectSlot>.Rent(newCap);
var newFreeStack = SlotArrayPool<int>.Rent(newCap); var newFreeStack = SlotArrayPool<int>.Rent(newCap);
@ -501,7 +501,6 @@ namespace AlicizaX.ObjectPool
m_Slots = newSlots; m_Slots = newSlots;
m_FreeStack = newFreeStack; m_FreeStack = newFreeStack;
m_SlotCapacity = newCap;
} }
private void ReleaseSlot(int idx) private void ReleaseSlot(int idx)
@ -514,8 +513,7 @@ namespace AlicizaX.ObjectPool
MarkSlotUnavailable(idx); MarkSlotUnavailable(idx);
RemoveFromAllNameChain(idx); RemoveFromAllNameChain(idx);
int targetHash = obj.Target.GetHashCode(); m_TargetMap.Remove(obj.Target);
m_TargetMap.Remove(targetHash);
obj.Release(false); obj.Release(false);
MemoryPool.Release(obj); MemoryPool.Release(obj);
@ -530,14 +528,13 @@ namespace AlicizaX.ObjectPool
slot.PrevUnused = -1; slot.PrevUnused = -1;
slot.NextUnused = -1; slot.NextUnused = -1;
if (m_FreeTop >= m_SlotCapacity) if (m_FreeTop >= m_FreeStack.Length)
{ {
int newCap = m_SlotCapacity * 2; int newCap = m_FreeStack.Length * 2;
var newFreeStack = SlotArrayPool<int>.Rent(newCap); var newFreeStack = SlotArrayPool<int>.Rent(newCap);
Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop); Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop);
SlotArrayPool<int>.Return(m_FreeStack, true); SlotArrayPool<int>.Return(m_FreeStack, true);
m_FreeStack = newFreeStack; m_FreeStack = newFreeStack;
m_SlotCapacity = newCap;
} }
m_FreeStack[m_FreeTop++] = idx; m_FreeStack[m_FreeTop++] = idx;
@ -643,13 +640,12 @@ namespace AlicizaX.ObjectPool
private void ShrinkStorageIfEmpty() private void ShrinkStorageIfEmpty()
{ {
if (m_TargetMap.Count > 0 || m_SlotCapacity <= InitSlotCapacity) if (m_TargetMap.Count > 0 || m_Slots.Length <= InitSlotCapacity)
return; return;
SlotArrayPool<ObjectSlot>.Return(m_Slots, true); SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
SlotArrayPool<int>.Return(m_FreeStack, true); SlotArrayPool<int>.Return(m_FreeStack, true);
m_SlotCapacity = InitSlotCapacity;
m_Slots = SlotArrayPool<ObjectSlot>.Rent(InitSlotCapacity); m_Slots = SlotArrayPool<ObjectSlot>.Rent(InitSlotCapacity);
m_FreeStack = SlotArrayPool<int>.Rent(InitSlotCapacity); m_FreeStack = SlotArrayPool<int>.Rent(InitSlotCapacity);
m_AllNameHeadMap.Clear(); m_AllNameHeadMap.Clear();
@ -679,8 +675,7 @@ namespace AlicizaX.ObjectPool
aliveCount++; aliveCount++;
object target = slot.Obj.Target; object target = slot.Obj.Target;
int targetHash = target.GetHashCode(); if (!m_TargetMap.TryGetValue(target, out int mappedIdx) || mappedIdx != i)
if (!m_TargetMap.TryGetValue(targetHash, out int mappedIdx) || mappedIdx != i)
{ {
UnityEngine.Debug.LogError($"Object pool '{FullName}' target index map is inconsistent."); UnityEngine.Debug.LogError($"Object pool '{FullName}' target index map is inconsistent.");
continue; continue;

View File

@ -56,18 +56,6 @@ namespace AlicizaX.ObjectPool
public bool HasObjectPool<T>(string name) where T : ObjectBase public bool HasObjectPool<T>(string name) where T : ObjectBase
=> m_ObjectPools.ContainsKey(new TypeNamePair(typeof(T), name)); => m_ObjectPools.ContainsKey(new TypeNamePair(typeof(T), name));
public bool HasObjectPool(Type objectType)
{
ValidateObjectType(objectType);
return m_ObjectPools.ContainsKey(new TypeNamePair(objectType));
}
public bool HasObjectPool(Type objectType, string name)
{
ValidateObjectType(objectType);
return m_ObjectPools.ContainsKey(new TypeNamePair(objectType, name));
}
// ========== Get ========== // ========== Get ==========
public IObjectPool<T> GetObjectPool<T>() where T : ObjectBase public IObjectPool<T> GetObjectPool<T>() where T : ObjectBase
@ -76,18 +64,6 @@ namespace AlicizaX.ObjectPool
public IObjectPool<T> GetObjectPool<T>(string name) where T : ObjectBase public IObjectPool<T> GetObjectPool<T>(string name) where T : ObjectBase
=> (IObjectPool<T>)InternalGet(new TypeNamePair(typeof(T), name)); => (IObjectPool<T>)InternalGet(new TypeNamePair(typeof(T), name));
public ObjectPoolBase GetObjectPool(Type objectType)
{
ValidateObjectType(objectType);
return InternalGet(new TypeNamePair(objectType));
}
public ObjectPoolBase GetObjectPool(Type objectType, string name)
{
ValidateObjectType(objectType);
return InternalGet(new TypeNamePair(objectType, name));
}
// ========== GetAll ========== // ========== GetAll ==========
int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results) int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results)
@ -134,36 +110,7 @@ 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_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
m_ObjectPoolList.Add(pool);
return pool;
}
public ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default)
{
ValidateObjectType(objectType);
var key = new TypeNamePair(objectType, options.Name);
if (m_ObjectPools.ContainsKey(key))
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError($"Already exist object pool '{key}'.");
#endif
return null;
}
var poolType = typeof(ObjectPool<>).MakeGenericType(objectType);
var pool = (ObjectPoolBase)Activator.CreateInstance(poolType,
options.Name ?? string.Empty,
options.AllowMultiSpawn,
options.AutoReleaseInterval ?? DefaultAutoReleaseInterval,
options.Capacity ?? DefaultCapacity,
options.ExpireTime ?? DefaultExpireTime,
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);
@ -179,18 +126,6 @@ namespace AlicizaX.ObjectPool
public bool DestroyObjectPool<T>(string name) where T : ObjectBase public bool DestroyObjectPool<T>(string name) where T : ObjectBase
=> InternalDestroy(new TypeNamePair(typeof(T), name)); => InternalDestroy(new TypeNamePair(typeof(T), name));
public bool DestroyObjectPool(Type objectType)
{
ValidateObjectType(objectType);
return InternalDestroy(new TypeNamePair(objectType));
}
public bool DestroyObjectPool(Type objectType, string name)
{
ValidateObjectType(objectType);
return InternalDestroy(new TypeNamePair(objectType, name));
}
public bool DestroyObjectPool<T>(IObjectPool<T> objectPool) where T : ObjectBase public bool DestroyObjectPool<T>(IObjectPool<T> objectPool) where T : ObjectBase
{ {
if (objectPool == null) if (objectPool == null)
@ -203,18 +138,6 @@ namespace AlicizaX.ObjectPool
return InternalDestroy(new TypeNamePair(typeof(T), objectPool.Name)); return InternalDestroy(new TypeNamePair(typeof(T), objectPool.Name));
} }
public bool DestroyObjectPool(ObjectPoolBase objectPool)
{
if (objectPool == null)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError("Object pool is invalid.");
#endif
return false;
}
return InternalDestroy(new TypeNamePair(objectPool.ObjectType, objectPool.Name));
}
// ========== Release ========== // ========== Release ==========
public void Release() public void Release()
@ -280,23 +203,6 @@ namespace AlicizaX.ObjectPool
m_ObjectPoolIndexMap[lastPool] = index; m_ObjectPoolIndexMap[lastPool] = index;
} }
private static void ValidateObjectType(Type objectType)
{
if (objectType == null)
{
#if UNITY_EDITOR
UnityEngine.Debug.LogError("Object type is invalid.");
#endif
return;
}
if (!typeof(ObjectBase).IsAssignableFrom(objectType))
{
#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)
=> a.Priority.CompareTo(b.Priority); => a.Priority.CompareTo(b.Priority);
} }

View File

@ -3,11 +3,13 @@ using System.Runtime.CompilerServices;
namespace AlicizaX.ObjectPool namespace AlicizaX.ObjectPool
{ {
/// <summary>
internal struct IntOpenHashMap /// 引用类型键的开放寻址哈希表使用身份相等性ReferenceEquals
/// </summary>
internal struct ReferenceOpenHashMap
{ {
private int[] m_Buckets; private int[] m_Buckets;
private int[] m_Keys; private object[] m_Keys;
private int[] m_Values; private int[] m_Values;
private int[] m_Next; private int[] m_Next;
private int m_Count; private int m_Count;
@ -19,46 +21,52 @@ namespace AlicizaX.ObjectPool
public int Count => m_Count; public int Count => m_Count;
public IntOpenHashMap(int capacity) public ReferenceOpenHashMap(int capacity)
{ {
int cap = NextPowerOf2(Math.Max(capacity, MinCapacity)); int cap = NextPowerOf2(Math.Max(capacity, MinCapacity));
m_Mask = cap - 1; m_Mask = cap - 1;
m_Buckets = new int[cap]; m_Buckets = SlotArrayPool<int>.Rent(cap);
m_Keys = new int[cap]; m_Keys = SlotArrayPool<object>.Rent(cap);
m_Values = new int[cap]; m_Values = SlotArrayPool<int>.Rent(cap);
m_Next = new int[cap]; m_Next = SlotArrayPool<int>.Rent(cap);
Array.Clear(m_Buckets, 0, m_Buckets.Length);
Array.Clear(m_Keys, 0, m_Keys.Length);
Array.Clear(m_Values, 0, m_Values.Length);
Array.Clear(m_Next, 0, m_Next.Length);
m_Count = 0; m_Count = 0;
m_FreeList = 0; m_FreeList = 0;
m_AllocCount = 0; m_AllocCount = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(int key, out int value) public bool TryGetValue(object key, out int value)
{ {
if (m_Buckets == null) { value = -1; return false; } if (m_Buckets == null || key == null) { value = -1; return false; }
int i = m_Buckets[(key & 0x7FFFFFFF) & m_Mask]; int hash = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
int i = m_Buckets[hash & m_Mask];
while (i > 0) while (i > 0)
{ {
int idx = i - 1; int idx = i - 1;
if (m_Keys[idx] == key) { value = m_Values[idx]; return true; } if (ReferenceEquals(m_Keys[idx], key)) { value = m_Values[idx]; return true; }
i = m_Next[idx]; i = m_Next[idx];
} }
value = -1; value = -1;
return false; return false;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddOrUpdate(int key, int value) public void AddOrUpdate(object key, int value)
{ {
if (key == null) return;
if (m_Count >= ((m_Mask + 1) * 3 >> 2)) if (m_Count >= ((m_Mask + 1) * 3 >> 2))
Grow(); Grow();
int bucket = (key & 0x7FFFFFFF) & m_Mask; int hash = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
int bucket = hash & m_Mask;
int i = m_Buckets[bucket]; int i = m_Buckets[bucket];
while (i > 0) while (i > 0)
{ {
int ei = i - 1; int ei = i - 1;
if (m_Keys[ei] == key) { m_Values[ei] = value; return; } if (ReferenceEquals(m_Keys[ei], key)) { m_Values[ei] = value; return; }
i = m_Next[ei]; i = m_Next[ei];
} }
@ -70,7 +78,7 @@ namespace AlicizaX.ObjectPool
} }
else else
{ {
if (m_AllocCount > m_Mask) { Grow(); bucket = (key & 0x7FFFFFFF) & m_Mask; } if (m_AllocCount > m_Mask) { Grow(); bucket = hash & m_Mask; }
idx = m_AllocCount++; idx = m_AllocCount++;
} }
@ -82,20 +90,21 @@ namespace AlicizaX.ObjectPool
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Remove(int key) public bool Remove(object key)
{ {
if (m_Buckets == null) return false; if (m_Buckets == null || key == null) return false;
int bucket = (key & 0x7FFFFFFF) & m_Mask; int hash = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
int bucket = hash & m_Mask;
int prev = 0; int prev = 0;
int i = m_Buckets[bucket]; int i = m_Buckets[bucket];
while (i > 0) while (i > 0)
{ {
int idx = i - 1; int idx = i - 1;
if (m_Keys[idx] == key) if (ReferenceEquals(m_Keys[idx], key))
{ {
if (prev == 0) m_Buckets[bucket] = m_Next[idx]; if (prev == 0) m_Buckets[bucket] = m_Next[idx];
else m_Next[prev - 1] = m_Next[idx]; else m_Next[prev - 1] = m_Next[idx];
m_Keys[idx] = 0; m_Keys[idx] = null;
m_Values[idx] = -1; m_Values[idx] = -1;
m_Next[idx] = m_FreeList; m_Next[idx] = m_FreeList;
m_FreeList = idx + 1; m_FreeList = idx + 1;
@ -107,7 +116,6 @@ namespace AlicizaX.ObjectPool
} }
return false; return false;
} }
public void Clear() public void Clear()
{ {
if (m_Buckets == null) return; if (m_Buckets == null) return;
@ -126,10 +134,12 @@ namespace AlicizaX.ObjectPool
int newCap = (m_Mask + 1) << 1; int newCap = (m_Mask + 1) << 1;
if (newCap < MinCapacity) newCap = MinCapacity; if (newCap < MinCapacity) newCap = MinCapacity;
int newMask = newCap - 1; int newMask = newCap - 1;
var newBuckets = new int[newCap]; var newBuckets = SlotArrayPool<int>.Rent(newCap);
var newKeys = new int[newCap]; var newKeys = SlotArrayPool<object>.Rent(newCap);
var newValues = new int[newCap]; var newValues = SlotArrayPool<int>.Rent(newCap);
var newNext = new int[newCap]; var newNext = SlotArrayPool<int>.Rent(newCap);
Array.Clear(newBuckets, 0, newBuckets.Length);
Array.Clear(newNext, 0, newNext.Length);
int newAlloc = 0; int newAlloc = 0;
int oldCap = m_Mask + 1; int oldCap = m_Mask + 1;
@ -142,13 +152,19 @@ namespace AlicizaX.ObjectPool
int ni = newAlloc++; int ni = newAlloc++;
newKeys[ni] = m_Keys[old]; newKeys[ni] = m_Keys[old];
newValues[ni] = m_Values[old]; newValues[ni] = m_Values[old];
int nb = (newKeys[ni] & 0x7FFFFFFF) & newMask; int hash = RuntimeHelpers.GetHashCode(newKeys[ni]) & 0x7FFFFFFF;
int nb = hash & newMask;
newNext[ni] = newBuckets[nb]; newNext[ni] = newBuckets[nb];
newBuckets[nb] = ni + 1; newBuckets[nb] = ni + 1;
i = m_Next[old]; i = m_Next[old];
} }
} }
SlotArrayPool<int>.Return(m_Buckets, true);
SlotArrayPool<object>.Return(m_Keys, true);
SlotArrayPool<int>.Return(m_Values, true);
SlotArrayPool<int>.Return(m_Next, true);
m_Buckets = newBuckets; m_Buckets = newBuckets;
m_Keys = newKeys; m_Keys = newKeys;
m_Values = newValues; m_Values = newValues;

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 311d5e5b578ed15428d9565ab237becc guid: 0c2c880135959a54f95efaa6ef962867
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@ -1,28 +0,0 @@
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
}
}

View File

@ -25,10 +25,14 @@ namespace AlicizaX.ObjectPool
{ {
int cap = NextPowerOf2(Math.Max(capacity, MinCapacity)); int cap = NextPowerOf2(Math.Max(capacity, MinCapacity));
m_Mask = cap - 1; m_Mask = cap - 1;
m_Buckets = new int[cap]; m_Buckets = SlotArrayPool<int>.Rent(cap);
m_Keys = new string[cap]; m_Keys = SlotArrayPool<string>.Rent(cap);
m_Values = new int[cap]; m_Values = SlotArrayPool<int>.Rent(cap);
m_Next = new int[cap]; m_Next = SlotArrayPool<int>.Rent(cap);
Array.Clear(m_Buckets, 0, m_Buckets.Length);
Array.Clear(m_Keys, 0, m_Keys.Length);
Array.Clear(m_Values, 0, m_Values.Length);
Array.Clear(m_Next, 0, m_Next.Length);
m_Count = 0; m_Count = 0;
m_FreeList = 0; m_FreeList = 0;
m_AllocCount = 0; m_AllocCount = 0;
@ -138,10 +142,12 @@ namespace AlicizaX.ObjectPool
int newCap = (m_Mask + 1) << 1; int newCap = (m_Mask + 1) << 1;
if (newCap < MinCapacity) newCap = MinCapacity; if (newCap < MinCapacity) newCap = MinCapacity;
int newMask = newCap - 1; int newMask = newCap - 1;
var newBuckets = new int[newCap]; var newBuckets = SlotArrayPool<int>.Rent(newCap);
var newKeys = new string[newCap]; var newKeys = SlotArrayPool<string>.Rent(newCap);
var newValues = new int[newCap]; var newValues = SlotArrayPool<int>.Rent(newCap);
var newNext = new int[newCap]; var newNext = SlotArrayPool<int>.Rent(newCap);
Array.Clear(newBuckets, 0, newBuckets.Length);
Array.Clear(newNext, 0, newNext.Length);
int newAlloc = 0; int newAlloc = 0;
int oldCap = m_Mask + 1; int oldCap = m_Mask + 1;
@ -162,6 +168,11 @@ namespace AlicizaX.ObjectPool
} }
} }
SlotArrayPool<int>.Return(m_Buckets, true);
SlotArrayPool<string>.Return(m_Keys, true);
SlotArrayPool<int>.Return(m_Values, true);
SlotArrayPool<int>.Return(m_Next, true);
m_Buckets = newBuckets; m_Buckets = newBuckets;
m_Keys = newKeys; m_Keys = newKeys;
m_Values = newValues; m_Values = newValues;

View File

@ -5,13 +5,13 @@ namespace AlicizaX.Timer.Runtime
[UnityEngine.Scripting.Preserve] [UnityEngine.Scripting.Preserve]
public interface ITimerService : IService public interface ITimerService : IService
{ {
int AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false); ulong AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false);
int AddTimer<T>(Action<T> callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class; ulong AddTimer<T>(Action<T> callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class;
void Stop(int timerId); void Stop(ulong timerHandle);
void Resume(int timerId); void Resume(ulong timerHandle);
bool IsRunning(int timerId); bool IsRunning(ulong timerHandle);
float GetLeftTime(int timerId); float GetLeftTime(ulong timerHandle);
void Restart(int timerId); void Restart(ulong timerHandle);
void RemoveTimer(int timerId); void RemoveTimer(ulong timerHandle);
} }
} }

View File

@ -1,14 +1,19 @@
namespace AlicizaX.Timer.Runtime namespace AlicizaX.Timer.Runtime
{ {
internal static class TimerDebugFlags
{
public const byte Running = 1 << 0;
public const byte Loop = 1 << 1;
public const byte Unscaled = 1 << 2;
}
internal struct TimerDebugInfo internal struct TimerDebugInfo
{ {
public int TimerId; public ulong TimerHandle;
public float LeftTime; public float LeftTime;
public float Duration; public float Duration;
public bool IsLoop; public float Age;
public bool IsRunning; public byte Flags;
public bool IsUnscaled;
public float CreationTime;
} }
internal interface ITimerServiceDebug internal interface ITimerServiceDebug

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ namespace AlicizaX.UI.Runtime
internal sealed partial class UIService internal sealed partial class UIService
{ {
private GameObject m_LayerBlock; private GameObject m_LayerBlock;
private int m_LastCountDownGuid; private ulong m_LastCountDownHandle;
private void InitUIBlock() private void InitUIBlock()
{ {
@ -23,21 +23,21 @@ namespace AlicizaX.UI.Runtime
public void SetUIBlock(float timeDuration) public void SetUIBlock(float timeDuration)
{ {
ITimerService timerService = GetTimerService(); ITimerService timerService = GetTimerService();
if (m_LastCountDownGuid != 0) if (m_LastCountDownHandle != 0UL)
{ {
timerService.RemoveTimer(m_LastCountDownGuid); timerService.RemoveTimer(m_LastCountDownHandle);
} }
SetLayerBlockOption(true); SetLayerBlockOption(true);
m_LastCountDownGuid = timerService.AddTimer(OnBlockCountDown, timeDuration); m_LastCountDownHandle = timerService.AddTimer(OnBlockCountDown, timeDuration);
} }
public void ForceExitBlock() public void ForceExitBlock()
{ {
ITimerService timerService = GetTimerService(); ITimerService timerService = GetTimerService();
if (m_LastCountDownGuid != 0) if (m_LastCountDownHandle != 0UL)
{ {
timerService.RemoveTimer(m_LastCountDownGuid); timerService.RemoveTimer(m_LastCountDownHandle);
} }
RecoverLayerOptionAll(); RecoverLayerOptionAll();
@ -56,7 +56,7 @@ namespace AlicizaX.UI.Runtime
public void RecoverLayerOptionAll() public void RecoverLayerOptionAll()
{ {
SetLayerBlockOption(false); SetLayerBlockOption(false);
m_LastCountDownGuid = 0; m_LastCountDownHandle = 0UL;
} }
} }
} }

View File

@ -10,12 +10,12 @@ namespace AlicizaX.UI.Runtime
private readonly struct CacheEntry private readonly struct CacheEntry
{ {
public readonly UIMetadata Metadata; public readonly UIMetadata Metadata;
public readonly int TimerId; public readonly ulong TimerHandle;
public CacheEntry(UIMetadata metadata, int timerId) public CacheEntry(UIMetadata metadata, ulong timerHandle)
{ {
Metadata = metadata; Metadata = metadata;
TimerId = timerId; TimerHandle = timerHandle;
} }
} }
@ -36,27 +36,27 @@ namespace AlicizaX.UI.Runtime
} }
RemoveFromCache(uiMetadata.MetaInfo.RuntimeTypeHandle); RemoveFromCache(uiMetadata.MetaInfo.RuntimeTypeHandle);
int timerId = -1; ulong timerHandle = 0UL;
uiMetadata.View.Holder.transform.SetParent(UICacheLayer); uiMetadata.View.Holder.transform.SetParent(UICacheLayer);
if (uiMetadata.MetaInfo.CacheTime > 0) if (uiMetadata.MetaInfo.CacheTime > 0)
{ {
ITimerService timerService = GetTimerService(); ITimerService timerService = GetTimerService();
timerId = timerService.AddTimer( timerHandle = timerService.AddTimer(
OnTimerDisposeWindow, OnTimerDisposeWindow,
uiMetadata, uiMetadata,
uiMetadata.MetaInfo.CacheTime, uiMetadata.MetaInfo.CacheTime,
isLoop: false, isLoop: false,
isUnscaled: true); isUnscaled: true);
if (timerId <= 0) if (timerHandle == 0UL)
{ {
Log.Warning($"Failed to create cache timer for {uiMetadata.UILogicType.Name}"); Log.Warning($"Failed to create cache timer for {uiMetadata.UILogicType.Name}");
} }
} }
uiMetadata.InCache = true; uiMetadata.InCache = true;
m_CacheWindow.Add(uiMetadata.MetaInfo.RuntimeTypeHandle, new CacheEntry(uiMetadata, timerId)); m_CacheWindow.Add(uiMetadata.MetaInfo.RuntimeTypeHandle, new CacheEntry(uiMetadata, timerHandle));
} }
private void OnTimerDisposeWindow(UIMetadata meta) private void OnTimerDisposeWindow(UIMetadata meta)
@ -74,9 +74,9 @@ namespace AlicizaX.UI.Runtime
{ {
m_CacheWindow.Remove(typeHandle); m_CacheWindow.Remove(typeHandle);
entry.Metadata.InCache = false; entry.Metadata.InCache = false;
if (entry.TimerId > 0 && _timerService != null) if (entry.TimerHandle != 0UL && _timerService != null)
{ {
_timerService.RemoveTimer(entry.TimerId); _timerService.RemoveTimer(entry.TimerHandle);
} }
} }
} }

View File

@ -144,9 +144,9 @@ namespace AlicizaX.UI.Runtime
continue; continue;
} }
if (entry.TimerId > 0 && _timerService != null) if (entry.TimerHandle != 0UL && _timerService != null)
{ {
_timerService.RemoveTimer(entry.TimerId); _timerService.RemoveTimer(entry.TimerHandle);
} }
entry.Metadata.InCache = false; entry.Metadata.InCache = false;
@ -157,10 +157,10 @@ namespace AlicizaX.UI.Runtime
m_CacheWindow.Clear(); m_CacheWindow.Clear();
} }
if (m_LastCountDownGuid != 0 && _timerService != null) if (m_LastCountDownHandle != 0UL && _timerService != null)
{ {
_timerService.RemoveTimer(m_LastCountDownGuid); _timerService.RemoveTimer(m_LastCountDownHandle);
m_LastCountDownGuid = 0; m_LastCountDownHandle = 0UL;
} }
if (m_LayerBlock != null) if (m_LayerBlock != null)