[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))]
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 TimerDebugInfo[] _timerBuffer;
@ -95,20 +95,23 @@ namespace AlicizaX.Timer.Editor
int timerCount = debug.GetAllTimers(_timerBuffer);
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++)
{
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(
"ID {0} | {1} | {2} | {3}",
timer.TimerId,
timer.IsLoop ? "Loop" : "Once",
timer.IsUnscaled ? "Unscaled" : "Scaled",
timer.IsRunning ? "Running" : "Paused");
timer.TimerHandle,
isLoop ? "Loop" : "Once",
isUnscaled ? "Unscaled" : "Scaled",
isRunning ? "Running" : "Paused");
string value = Utility.Text.Format(
"Left {0:F2}s | Duration {1:F2}s",
@ -142,26 +145,28 @@ namespace AlicizaX.Timer.Editor
{
TimerDebugInfo staleTimer = _leakBuffer[i];
EditorGUILayout.LabelField(
Utility.Text.Format("ID {0}", staleTimer.TimerId),
Utility.Text.Format("Created {0:F1}s ago", staleTimer.CreationTime));
Utility.Text.Format("ID {0}", staleTimer.TimerHandle),
Utility.Text.Format("Created {0:F1}s ago", staleTimer.Age));
}
}
#endif
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
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

View File

