diff --git a/Editor/Timer/TimerComponentInspector.cs b/Editor/Timer/TimerComponentInspector.cs index 75da6ce..f0045bd 100644 --- a/Editor/Timer/TimerComponentInspector.cs +++ b/Editor/Timer/TimerComponentInspector.cs @@ -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 diff --git a/Runtime/Debugger/DebuggerComponent.TimerInformationWindow.cs b/Runtime/Debugger/DebuggerComponent.TimerInformationWindow.cs index e729dcc..b02d7fb 100644 --- a/Runtime/Debugger/DebuggerComponent.TimerInformationWindow.cs +++ b/Runtime/Debugger/DebuggerComponent.TimerInformationWindow.cs @@ -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() 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) + 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) - { - m_TimerInfos = new TimerDebugInfo[1]; - } - return 0; + ratio = 0f; + } + else if (ratio > 1f) + { + 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); } } } diff --git a/Runtime/ObjectPool/IObjectPoolService.cs b/Runtime/ObjectPool/IObjectPoolService.cs index 7c75286..6bcca89 100644 --- a/Runtime/ObjectPool/IObjectPoolService.cs +++ b/Runtime/ObjectPool/IObjectPoolService.cs @@ -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() where T : ObjectBase; bool HasObjectPool(string name) where T : ObjectBase; - bool HasObjectPool(Type objectType); - bool HasObjectPool(Type objectType, string name); IObjectPool GetObjectPool() where T : ObjectBase; IObjectPool GetObjectPool(string name) where T : ObjectBase; - ObjectPoolBase GetObjectPool(Type objectType); - ObjectPoolBase GetObjectPool(Type objectType, string name); IObjectPool CreatePool(ObjectPoolCreateOptions options = default) where T : ObjectBase; - ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default); bool DestroyObjectPool() where T : ObjectBase; bool DestroyObjectPool(string name) where T : ObjectBase; - bool DestroyObjectPool(Type objectType); - bool DestroyObjectPool(Type objectType, string name); bool DestroyObjectPool(IObjectPool objectPool) where T : ObjectBase; - bool DestroyObjectPool(ObjectPoolBase objectPool); void Release(); void ReleaseAllUnused(); diff --git a/Runtime/ObjectPool/IPoolableObject.cs b/Runtime/ObjectPool/IPoolableObject.cs deleted file mode 100644 index 1aad388..0000000 --- a/Runtime/ObjectPool/IPoolableObject.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace AlicizaX.ObjectPool -{ - /// - /// 可池化对象接口,支持自定义回收和重用逻辑 - /// - public interface IPoolableObject - { - /// - /// 对象被回收到池中时调用(重置状态) - /// - void OnRecycle(); - - /// - /// 对象从池中取出时调用(初始化状态) - /// - void OnReuse(); - } -} diff --git a/Runtime/ObjectPool/IPoolableObject.cs.meta b/Runtime/ObjectPool/IPoolableObject.cs.meta deleted file mode 100644 index 876e9d4..0000000 --- a/Runtime/ObjectPool/IPoolableObject.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0c4c7354bf5820e408151d8ecbd1bab1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/ObjectPool/IntOpenHashMap.cs.meta b/Runtime/ObjectPool/IntOpenHashMap.cs.meta deleted file mode 100644 index a5d1a3a..0000000 --- a/Runtime/ObjectPool/IntOpenHashMap.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6e20a3f64bc47ae4dbf407342897a015 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/ObjectPool/ObjectBase.cs b/Runtime/ObjectPool/ObjectBase.cs index dd6704d..9ae0c1d 100644 --- a/Runtime/ObjectPool/ObjectBase.cs +++ b/Runtime/ObjectPool/ObjectBase.cs @@ -54,71 +54,4 @@ namespace AlicizaX.ObjectPool m_LastUseTime = 0f; } } - - /// - /// 泛型对象池基类,消除装箱开销 - /// - public abstract class ObjectBase : 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; - } - } } diff --git a/Runtime/ObjectPool/ObjectPoolComponent.cs b/Runtime/ObjectPool/ObjectPoolComponent.cs index a2646e9..dfd3ccc 100644 --- a/Runtime/ObjectPool/ObjectPoolComponent.cs +++ b/Runtime/ObjectPool/ObjectPoolComponent.cs @@ -3,7 +3,9 @@ using UnityEngine; namespace AlicizaX { - + [DisallowMultipleComponent] + [AddComponentMenu("Game Framework/ObjectPool")] + [UnityEngine.Scripting.Preserve] public sealed class ObjectPoolComponent : MonoBehaviour { private IObjectPoolService _mObjectPoolService; diff --git a/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs b/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs index e6c00f1..f7b0231 100644 --- a/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs +++ b/Runtime/ObjectPool/ObjectPoolService.ObjectPool.cs @@ -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.Rent(initCap); m_FreeStack = SlotArrayPool.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.Rent(targetCapacity); var newFreeStack = SlotArrayPool.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.Return(m_Slots, true); SlotArrayPool.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.Rent(newCap); var newFreeStack = SlotArrayPool.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.Rent(newCap); Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop); SlotArrayPool.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.Return(m_Slots, true); SlotArrayPool.Return(m_FreeStack, true); - m_SlotCapacity = InitSlotCapacity; m_Slots = SlotArrayPool.Rent(InitSlotCapacity); m_FreeStack = SlotArrayPool.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; diff --git a/Runtime/ObjectPool/ObjectPoolService.cs b/Runtime/ObjectPool/ObjectPoolService.cs index a911cde..967f754 100644 --- a/Runtime/ObjectPool/ObjectPoolService.cs +++ b/Runtime/ObjectPool/ObjectPoolService.cs @@ -56,18 +56,6 @@ namespace AlicizaX.ObjectPool public bool HasObjectPool(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 GetObjectPool() where T : ObjectBase @@ -76,18 +64,6 @@ namespace AlicizaX.ObjectPool public IObjectPool GetObjectPool(string name) where T : ObjectBase => (IObjectPool)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(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(IObjectPool 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); } diff --git a/Runtime/ObjectPool/IntOpenHashMap.cs b/Runtime/ObjectPool/ReferenceOpenHashMap.cs similarity index 61% rename from Runtime/ObjectPool/IntOpenHashMap.cs rename to Runtime/ObjectPool/ReferenceOpenHashMap.cs index 0bfce09..6f88d09 100644 --- a/Runtime/ObjectPool/IntOpenHashMap.cs +++ b/Runtime/ObjectPool/ReferenceOpenHashMap.cs @@ -3,11 +3,13 @@ using System.Runtime.CompilerServices; namespace AlicizaX.ObjectPool { - - internal struct IntOpenHashMap + /// + /// 引用类型键的开放寻址哈希表,使用身份相等性(ReferenceEquals) + /// + 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.Rent(cap); + m_Keys = SlotArrayPool.Rent(cap); + m_Values = SlotArrayPool.Rent(cap); + m_Next = SlotArrayPool.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.Rent(newCap); + var newKeys = SlotArrayPool.Rent(newCap); + var newValues = SlotArrayPool.Rent(newCap); + var newNext = SlotArrayPool.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.Return(m_Buckets, true); + SlotArrayPool.Return(m_Keys, true); + SlotArrayPool.Return(m_Values, true); + SlotArrayPool.Return(m_Next, true); + m_Buckets = newBuckets; m_Keys = newKeys; m_Values = newValues; diff --git a/Runtime/ObjectPool/ReleaseStrategy.cs.meta b/Runtime/ObjectPool/ReferenceOpenHashMap.cs.meta similarity index 83% rename from Runtime/ObjectPool/ReleaseStrategy.cs.meta rename to Runtime/ObjectPool/ReferenceOpenHashMap.cs.meta index 5ecc7ad..bdc6968 100644 --- a/Runtime/ObjectPool/ReleaseStrategy.cs.meta +++ b/Runtime/ObjectPool/ReferenceOpenHashMap.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 311d5e5b578ed15428d9565ab237becc +guid: 0c2c880135959a54f95efaa6ef962867 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/ObjectPool/ReleaseStrategy.cs b/Runtime/ObjectPool/ReleaseStrategy.cs deleted file mode 100644 index 68f629a..0000000 --- a/Runtime/ObjectPool/ReleaseStrategy.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace AlicizaX.ObjectPool -{ - /// - /// 对象池释放策略 - /// - public enum ReleaseStrategy - { - /// - /// LRU (Least Recently Used) - 最近最少使用 - /// - LRU = 0, - - /// - /// LFU (Least Frequently Used) - 最不经常使用 - /// - LFU = 1, - - /// - /// Priority - 基于优先级 - /// - Priority = 2, - - /// - /// Hybrid - 混合策略 (LRU + Priority) - /// - Hybrid = 3 - } -} diff --git a/Runtime/ObjectPool/StringOpenHashMap.cs b/Runtime/ObjectPool/StringOpenHashMap.cs index d7fa7ed..f5755da 100644 --- a/Runtime/ObjectPool/StringOpenHashMap.cs +++ b/Runtime/ObjectPool/StringOpenHashMap.cs @@ -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.Rent(cap); + m_Keys = SlotArrayPool.Rent(cap); + m_Values = SlotArrayPool.Rent(cap); + m_Next = SlotArrayPool.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.Rent(newCap); + var newKeys = SlotArrayPool.Rent(newCap); + var newValues = SlotArrayPool.Rent(newCap); + var newNext = SlotArrayPool.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.Return(m_Buckets, true); + SlotArrayPool.Return(m_Keys, true); + SlotArrayPool.Return(m_Values, true); + SlotArrayPool.Return(m_Next, true); + m_Buckets = newBuckets; m_Keys = newKeys; m_Values = newValues; diff --git a/Runtime/Timer/ITimerService.cs b/Runtime/Timer/ITimerService.cs index 34605fc..7c495a2 100644 --- a/Runtime/Timer/ITimerService.cs +++ b/Runtime/Timer/ITimerService.cs @@ -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(Action 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(Action 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); } } diff --git a/Runtime/Timer/ITimerServiceDebug.cs b/Runtime/Timer/ITimerServiceDebug.cs index 9c33229..b1b50e6 100644 --- a/Runtime/Timer/ITimerServiceDebug.cs +++ b/Runtime/Timer/ITimerServiceDebug.cs @@ -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 diff --git a/Runtime/Timer/TimerService.cs b/Runtime/Timer/TimerService.cs index 729da95..e21cf9f 100644 --- a/Runtime/Timer/TimerService.cs +++ b/Runtime/Timer/TimerService.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Unity.IL2CPP.CompilerServices; using UnityEngine; @@ -20,125 +19,101 @@ namespace AlicizaX.Timer.Runtime } } - [Il2CppSetOption(Option.NullChecks, false)] - [Il2CppSetOption(Option.ArrayBoundsChecks, false)] - [StructLayout(LayoutKind.Sequential, Pack = 4)] - internal struct TimerInfo - { - public int TimerId; - public int Version; - public int QueueIndex; - public int ActiveIndex; - public float TriggerTime; - public float Duration; - public float RemainingTime; - public TimerHandlerNoArgs NoArgsHandler; - public TimerGenericInvoker GenericInvoker; - public object GenericHandler; - public object GenericArg; - public bool IsLoop; - public bool IsRunning; - public bool IsUnscaled; - public bool IsActive; - public byte HandlerType; - - public float CreationTime; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - TimerId = 0; - QueueIndex = -1; - ActiveIndex = -1; - TriggerTime = 0f; - Duration = 0f; - RemainingTime = 0f; - NoArgsHandler = null; - GenericInvoker = null; - GenericHandler = null; - GenericArg = null; - IsLoop = false; - IsRunning = false; - IsUnscaled = false; - IsActive = false; - HandlerType = 0; - CreationTime = 0f; - } - } - [UnityEngine.Scripting.Preserve] [Il2CppSetOption(Option.NullChecks, false)] [Il2CppSetOption(Option.ArrayBoundsChecks, false)] internal sealed class TimerService : ServiceBase, ITimerService, IServiceTickable, ITimerServiceDebug { - private const int MAX_CAPACITY = 256; - private const int HANDLE_INDEX_BITS = 9; - private const int HANDLE_INDEX_MASK = MAX_CAPACITY - 1; - private const int HANDLE_VERSION_MASK = 0x3FFFFF; + private const int PAGE_SHIFT = 8; + private const int PAGE_SIZE = 1 << PAGE_SHIFT; + private const int PAGE_MASK = PAGE_SIZE - 1; + private const int INITIAL_PAGE_TABLE_CAPACITY = 4; + private const int INITIAL_INDEX_CAPACITY = PAGE_SIZE; private const float MINIMUM_DELAY = 0.0001f; - private const byte HANDLER_NO_ARGS = 0; - private const byte HANDLER_GENERIC = 1; - private const float LEAK_DETECTION_THRESHOLD = 300f; - private readonly TimerInfo[] _timerPool; - private readonly int[] _freeIndices; - private readonly int[] _activeIndices; - private readonly TimerQueue _scaledQueue; - private readonly TimerQueue _unscaledQueue; + private const byte HANDLER_NONE = 0; + private const byte HANDLER_NO_ARGS = 1; + private const byte HANDLER_GENERIC = 2; - private int _freeCount; - private int _activeCount; - private int _peakActiveCount; + private const byte STATE_ACTIVE = 1 << 0; + private const byte STATE_RUNNING = 1 << 1; + private const byte STATE_LOOP = 1 << 2; + private const byte STATE_UNSCALED = 1 << 3; + + private sealed class TimerPage + { + public readonly ulong[] Handles = new ulong[PAGE_SIZE]; + public readonly int[] QueueIndices = new int[PAGE_SIZE]; + public readonly int[] ActiveIndices = new int[PAGE_SIZE]; + public readonly float[] TriggerTimes = new float[PAGE_SIZE]; + public readonly float[] Durations = new float[PAGE_SIZE]; + public readonly float[] RemainingTimes = new float[PAGE_SIZE]; + public readonly float[] CreationTimes = new float[PAGE_SIZE]; + public readonly byte[] States = new byte[PAGE_SIZE]; + public readonly byte[] HandlerTypes = new byte[PAGE_SIZE]; + public readonly TimerHandlerNoArgs[] NoArgsHandlers = new TimerHandlerNoArgs[PAGE_SIZE]; + public readonly TimerGenericInvoker[] GenericInvokers = new TimerGenericInvoker[PAGE_SIZE]; + public readonly object[] GenericHandlers = new object[PAGE_SIZE]; + public readonly object[] GenericArgs = new object[PAGE_SIZE]; + + public TimerPage() + { + for (int i = 0; i < PAGE_SIZE; i++) + { + QueueIndices[i] = -1; + ActiveIndices[i] = -1; + } + } + } private sealed class TimerQueue { private readonly TimerService _owner; - private readonly int[] _heap; + private int[] _heap; private int _count; public TimerQueue(TimerService owner) { _owner = owner; - _heap = new int[MAX_CAPACITY]; + _heap = new int[INITIAL_INDEX_CAPACITY]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(int poolIndex) + public void Add(int slotIndex) { - ref TimerInfo timer = ref _owner._timerPool[poolIndex]; - int index = _count++; - _heap[index] = poolIndex; - timer.QueueIndex = index; - BubbleUp(index); + EnsureCapacity(_count + 1); + + int heapIndex = _count++; + _heap[heapIndex] = slotIndex; + _owner.SetQueueIndex(slotIndex, heapIndex); + BubbleUp(heapIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Remove(int poolIndex) + public void Remove(int slotIndex) { - ref TimerInfo timer = ref _owner._timerPool[poolIndex]; - int index = timer.QueueIndex; - if ((uint)index >= (uint)_count) + int heapIndex = _owner.GetQueueIndex(slotIndex); + if ((uint)heapIndex >= (uint)_count) { - timer.QueueIndex = -1; + _owner.SetQueueIndex(slotIndex, -1); return; } int lastIndex = --_count; - int lastPoolIndex = _heap[lastIndex]; - timer.QueueIndex = -1; + int lastSlotIndex = _heap[lastIndex]; + _owner.SetQueueIndex(slotIndex, -1); - if (index == lastIndex) + if (heapIndex == lastIndex) { return; } - _heap[index] = lastPoolIndex; - _owner._timerPool[lastPoolIndex].QueueIndex = index; - - if (!BubbleUp(index)) + _heap[heapIndex] = lastSlotIndex; + _owner.SetQueueIndex(lastSlotIndex, heapIndex); + if (!BubbleUp(heapIndex)) { - BubbleDown(index); + BubbleDown(heapIndex); } } @@ -146,22 +121,20 @@ namespace AlicizaX.Timer.Runtime { while (_count > 0) { - int poolIndex = _heap[0]; - ref TimerInfo timer = ref _owner._timerPool[poolIndex]; - - if (!timer.IsActive || !timer.IsRunning) + int slotIndex = _heap[0]; + if (!_owner.IsSlotActive(slotIndex) || !_owner.IsSlotRunning(slotIndex)) { RemoveRoot(); continue; } - if (timer.TriggerTime > currentTime) + if (_owner.GetTriggerTime(slotIndex) > currentTime) { - break; + return; } RemoveRoot(); - _owner.ProcessDueTimer(poolIndex, currentTime); + _owner.ProcessDueTimer(slotIndex, currentTime); } } @@ -169,26 +142,26 @@ namespace AlicizaX.Timer.Runtime { while (_count > 0) { - int poolIndex = _heap[--_count]; - _owner._timerPool[poolIndex].QueueIndex = -1; + int slotIndex = _heap[--_count]; + _owner.SetQueueIndex(slotIndex, -1); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveRoot() { - int rootPoolIndex = _heap[0]; + int rootSlotIndex = _heap[0]; int lastIndex = --_count; - _owner._timerPool[rootPoolIndex].QueueIndex = -1; + _owner.SetQueueIndex(rootSlotIndex, -1); if (lastIndex <= 0) { return; } - int lastPoolIndex = _heap[lastIndex]; - _heap[0] = lastPoolIndex; - _owner._timerPool[lastPoolIndex].QueueIndex = 0; + int lastSlotIndex = _heap[lastIndex]; + _heap[0] = lastSlotIndex; + _owner.SetQueueIndex(lastSlotIndex, 0); BubbleDown(0); } @@ -198,14 +171,14 @@ namespace AlicizaX.Timer.Runtime bool moved = false; while (index > 0) { - int parent = (index - 1) >> 1; - if (!Less(_heap[index], _heap[parent])) + int parentIndex = (index - 1) >> 1; + if (!Less(_heap[index], _heap[parentIndex])) { break; } - Swap(index, parent); - index = parent; + Swap(index, parentIndex); + index = parentIndex; moved = true; } @@ -217,242 +190,244 @@ namespace AlicizaX.Timer.Runtime { while (true) { - int left = (index << 1) + 1; - if (left >= _count) + int leftIndex = (index << 1) + 1; + if (leftIndex >= _count) { return; } - int right = left + 1; - int smallest = left; - if (right < _count && Less(_heap[right], _heap[left])) + int rightIndex = leftIndex + 1; + int smallestIndex = leftIndex; + if (rightIndex < _count && Less(_heap[rightIndex], _heap[leftIndex])) { - smallest = right; + smallestIndex = rightIndex; } - if (!Less(_heap[smallest], _heap[index])) + if (!Less(_heap[smallestIndex], _heap[index])) { return; } - Swap(index, smallest); - index = smallest; + Swap(index, smallestIndex); + index = smallestIndex; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool Less(int leftPoolIndex, int rightPoolIndex) + private bool Less(int leftSlotIndex, int rightSlotIndex) { - ref TimerInfo left = ref _owner._timerPool[leftPoolIndex]; - ref TimerInfo right = ref _owner._timerPool[rightPoolIndex]; - - if (left.TriggerTime < right.TriggerTime) + float leftTrigger = _owner.GetTriggerTime(leftSlotIndex); + float rightTrigger = _owner.GetTriggerTime(rightSlotIndex); + if (leftTrigger < rightTrigger) { return true; } - if (left.TriggerTime > right.TriggerTime) + if (leftTrigger > rightTrigger) { return false; } - return left.TimerId < right.TimerId; + return _owner.GetHandle(leftSlotIndex) < _owner.GetHandle(rightSlotIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Swap(int leftIndex, int rightIndex) { - int leftPoolIndex = _heap[leftIndex]; - int rightPoolIndex = _heap[rightIndex]; + int leftSlotIndex = _heap[leftIndex]; + int rightSlotIndex = _heap[rightIndex]; + _heap[leftIndex] = rightSlotIndex; + _heap[rightIndex] = leftSlotIndex; + _owner.SetQueueIndex(leftSlotIndex, rightIndex); + _owner.SetQueueIndex(rightSlotIndex, leftIndex); + } - _heap[leftIndex] = rightPoolIndex; - _heap[rightIndex] = leftPoolIndex; + private void EnsureCapacity(int required) + { + if (required <= _heap.Length) + { + return; + } - _owner._timerPool[leftPoolIndex].QueueIndex = rightIndex; - _owner._timerPool[rightPoolIndex].QueueIndex = leftIndex; + int newCapacity = TimerService.GetExpandedCapacity(_heap.Length, required); + int[] newHeap = new int[newCapacity]; + Array.Copy(_heap, 0, newHeap, 0, _count); + _heap = newHeap; } } + private TimerPage[] _pages; + private int[] _freeSlots; + private int[] _activeSlots; + private readonly TimerQueue _scaledQueue; + private readonly TimerQueue _unscaledQueue; + + private int _pageCount; + private int _slotCapacity; + private int _freeCount; + private int _activeCount; + private int _peakActiveCount; + private int _executingSlotIndex; + private float _executingCurrentTime; + public TimerService() { - _timerPool = new TimerInfo[MAX_CAPACITY]; - _freeIndices = new int[MAX_CAPACITY]; - _activeIndices = new int[MAX_CAPACITY]; + _pages = new TimerPage[INITIAL_PAGE_TABLE_CAPACITY]; + _freeSlots = new int[INITIAL_INDEX_CAPACITY]; + _activeSlots = new int[INITIAL_INDEX_CAPACITY]; _scaledQueue = new TimerQueue(this); _unscaledQueue = new TimerQueue(this); + _executingSlotIndex = -1; - _freeCount = MAX_CAPACITY; - for (int i = 0; i < MAX_CAPACITY; i++) - { - _freeIndices[i] = i; - _timerPool[i].QueueIndex = -1; - _timerPool[i].ActiveIndex = -1; - } + AddPage(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false) + public ulong AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false) { if (callback == null) { - return 0; + return 0UL; } - int poolIndex = AcquireTimerIndex(); - if (poolIndex < 0) - { - return 0; - } + int slotIndex = AcquireSlotIndex(); + InitializeTimer(slotIndex, time, isLoop, isUnscaled); - InitializeTimer(poolIndex, time, isLoop, isUnscaled); + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + page.HandlerTypes[slotOffset] = HANDLER_NO_ARGS; + page.NoArgsHandlers[slotOffset] = callback; - ref TimerInfo timer = ref _timerPool[poolIndex]; - timer.HandlerType = HANDLER_NO_ARGS; - timer.NoArgsHandler = callback; - - AddToActiveSet(poolIndex); - ScheduleTimer(poolIndex); - return timer.TimerId; + AddToActiveSet(slotIndex); + ScheduleTimer(slotIndex); + return page.Handles[slotOffset]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int AddTimer(Action callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class + public ulong AddTimer(Action callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class { if (callback == null) { - return 0; + return 0UL; } - int poolIndex = AcquireTimerIndex(); - if (poolIndex < 0) - { - return 0; - } + int slotIndex = AcquireSlotIndex(); + InitializeTimer(slotIndex, time, isLoop, isUnscaled); - InitializeTimer(poolIndex, time, isLoop, isUnscaled); + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + page.HandlerTypes[slotOffset] = HANDLER_GENERIC; + page.GenericInvokers[slotOffset] = TimerGenericInvokerCache.Invoke; + page.GenericHandlers[slotOffset] = callback; + page.GenericArgs[slotOffset] = arg; - ref TimerInfo timer = ref _timerPool[poolIndex]; - timer.HandlerType = HANDLER_GENERIC; - timer.GenericInvoker = TimerGenericInvokerCache.Invoke; - timer.GenericHandler = callback; - timer.GenericArg = arg; - - AddToActiveSet(poolIndex); - ScheduleTimer(poolIndex); - return timer.TimerId; + AddToActiveSet(slotIndex); + ScheduleTimer(slotIndex); + return page.Handles[slotOffset]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Stop(int timerId) + public void Stop(ulong timerHandle) { - int poolIndex = GetPoolIndex(timerId); - if (poolIndex < 0) + int slotIndex = GetSlotIndex(timerHandle); + if (slotIndex < 0 || !IsSlotRunning(slotIndex)) { return; } - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (!timer.IsRunning) + if (GetQueueIndex(slotIndex) >= 0) { - return; - } - - if (timer.QueueIndex >= 0) - { - GetQueue(timer.IsUnscaled).Remove(poolIndex); - float currentTime = GetCurrentTime(timer.IsUnscaled); - float remainingTime = timer.TriggerTime - currentTime; - timer.RemainingTime = remainingTime > MINIMUM_DELAY ? remainingTime : MINIMUM_DELAY; + GetQueue(IsSlotUnscaled(slotIndex)).Remove(slotIndex); + float remainingTime = GetTriggerTime(slotIndex) - GetCurrentTime(IsSlotUnscaled(slotIndex)); + SetRemainingTime(slotIndex, remainingTime > MINIMUM_DELAY ? remainingTime : MINIMUM_DELAY); } else { - timer.RemainingTime = timer.IsLoop ? timer.Duration : MINIMUM_DELAY; + SetRemainingTime(slotIndex, IsSlotLoop(slotIndex) ? GetDuration(slotIndex) : MINIMUM_DELAY); } - timer.IsRunning = false; + ClearState(slotIndex, STATE_RUNNING); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Resume(int timerId) + public void Resume(ulong timerHandle) { - int poolIndex = GetPoolIndex(timerId); - if (poolIndex < 0) + int slotIndex = GetSlotIndex(timerHandle); + if (slotIndex < 0 || IsSlotRunning(slotIndex)) { return; } - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (timer.IsRunning) + float delay = GetRemainingTime(slotIndex); + if (delay <= MINIMUM_DELAY) { - return; + delay = MINIMUM_DELAY; } - float delay = timer.RemainingTime > MINIMUM_DELAY ? timer.RemainingTime : MINIMUM_DELAY; - timer.TriggerTime = GetCurrentTime(timer.IsUnscaled) + delay; - timer.RemainingTime = 0f; - timer.IsRunning = true; - ScheduleTimer(poolIndex); + SetTriggerTime(slotIndex, GetCurrentTime(IsSlotUnscaled(slotIndex)) + delay); + SetRemainingTime(slotIndex, 0f); + SetState(slotIndex, STATE_RUNNING); + ScheduleTimer(slotIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsRunning(int timerId) + public bool IsRunning(ulong timerHandle) { - int poolIndex = GetPoolIndex(timerId); - return poolIndex >= 0 && _timerPool[poolIndex].IsRunning; + int slotIndex = GetSlotIndex(timerHandle); + return slotIndex >= 0 && IsSlotRunning(slotIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float GetLeftTime(int timerId) + public float GetLeftTime(ulong timerHandle) { - int poolIndex = GetPoolIndex(timerId); - if (poolIndex < 0) + int slotIndex = GetSlotIndex(timerHandle); + if (slotIndex < 0) { return 0f; } - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (!timer.IsRunning) + if (!IsSlotRunning(slotIndex)) { - return timer.RemainingTime; + return GetRemainingTime(slotIndex); } - float leftTime = timer.TriggerTime - GetCurrentTime(timer.IsUnscaled); + float leftTime = GetTriggerTime(slotIndex) - GetCurrentTime(IsSlotUnscaled(slotIndex)); return leftTime > 0f ? leftTime : 0f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Restart(int timerId) + public void Restart(ulong timerHandle) { - int poolIndex = GetPoolIndex(timerId); - if (poolIndex < 0) + int slotIndex = GetSlotIndex(timerHandle); + if (slotIndex < 0) { return; } - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (timer.QueueIndex >= 0) + if (GetQueueIndex(slotIndex) >= 0) { - GetQueue(timer.IsUnscaled).Remove(poolIndex); + GetQueue(IsSlotUnscaled(slotIndex)).Remove(slotIndex); } - timer.TriggerTime = GetCurrentTime(timer.IsUnscaled) + timer.Duration; - timer.RemainingTime = 0f; - timer.IsRunning = true; - ScheduleTimer(poolIndex); + SetTriggerTime(slotIndex, GetCurrentTime(IsSlotUnscaled(slotIndex)) + GetDuration(slotIndex)); + SetRemainingTime(slotIndex, 0f); + SetState(slotIndex, STATE_RUNNING); + ScheduleTimer(slotIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveTimer(int timerId) + public void RemoveTimer(ulong timerHandle) { - int poolIndex = GetPoolIndex(timerId); - if (poolIndex >= 0) + int slotIndex = GetSlotIndex(timerHandle); + if (slotIndex >= 0) { - ReleaseTimer(poolIndex); + ReleaseTimer(slotIndex); } } void IServiceTickable.Tick(float deltaTime) { + RecoverInterruptedExecution(); _scaledQueue.Advance(Time.time); _unscaledQueue.Advance(Time.unscaledTime); } @@ -471,7 +446,7 @@ namespace AlicizaX.Timer.Runtime void ITimerServiceDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount) { activeCount = _activeCount; - poolCapacity = MAX_CAPACITY; + poolCapacity = _slotCapacity; peakActiveCount = _peakActiveCount; freeCount = _freeCount; } @@ -486,10 +461,10 @@ namespace AlicizaX.Timer.Runtime int count = _activeCount < results.Length ? _activeCount : results.Length; float currentTime = Time.time; float currentUnscaledTime = Time.unscaledTime; - + float realtimeSinceStartup = Time.realtimeSinceStartup; for (int i = 0; i < count; i++) { - FillDebugInfo(_activeIndices[i], ref results[i], currentTime, currentUnscaledTime); + FillDebugInfo(_activeSlots[i], ref results[i], currentTime, currentUnscaledTime, realtimeSinceStartup); } return count; @@ -503,25 +478,25 @@ namespace AlicizaX.Timer.Runtime } int count = 0; - float realtimeSinceStartup = Time.realtimeSinceStartup; float currentTime = Time.time; float currentUnscaledTime = Time.unscaledTime; + float realtimeSinceStartup = Time.realtimeSinceStartup; for (int i = 0; i < _activeCount && count < results.Length; i++) { - int poolIndex = _activeIndices[i]; - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (timer.IsLoop) + int slotIndex = _activeSlots[i]; + if (IsSlotLoop(slotIndex)) { continue; } - if (realtimeSinceStartup - timer.CreationTime <= LEAK_DETECTION_THRESHOLD) + float age = realtimeSinceStartup - GetCreationTime(slotIndex); + if (age <= LEAK_DETECTION_THRESHOLD) { continue; } - FillDebugInfo(poolIndex, ref results[count], currentTime, currentUnscaledTime); + FillDebugInfo(slotIndex, ref results[count], currentTime, currentUnscaledTime, realtimeSinceStartup); count++; } @@ -529,48 +504,56 @@ namespace AlicizaX.Timer.Runtime } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AcquireTimerIndex() + private int AcquireSlotIndex() { if (_freeCount <= 0) { - return -1; + AddPage(); } - return _freeIndices[--_freeCount]; + return _freeSlots[--_freeCount]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void InitializeTimer(int poolIndex, float time, bool isLoop, bool isUnscaled) + private void InitializeTimer(int slotIndex, float time, bool isLoop, bool isUnscaled) { - ref TimerInfo timer = ref _timerPool[poolIndex]; + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; float duration = NormalizeDelay(time); - float currentTime = GetCurrentTime(isUnscaled); + byte state = (byte)(STATE_ACTIVE | STATE_RUNNING); + if (isLoop) + { + state |= STATE_LOOP; + } - int version = NextVersion(timer.Version); - timer.Version = version; - timer.TimerId = ComposeTimerId(poolIndex, version); - timer.TriggerTime = currentTime + duration; - timer.Duration = duration; - timer.RemainingTime = 0f; - timer.NoArgsHandler = null; - timer.GenericInvoker = null; - timer.GenericHandler = null; - timer.GenericArg = null; - timer.IsLoop = isLoop; - timer.IsRunning = true; - timer.IsUnscaled = isUnscaled; - timer.IsActive = true; - timer.HandlerType = HANDLER_NO_ARGS; + if (isUnscaled) + { + state |= STATE_UNSCALED; + } - timer.CreationTime = Time.realtimeSinceStartup; + page.Handles[slotOffset] = ComposeHandle(slotIndex, NextVersion(page.Handles[slotOffset])); + page.TriggerTimes[slotOffset] = GetCurrentTime(isUnscaled) + duration; + page.Durations[slotOffset] = duration; + page.RemainingTimes[slotOffset] = 0f; + page.CreationTimes[slotOffset] = Time.realtimeSinceStartup; + page.States[slotOffset] = state; + page.HandlerTypes[slotOffset] = HANDLER_NONE; + page.NoArgsHandlers[slotOffset] = null; + page.GenericInvokers[slotOffset] = null; + page.GenericHandlers[slotOffset] = null; + page.GenericArgs[slotOffset] = null; + page.QueueIndices[slotOffset] = -1; + page.ActiveIndices[slotOffset] = -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAllTimers() { + RecoverInterruptedExecution(); + while (_activeCount > 0) { - ReleaseTimer(_activeIndices[_activeCount - 1]); + ReleaseTimer(_activeSlots[_activeCount - 1]); } _scaledQueue.Clear(); @@ -578,97 +561,159 @@ namespace AlicizaX.Timer.Runtime } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ProcessDueTimer(int poolIndex, float currentTime) + private void RecoverInterruptedExecution() { - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (!timer.IsActive) + int slotIndex = _executingSlotIndex; + if (slotIndex < 0) { return; } - bool shouldRemoveAfterCallback = !timer.IsLoop; + _executingSlotIndex = -1; + if (!IsSlotActive(slotIndex) || GetQueueIndex(slotIndex) >= 0) + { + return; + } - switch (timer.HandlerType) + if (!IsSlotLoop(slotIndex)) + { + ReleaseTimer(slotIndex); + return; + } + + if (IsSlotRunning(slotIndex)) + { + RescheduleLoopTimer(slotIndex, _executingCurrentTime); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessDueTimer(int slotIndex, float currentTime) + { + if (!IsSlotActive(slotIndex) || !IsSlotRunning(slotIndex)) + { + return; + } + + _executingSlotIndex = slotIndex; + _executingCurrentTime = currentTime; + + switch (GetHandlerType(slotIndex)) { case HANDLER_NO_ARGS: - timer.NoArgsHandler(); + InvokeNoArgs(slotIndex); break; case HANDLER_GENERIC: - timer.GenericInvoker(timer.GenericHandler, timer.GenericArg); + InvokeGeneric(slotIndex); break; } - if (!timer.IsActive) + _executingSlotIndex = -1; + + if (!IsSlotActive(slotIndex)) { return; } - if (timer.QueueIndex >= 0) + if (GetQueueIndex(slotIndex) >= 0) { return; } - if (shouldRemoveAfterCallback) + if (!IsSlotLoop(slotIndex)) { - ReleaseTimer(poolIndex); + ReleaseTimer(slotIndex); + return; } - else if (timer.IsRunning) + + if (IsSlotRunning(slotIndex)) { - timer.TriggerTime = currentTime + timer.Duration; - ScheduleTimer(poolIndex); + RescheduleLoopTimer(slotIndex, currentTime); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ScheduleTimer(int poolIndex) + private void ScheduleTimer(int slotIndex) { - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (!timer.IsActive || !timer.IsRunning) + if (!IsSlotActive(slotIndex) || !IsSlotRunning(slotIndex) || GetQueueIndex(slotIndex) >= 0) { return; } - if (timer.QueueIndex >= 0) + bool isUnscaled = IsSlotUnscaled(slotIndex); + float currentTime = GetCurrentTime(isUnscaled); + if (GetTriggerTime(slotIndex) <= currentTime) { - return; + SetTriggerTime(slotIndex, currentTime + MINIMUM_DELAY); } - float currentTime = GetCurrentTime(timer.IsUnscaled); - if (timer.TriggerTime <= currentTime) - { - timer.TriggerTime = currentTime + MINIMUM_DELAY; - } - - GetQueue(timer.IsUnscaled).Add(poolIndex); + GetQueue(isUnscaled).Add(slotIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReleaseTimer(int poolIndex) + private void RescheduleLoopTimer(int slotIndex, float currentTime) { - ref TimerInfo timer = ref _timerPool[poolIndex]; - if (!timer.IsActive) + float duration = GetDuration(slotIndex); + float nextTriggerTime = GetTriggerTime(slotIndex) + duration; + if (nextTriggerTime <= currentTime) + { + float overrun = currentTime - nextTriggerTime; + int skipCount = (int)(overrun / duration) + 1; + nextTriggerTime += skipCount * duration; + } + + SetTriggerTime(slotIndex, nextTriggerTime); + ScheduleTimer(slotIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReleaseTimer(int slotIndex) + { + if (!IsSlotActive(slotIndex)) { return; } - if (timer.QueueIndex >= 0) + if (GetQueueIndex(slotIndex) >= 0) { - GetQueue(timer.IsUnscaled).Remove(poolIndex); + GetQueue(IsSlotUnscaled(slotIndex)).Remove(slotIndex); } - RemoveFromActiveSet(poolIndex); - timer.Clear(); - _freeIndices[_freeCount++] = poolIndex; + if (_executingSlotIndex == slotIndex) + { + _executingSlotIndex = -1; + } + + RemoveFromActiveSet(slotIndex); + + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + page.QueueIndices[slotOffset] = -1; + page.ActiveIndices[slotOffset] = -1; + page.TriggerTimes[slotOffset] = 0f; + page.Durations[slotOffset] = 0f; + page.RemainingTimes[slotOffset] = 0f; + page.CreationTimes[slotOffset] = 0f; + page.States[slotOffset] = 0; + page.HandlerTypes[slotOffset] = HANDLER_NONE; + page.NoArgsHandlers[slotOffset] = null; + page.GenericInvokers[slotOffset] = null; + page.GenericHandlers[slotOffset] = null; + page.GenericArgs[slotOffset] = null; + + _freeSlots[_freeCount++] = slotIndex; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddToActiveSet(int poolIndex) + private void AddToActiveSet(int slotIndex) { - int position = _activeCount; - _activeIndices[position] = poolIndex; - _timerPool[poolIndex].ActiveIndex = position; - _activeCount = position + 1; + EnsureIndexBufferCapacity(ref _activeSlots, _activeCount + 1); + + int activeIndex = _activeCount; + _activeSlots[activeIndex] = slotIndex; + SetActiveIndex(slotIndex, activeIndex); + _activeCount = activeIndex + 1; if (_activeCount > _peakActiveCount) { @@ -677,58 +722,122 @@ namespace AlicizaX.Timer.Runtime } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void RemoveFromActiveSet(int poolIndex) + private void RemoveFromActiveSet(int slotIndex) { - ref TimerInfo timer = ref _timerPool[poolIndex]; - int position = timer.ActiveIndex; - if ((uint)position >= (uint)_activeCount) + int activeIndex = GetActiveIndex(slotIndex); + if ((uint)activeIndex >= (uint)_activeCount) { return; } - int lastPosition = --_activeCount; - int lastPoolIndex = _activeIndices[lastPosition]; - - if (position != lastPosition) + int lastIndex = --_activeCount; + int lastSlotIndex = _activeSlots[lastIndex]; + if (activeIndex != lastIndex) { - _activeIndices[position] = lastPoolIndex; - _timerPool[lastPoolIndex].ActiveIndex = position; + _activeSlots[activeIndex] = lastSlotIndex; + SetActiveIndex(lastSlotIndex, activeIndex); } - timer.ActiveIndex = -1; + SetActiveIndex(slotIndex, -1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetPoolIndex(int timerId) + private int GetSlotIndex(ulong timerHandle) { - if (timerId <= 0) + uint slotValue = (uint)timerHandle; + if (slotValue == 0U) { return -1; } - int poolIndex = (timerId & HANDLE_INDEX_MASK) - 1; - if ((uint)poolIndex >= MAX_CAPACITY) + int slotIndex = (int)(slotValue - 1U); + if ((uint)slotIndex >= (uint)_slotCapacity) { return -1; } - ref TimerInfo timer = ref _timerPool[poolIndex]; - return timer.IsActive && timer.TimerId == timerId ? poolIndex : -1; + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + return page.States[slotOffset] != 0 && page.Handles[slotOffset] == timerHandle ? slotIndex : -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ComposeTimerId(int poolIndex, int version) + private void FillDebugInfo(int slotIndex, ref TimerDebugInfo info, float currentTime, float currentUnscaledTime, float realtimeSinceStartup) { - return (version << HANDLE_INDEX_BITS) | (poolIndex + 1); + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + byte state = page.States[slotOffset]; + float leftTime; + if ((state & STATE_RUNNING) != 0) + { + leftTime = page.TriggerTimes[slotOffset] - ((state & STATE_UNSCALED) != 0 ? currentUnscaledTime : currentTime); + if (leftTime < 0f) + { + leftTime = 0f; + } + } + else + { + leftTime = page.RemainingTimes[slotOffset]; + } + + byte debugFlags = 0; + if ((state & STATE_RUNNING) != 0) + { + debugFlags |= TimerDebugFlags.Running; + } + + if ((state & STATE_LOOP) != 0) + { + debugFlags |= TimerDebugFlags.Loop; + } + + if ((state & STATE_UNSCALED) != 0) + { + debugFlags |= TimerDebugFlags.Unscaled; + } + + info.TimerHandle = page.Handles[slotOffset]; + info.LeftTime = leftTime; + info.Duration = page.Durations[slotOffset]; + info.Age = realtimeSinceStartup - page.CreationTimes[slotOffset]; + info.Flags = debugFlags; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int NextVersion(int version) + private void AddPage() { + EnsurePageTableCapacity(_pageCount + 1); + + TimerPage page = new TimerPage(); + _pages[_pageCount] = page; + + int baseSlotIndex = _pageCount << PAGE_SHIFT; + EnsureIndexBufferCapacity(ref _freeSlots, _freeCount + PAGE_SIZE); + for (int i = 0; i < PAGE_SIZE; i++) + { + _freeSlots[_freeCount + i] = baseSlotIndex + PAGE_SIZE - 1 - i; + } + + _freeCount += PAGE_SIZE; + _pageCount++; + _slotCapacity = _pageCount << PAGE_SHIFT; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ComposeHandle(int slotIndex, uint version) + { + return ((ulong)version << 32) | ((uint)slotIndex + 1U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint NextVersion(ulong previousHandle) + { + uint version = (uint)(previousHandle >> 32); version++; - if (version > HANDLE_VERSION_MASK) + if (version == 0U) { - version = 1; + version = 1U; } return version; @@ -746,6 +855,60 @@ namespace AlicizaX.Timer.Runtime return isUnscaled ? Time.unscaledTime : Time.time; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetExpandedCapacity(int currentCapacity, int requiredCapacity) + { + int newCapacity = currentCapacity; + if (newCapacity < PAGE_SIZE) + { + newCapacity = PAGE_SIZE; + } + + while (newCapacity < requiredCapacity) + { + int growth = newCapacity >> 1; + if (growth < PAGE_SIZE) + { + growth = PAGE_SIZE; + } + + newCapacity += growth; + } + + return newCapacity; + } + + private static void EnsureIndexBufferCapacity(ref int[] buffer, int requiredCapacity) + { + if (requiredCapacity <= buffer.Length) + { + return; + } + + int newCapacity = GetExpandedCapacity(buffer.Length, requiredCapacity); + int[] newBuffer = new int[newCapacity]; + Array.Copy(buffer, 0, newBuffer, 0, buffer.Length); + buffer = newBuffer; + } + + private void EnsurePageTableCapacity(int requiredPageCount) + { + if (requiredPageCount <= _pages.Length) + { + return; + } + + int newCapacity = _pages.Length; + while (newCapacity < requiredPageCount) + { + newCapacity <<= 1; + } + + TimerPage[] newPages = new TimerPage[newCapacity]; + Array.Copy(_pages, 0, newPages, 0, _pageCount); + _pages = newPages; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private TimerQueue GetQueue(bool isUnscaled) { @@ -753,30 +916,146 @@ namespace AlicizaX.Timer.Runtime } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FillDebugInfo(int poolIndex, ref TimerDebugInfo info, float currentTime, float currentUnscaledTime) + private void InvokeNoArgs(int slotIndex) { - ref TimerInfo timer = ref _timerPool[poolIndex]; - float leftTime; - if (timer.IsRunning) - { - leftTime = timer.TriggerTime - (timer.IsUnscaled ? currentUnscaledTime : currentTime); - if (leftTime < 0f) - { - leftTime = 0f; - } - } - else - { - leftTime = timer.RemainingTime; - } + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + page.NoArgsHandlers[slotIndex & PAGE_MASK](); + } - info.TimerId = timer.TimerId; - info.LeftTime = leftTime; - info.Duration = timer.Duration; - info.IsLoop = timer.IsLoop; - info.IsRunning = timer.IsRunning; - info.IsUnscaled = timer.IsUnscaled; - info.CreationTime = timer.CreationTime; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void InvokeGeneric(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + page.GenericInvokers[slotOffset](page.GenericHandlers[slotOffset], page.GenericArgs[slotOffset]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong GetHandle(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.Handles[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetTriggerTime(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.TriggerTimes[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetTriggerTime(int slotIndex, float value) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + page.TriggerTimes[slotIndex & PAGE_MASK] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetDuration(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.Durations[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetRemainingTime(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.RemainingTimes[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetRemainingTime(int slotIndex, float value) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + page.RemainingTimes[slotIndex & PAGE_MASK] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetCreationTime(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.CreationTimes[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetQueueIndex(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.QueueIndices[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetQueueIndex(int slotIndex, int queueIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + page.QueueIndices[slotIndex & PAGE_MASK] = queueIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetActiveIndex(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.ActiveIndices[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetActiveIndex(int slotIndex, int activeIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + page.ActiveIndices[slotIndex & PAGE_MASK] = activeIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte GetHandlerType(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return page.HandlerTypes[slotIndex & PAGE_MASK]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSlotActive(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return (page.States[slotIndex & PAGE_MASK] & STATE_ACTIVE) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSlotRunning(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return (page.States[slotIndex & PAGE_MASK] & STATE_RUNNING) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSlotLoop(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return (page.States[slotIndex & PAGE_MASK] & STATE_LOOP) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSlotUnscaled(int slotIndex) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + return (page.States[slotIndex & PAGE_MASK] & STATE_UNSCALED) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetState(int slotIndex, byte mask) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + page.States[slotOffset] = (byte)(page.States[slotOffset] | mask); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearState(int slotIndex, byte mask) + { + TimerPage page = _pages[slotIndex >> PAGE_SHIFT]; + int slotOffset = slotIndex & PAGE_MASK; + page.States[slotOffset] = (byte)(page.States[slotOffset] & ~mask); } } } diff --git a/Runtime/UI/Manager/UIService.Block.cs b/Runtime/UI/Manager/UIService.Block.cs index 09b6858..e2af527 100644 --- a/Runtime/UI/Manager/UIService.Block.cs +++ b/Runtime/UI/Manager/UIService.Block.cs @@ -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; } } } diff --git a/Runtime/UI/Manager/UIService.Cache.cs b/Runtime/UI/Manager/UIService.Cache.cs index e5ce353..1a35714 100644 --- a/Runtime/UI/Manager/UIService.Cache.cs +++ b/Runtime/UI/Manager/UIService.Cache.cs @@ -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); } } } diff --git a/Runtime/UI/Manager/UIService.cs b/Runtime/UI/Manager/UIService.cs index 968aa9a..ac4ce67 100644 --- a/Runtime/UI/Manager/UIService.cs +++ b/Runtime/UI/Manager/UIService.cs @@ -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)