@ -9,31 +9,53 @@ namespace AlicizaX.Debugger.Runtime
private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase
{
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
{
public VisualElement Root;
public Label Title;
public Label Value;
public VisualElement LoopIndicator;
public VisualElement ScaleIndicator;
public VisualElement StateIndicator;
public VisualElement Fill;
}
private ITimerServiceDebug _mTimerDebug;
private TimerDebugInfo[] m_TimerInfos;
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 TimerDebugInfo[] m_TimerInfos = new TimerDebugInfo[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)
{
_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)
{
if (_mTimerDebug == null)
@ -44,91 +66,61 @@ namespace AlicizaX.Debugger.Runtime
root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText));
VisualElement overview = CreateSection("Timer Pool Overview", out VisualElement overviewCard);
m_ActiveCountLabel = AddTextRow(overviewCard, "Active Timer Count").Value;
m_PoolCapacityLabel = AddTextRow(overviewCard, "Pool Capacity").Value;
m_PeakCountLabel = AddTextRow(overviewCard, "Peak Active Count").Value;
m_FreeCountLabel = AddTextRow(overviewCard, "Free Count").Value;
m_UsageLabel = AddTextRow(overviewCard, "Pool Usage").Value;
overviewCard.Add(CreateUsageRow("Active Usage", DebuggerTheme.Accent, out m_ActiveUsageFill));
overviewCard.Add(CreateUsageRow("Peak Usage", DebuggerTheme.Warning, out m_PeakUsageFill));
overviewCard.Add(CreateUsageRow("Free Capacity", DebuggerTheme.Positive, out m_FreeUsageFill));
root.Add(overview);
VisualElement section = CreateSection("Active Timers", out VisualElement timerCard);
m_SectionTitleLabel = section.ElementAt(0) as Label;
m_WarningLabel = new Label();
m_WarningLabel.style.color = new Color(1f, 0.5f, 0f);
m_WarningLabel.style.display = DisplayStyle.None;
m_WarningLabel.style.marginBottom = 4f;
timerCard.Add(m_WarningLabel);
VisualElement sample = CreateSection("Timer Sample", out VisualElement sampleCard);
sampleCard.Add(CreateNoteLabel(SAMPLE_NOTE, DebuggerTheme.SecondaryText));
m_OverflowNote = CreateNoteLabel(OVERFLOW_NOTE, new Color(1f, 0.5f, 0f));
m_OverflowNote.style.display = DisplayStyle.None;
sampleCard.Add(m_OverflowNote);
m_EmptyRow = AddTextRow(timerCard, string.Empty);
m_EmptyRow.Root.style.display = DisplayStyle.None;
m_EmptyNote = CreateNoteLabel(EMPTY_NOTE, DebuggerTheme.SecondaryText);
m_EmptyNote.style.display = DisplayStyle.None;
sampleCard.Add(m_EmptyNote);
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;
}
root.Add(section);
root.Add(sample);
RefreshContent();
}
private void RefreshContent()
{
if (_mTimerDebug == null)
if (_mTimerDebug == null || m_ActiveUsageFill == null)
{
return;
}
_mTimerDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
float poolUsage = poolCapacity > 0 ? (float)activeCount / poolCapacity : 0f;
m_ActiveCountLabel.text = activeCount.ToString();
m_PoolCapacityLabel.text = poolCapacity.ToString();
m_PeakCountLabel.text = peakActiveCount.ToString();
m_FreeCountLabel.text = freeCount.ToString();
m_UsageLabel.text = Utility.Text.Format("{0:P1}", poolUsage);
float capacity = poolCapacity > 0 ? poolCapacity : 1f;
SetFillRatio(m_ActiveUsageFill, activeCount / capacity);
SetFillRatio(m_PeakUsageFill, peakActiveCount / capacity);
SetFillRatio(m_FreeUsageFill, freeCount / capacity);
if (activeCount <= 0)
{
m_SectionTitleLabel.text = "Active Timers";
m_WarningLabel.style.display = DisplayStyle.None;
m_EmptyRow.Root.style.display = DisplayStyle.Flex;
m_EmptyRow.Title.text = "Status";
m_EmptyRow.Value.text = "No active timers";
m_EmptyNote.style.display = DisplayStyle.Flex;
m_OverflowNote.style.display = DisplayStyle.None;
SetTimerRowsVisible(0);
return;
}
EnsureTimerInfoBuffer(activeCount);
m_EmptyNote.style.display = DisplayStyle.None;
int timerCount = _mTimerDebug.GetAllTimers(m_TimerInfos);
int displayCount = timerCount > MAX_DISPLAY_COUNT ? MAX_DISPLAY_COUNT : timerCount;
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;
}
m_OverflowNote.style.display = activeCount > MAX_DISPLAY_COUNT ? DisplayStyle.Flex : DisplayStyle.None;
for (int i = 0; i < displayCount; i++)
{
ref RowView row = ref m_TimerRows[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;
UpdateTimerRow(ref m_TimerRows[i], ref m_TimerInfos[i]);
}
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;
VisualElement row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.style.minHeight = 36f * scale;
row.style.height = 20f * scale;
row.style.marginBottom = 4f * scale;
Label titleLabel = new Label(title);
titleLabel.style.minWidth = 280f * scale;
titleLabel.style.maxWidth = 280f * scale;
titleLabel.style.color = DebuggerTheme.SecondaryText;
titleLabel.style.fontSize = 18f * scale;
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
titleLabel.style.flexShrink = 0f;
titleLabel.style.whiteSpace = WhiteSpace.Normal;
VisualElement loopIndicator = CreateIndicator(6f * scale, DebuggerTheme.Accent);
VisualElement scaleIndicator = CreateIndicator(6f * scale, DebuggerTheme.Warning);
VisualElement stateIndicator = CreateIndicator(6f * scale, DebuggerTheme.Positive);
Label valueLabel = new Label();
valueLabel.style.flexGrow = 1f;
valueLabel.style.color = DebuggerTheme.PrimaryText;
valueLabel.style.fontSize = 18f * scale;
valueLabel.style.whiteSpace = WhiteSpace.Normal;
row.Add(loopIndicator);
row.Add(scaleIndicator);
row.Add(stateIndicator);
row.Add(titleLabel);
row.Add(valueLabel);
VisualElement track = new VisualElement();
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);
RowView view;
view.Root = row;
view.Title = titleLabel;
view.Value = valueLabel;
view.LoopIndicator = loopIndicator;
view.ScaleIndicator = scaleIndicator;
view.StateIndicator = stateIndicator;
view.Fill = fill;
return view;
}
private int EnsureTimerInfoBuffer(int count)
private static VisualElement CreateIndicator(float size, Color color)
{
if (count <= 0)
{
if (m_TimerInfos == null || m_TimerInfos.Length == 0)
{
m_TimerInfos = new TimerDebugInfo[1];
}
return 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;
}
if (m_TimerInfos == null || m_TimerInfos.Length < count)
private static void UpdateTimerRow(ref RowView row, ref TimerDebugInfo info)
{
m_TimerInfos = new TimerDebugInfo[count];
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)
{
ratio = 0f;
}
else if (ratio > 1f)
{
ratio = 1f;
}
return 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)
{
ratio = 0f;
}
else if (ratio > 1f)
{
ratio = 1f;
}
fill.style.width = Length.Percent(ratio * 100f);
}
}
}

View File

@ -1,4 +1,3 @@
using System;
namespace AlicizaX.ObjectPool
{
public readonly struct ObjectPoolCreateOptions
@ -9,7 +8,6 @@ namespace AlicizaX.ObjectPool
public readonly int? Capacity;
public readonly float? ExpireTime;
public readonly int Priority;
public readonly ReleaseStrategy ReleaseStrategy;
public ObjectPoolCreateOptions(
string name = "",
@ -17,8 +15,7 @@ namespace AlicizaX.ObjectPool
float? autoReleaseInterval = null,
int? capacity = null,
float? expireTime = null,
int priority = 0,
ReleaseStrategy releaseStrategy = ReleaseStrategy.LRU)
int priority = 0)
{
Name = name ?? string.Empty;
AllowMultiSpawn = allowMultiSpawn;
@ -26,11 +23,10 @@ namespace AlicizaX.ObjectPool
Capacity = capacity;
ExpireTime = expireTime;
Priority = priority;
ReleaseStrategy = releaseStrategy;
}
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 = "")
=> new ObjectPoolCreateOptions(name: name);
@ -46,23 +42,15 @@ namespace AlicizaX.ObjectPool
bool HasObjectPool<T>() 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>(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;
ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default);
bool DestroyObjectPool<T>() 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(ObjectPoolBase objectPool);
void Release();
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;
}
}
/// <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
{
[DisallowMultipleComponent]
[AddComponentMenu("Game Framework/ObjectPool")]
[UnityEngine.Scripting.Preserve]
public sealed class ObjectPoolComponent : MonoBehaviour
{
private IObjectPoolService _mObjectPoolService;

View File

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

View File

@ -56,18 +56,6 @@ namespace AlicizaX.ObjectPool
public bool HasObjectPool<T>(string name) where T : ObjectBase
=> 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 ==========
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
=> (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 ==========
int IObjectPoolServiceDebugView.GetAllObjectPools(bool sort, ObjectPoolBase[] results)
@ -134,36 +110,7 @@ namespace AlicizaX.ObjectPool
options.AutoReleaseInterval ?? DefaultAutoReleaseInterval,
options.Capacity ?? DefaultCapacity,
options.ExpireTime ?? DefaultExpireTime,
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);
options.Priority);
m_ObjectPools.Add(key, pool);
m_ObjectPoolIndexMap.Add(pool, m_ObjectPoolList.Count);
@ -179,18 +126,6 @@ namespace AlicizaX.ObjectPool
public bool DestroyObjectPool<T>(string name) where T : ObjectBase
=> 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
{
if (objectPool == null)
@ -203,18 +138,6 @@ namespace AlicizaX.ObjectPool
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 ==========
public void Release()
@ -280,23 +203,6 @@ namespace AlicizaX.ObjectPool
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)
=> a.Priority.CompareTo(b.Priority);
}

View File

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

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 311d5e5b578ed15428d9565ab237becc
guid: 0c2c880135959a54f95efaa6ef962867
MonoImporter:
externalObjects: {}
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));
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_Buckets = SlotArrayPool<int>.Rent(cap);
m_Keys = SlotArrayPool<string>.Rent(cap);
m_Values = SlotArrayPool<int>.Rent(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_FreeList = 0;
m_AllocCount = 0;
@ -138,10 +142,12 @@ namespace AlicizaX.ObjectPool
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];
var newBuckets = SlotArrayPool<int>.Rent(newCap);
var newKeys = SlotArrayPool<string>.Rent(newCap);
var newValues = SlotArrayPool<int>.Rent(newCap);
var newNext = SlotArrayPool<int>.Rent(newCap);
Array.Clear(newBuckets, 0, newBuckets.Length);
Array.Clear(newNext, 0, newNext.Length);
int newAlloc = 0;
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_Keys = newKeys;
m_Values = newValues;

View File

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

View File

@ -1,14 +1,19 @@
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
{
public int TimerId;
public ulong TimerHandle;
public float LeftTime;
public float Duration;
public bool IsLoop;
public bool IsRunning;
public bool IsUnscaled;
public float CreationTime;
public float Age;
public byte Flags;
}
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
{
private GameObject m_LayerBlock;
private int m_LastCountDownGuid;
private ulong m_LastCountDownHandle;
private void InitUIBlock()
{
@ -23,21 +23,21 @@ namespace AlicizaX.UI.Runtime
public void SetUIBlock(float timeDuration)
{
ITimerService timerService = GetTimerService();
if (m_LastCountDownGuid != 0)
if (m_LastCountDownHandle != 0UL)
{
timerService.RemoveTimer(m_LastCountDownGuid);
timerService.RemoveTimer(m_LastCountDownHandle);
}
SetLayerBlockOption(true);
m_LastCountDownGuid = timerService.AddTimer(OnBlockCountDown, timeDuration);
m_LastCountDownHandle = timerService.AddTimer(OnBlockCountDown, timeDuration);
}
public void ForceExitBlock()
{
ITimerService timerService = GetTimerService();
if (m_LastCountDownGuid != 0)
if (m_LastCountDownHandle != 0UL)
{
timerService.RemoveTimer(m_LastCountDownGuid);
timerService.RemoveTimer(m_LastCountDownHandle);
}
RecoverLayerOptionAll();
@ -56,7 +56,7 @@ namespace AlicizaX.UI.Runtime
public void RecoverLayerOptionAll()
{
SetLayerBlockOption(false);
m_LastCountDownGuid = 0;
m_LastCountDownHandle = 0UL;
}
}
}

View File

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