com.alicizax.unity.framework/Runtime/Timer/TimerService.cs

1062 lines
35 KiB
C#
Raw Normal View History

2025-09-05 19:46:30 +08:00
using System;
2025-12-24 14:34:26 +08:00
using System.Runtime.CompilerServices;
using Unity.IL2CPP.CompilerServices;
2025-09-05 19:46:30 +08:00
using UnityEngine;
namespace AlicizaX.Timer.Runtime
2025-09-05 19:46:30 +08:00
{
2025-12-24 14:34:26 +08:00
public delegate void TimerHandlerNoArgs();
internal delegate void TimerGenericInvoker(object handler, object arg);
2026-03-24 17:45:15 +08:00
internal static class TimerGenericInvokerCache<T> where T : class
2026-03-24 17:45:15 +08:00
{
public static readonly TimerGenericInvoker Invoke = InvokeGeneric;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InvokeGeneric(object handler, object arg)
2026-03-24 17:45:15 +08:00
{
((Action<T>)handler).Invoke((T)arg);
2026-03-24 17:45:15 +08:00
}
}
2025-12-24 14:34:26 +08:00
2025-09-05 19:46:30 +08:00
[UnityEngine.Scripting.Preserve]
2025-12-24 14:34:26 +08:00
[Il2CppSetOption(Option.NullChecks, false)]
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
2026-04-27 11:01:30 +08:00
internal sealed class TimerService : ServiceBase, ITimerService, IServiceTickable, ITimerServiceDebug
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
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 float LEAK_DETECTION_THRESHOLD = 300f;
2026-04-27 12:06:09 +08:00
private const byte HANDLER_NONE = 0;
private const byte HANDLER_NO_ARGS = 1;
private const byte HANDLER_GENERIC = 2;
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
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;
}
}
}
2025-12-24 14:34:26 +08:00
private sealed class TimerQueue
2025-12-24 14:34:26 +08:00
{
private readonly TimerService _owner;
2026-04-27 12:06:09 +08:00
private int[] _heap;
private int _count;
2025-09-05 19:46:30 +08:00
public TimerQueue(TimerService owner)
2025-12-24 14:34:26 +08:00
{
_owner = owner;
2026-04-27 12:06:09 +08:00
_heap = new int[INITIAL_INDEX_CAPACITY];
2025-12-24 14:34:26 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public void Add(int slotIndex)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
EnsureCapacity(_count + 1);
int heapIndex = _count++;
_heap[heapIndex] = slotIndex;
_owner.SetQueueIndex(slotIndex, heapIndex);
BubbleUp(heapIndex);
2025-09-05 19:46:30 +08:00
}
2025-12-24 14:34:26 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public void Remove(int slotIndex)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
int heapIndex = _owner.GetQueueIndex(slotIndex);
if ((uint)heapIndex >= (uint)_count)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
_owner.SetQueueIndex(slotIndex, -1);
return;
2025-12-24 14:34:26 +08:00
}
int lastIndex = --_count;
2026-04-27 12:06:09 +08:00
int lastSlotIndex = _heap[lastIndex];
_owner.SetQueueIndex(slotIndex, -1);
2026-04-27 12:06:09 +08:00
if (heapIndex == lastIndex)
2025-12-24 14:34:26 +08:00
{
return;
2025-09-05 19:46:30 +08:00
}
2026-04-27 12:06:09 +08:00
_heap[heapIndex] = lastSlotIndex;
_owner.SetQueueIndex(lastSlotIndex, heapIndex);
if (!BubbleUp(heapIndex))
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
BubbleDown(heapIndex);
2025-12-24 14:34:26 +08:00
}
2025-09-05 19:46:30 +08:00
}
public void Advance(float currentTime)
2025-09-05 19:46:30 +08:00
{
while (_count > 0)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = _heap[0];
if (!_owner.IsSlotActive(slotIndex) || !_owner.IsSlotRunning(slotIndex))
2025-12-24 14:34:26 +08:00
{
RemoveRoot();
continue;
2025-12-24 14:34:26 +08:00
}
2026-04-27 12:06:09 +08:00
if (_owner.GetTriggerTime(slotIndex) > currentTime)
{
2026-04-27 12:06:09 +08:00
return;
}
RemoveRoot();
2026-04-27 12:06:09 +08:00
_owner.ProcessDueTimer(slotIndex, currentTime);
}
}
public void Clear()
{
while (_count > 0)
{
2026-04-27 12:06:09 +08:00
int slotIndex = _heap[--_count];
_owner.SetQueueIndex(slotIndex, -1);
2026-03-24 17:45:15 +08:00
}
}
2025-09-05 19:46:30 +08:00
2026-03-24 17:45:15 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveRoot()
2026-03-24 17:45:15 +08:00
{
2026-04-27 12:06:09 +08:00
int rootSlotIndex = _heap[0];
int lastIndex = --_count;
2026-04-27 12:06:09 +08:00
_owner.SetQueueIndex(rootSlotIndex, -1);
if (lastIndex <= 0)
2026-03-24 17:45:15 +08:00
{
return;
2025-12-24 14:34:26 +08:00
}
2026-03-24 17:45:15 +08:00
2026-04-27 12:06:09 +08:00
int lastSlotIndex = _heap[lastIndex];
_heap[0] = lastSlotIndex;
_owner.SetQueueIndex(lastSlotIndex, 0);
BubbleDown(0);
2025-12-24 14:34:26 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool BubbleUp(int index)
2025-12-24 14:34:26 +08:00
{
bool moved = false;
while (index > 0)
2026-03-24 17:45:15 +08:00
{
2026-04-27 12:06:09 +08:00
int parentIndex = (index - 1) >> 1;
if (!Less(_heap[index], _heap[parentIndex]))
{
break;
}
2026-04-27 12:06:09 +08:00
Swap(index, parentIndex);
index = parentIndex;
moved = true;
2026-03-24 17:45:15 +08:00
}
2025-09-05 19:46:30 +08:00
return moved;
}
2025-12-24 14:34:26 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BubbleDown(int index)
{
while (true)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
int leftIndex = (index << 1) + 1;
if (leftIndex >= _count)
{
return;
}
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
int rightIndex = leftIndex + 1;
int smallestIndex = leftIndex;
if (rightIndex < _count && Less(_heap[rightIndex], _heap[leftIndex]))
{
2026-04-27 12:06:09 +08:00
smallestIndex = rightIndex;
}
2025-12-25 10:50:14 +08:00
2026-04-27 12:06:09 +08:00
if (!Less(_heap[smallestIndex], _heap[index]))
2025-12-24 14:34:26 +08:00
{
return;
2025-12-24 14:34:26 +08:00
}
2025-09-05 19:46:30 +08:00
2026-04-27 12:06:09 +08:00
Swap(index, smallestIndex);
index = smallestIndex;
2025-12-24 14:34:26 +08:00
}
}
2025-12-25 10:50:14 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private bool Less(int leftSlotIndex, int rightSlotIndex)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
float leftTrigger = _owner.GetTriggerTime(leftSlotIndex);
float rightTrigger = _owner.GetTriggerTime(rightSlotIndex);
if (leftTrigger < rightTrigger)
2025-12-24 14:34:26 +08:00
{
return true;
2025-09-05 19:46:30 +08:00
}
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
if (leftTrigger > rightTrigger)
2025-12-24 14:34:26 +08:00
{
return false;
2025-12-24 14:34:26 +08:00
}
2025-12-25 10:50:14 +08:00
2026-04-27 12:06:09 +08:00
return _owner.GetHandle(leftSlotIndex) < _owner.GetHandle(rightSlotIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int leftIndex, int rightIndex)
{
2026-04-27 12:06:09 +08:00
int leftSlotIndex = _heap[leftIndex];
int rightSlotIndex = _heap[rightIndex];
_heap[leftIndex] = rightSlotIndex;
_heap[rightIndex] = leftSlotIndex;
_owner.SetQueueIndex(leftSlotIndex, rightIndex);
_owner.SetQueueIndex(rightSlotIndex, leftIndex);
}
2026-04-27 12:06:09 +08:00
private void EnsureCapacity(int required)
{
if (required <= _heap.Length)
{
return;
}
2026-04-27 12:06:09 +08:00
int newCapacity = TimerService.GetExpandedCapacity(_heap.Length, required);
int[] newHeap = new int[newCapacity];
Array.Copy(_heap, 0, newHeap, 0, _count);
_heap = newHeap;
2025-09-05 19:46:30 +08:00
}
}
2026-04-27 12:06:09 +08:00
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()
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
_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);
2026-04-27 12:06:09 +08:00
_executingSlotIndex = -1;
2026-04-27 12:06:09 +08:00
AddPage();
2025-12-24 14:34:26 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public ulong AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false)
2025-12-24 14:34:26 +08:00
{
if (callback == null)
{
2026-04-27 12:06:09 +08:00
return 0UL;
}
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
int slotIndex = AcquireSlotIndex();
InitializeTimer(slotIndex, time, isLoop, isUnscaled);
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
TimerPage page = _pages[slotIndex >> PAGE_SHIFT];
int slotOffset = slotIndex & PAGE_MASK;
page.HandlerTypes[slotOffset] = HANDLER_NO_ARGS;
page.NoArgsHandlers[slotOffset] = callback;
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
AddToActiveSet(slotIndex);
ScheduleTimer(slotIndex);
return page.Handles[slotOffset];
2025-09-05 19:46:30 +08:00
}
2025-12-24 14:34:26 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public ulong AddTimer<T>(Action<T> callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class
2025-09-05 19:46:30 +08:00
{
if (callback == null)
{
2026-04-27 12:06:09 +08:00
return 0UL;
}
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
int slotIndex = AcquireSlotIndex();
InitializeTimer(slotIndex, time, isLoop, isUnscaled);
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
TimerPage page = _pages[slotIndex >> PAGE_SHIFT];
int slotOffset = slotIndex & PAGE_MASK;
page.HandlerTypes[slotOffset] = HANDLER_GENERIC;
page.GenericInvokers[slotOffset] = TimerGenericInvokerCache<T>.Invoke;
page.GenericHandlers[slotOffset] = callback;
page.GenericArgs[slotOffset] = arg;
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
AddToActiveSet(slotIndex);
ScheduleTimer(slotIndex);
return page.Handles[slotOffset];
2025-12-24 14:34:26 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public void Stop(ulong timerHandle)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = GetSlotIndex(timerHandle);
if (slotIndex < 0 || !IsSlotRunning(slotIndex))
{
return;
}
2025-12-24 14:34:26 +08:00
2026-04-27 12:06:09 +08:00
if (GetQueueIndex(slotIndex) >= 0)
{
2026-04-27 12:06:09 +08:00
GetQueue(IsSlotUnscaled(slotIndex)).Remove(slotIndex);
float remainingTime = GetTriggerTime(slotIndex) - GetCurrentTime(IsSlotUnscaled(slotIndex));
SetRemainingTime(slotIndex, remainingTime > MINIMUM_DELAY ? remainingTime : MINIMUM_DELAY);
}
else
{
2026-04-27 12:06:09 +08:00
SetRemainingTime(slotIndex, IsSlotLoop(slotIndex) ? GetDuration(slotIndex) : MINIMUM_DELAY);
}
2025-09-05 19:46:30 +08:00
2026-04-27 12:06:09 +08:00
ClearState(slotIndex, STATE_RUNNING);
2025-09-05 19:46:30 +08:00
}
2025-12-24 14:34:26 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public void Resume(ulong timerHandle)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = GetSlotIndex(timerHandle);
if (slotIndex < 0 || IsSlotRunning(slotIndex))
2025-12-24 14:34:26 +08:00
{
return;
2025-12-24 14:34:26 +08:00
}
2026-04-27 12:06:09 +08:00
float delay = GetRemainingTime(slotIndex);
if (delay <= MINIMUM_DELAY)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
delay = MINIMUM_DELAY;
2025-12-24 14:34:26 +08:00
}
2026-04-27 12:06:09 +08:00
SetTriggerTime(slotIndex, GetCurrentTime(IsSlotUnscaled(slotIndex)) + delay);
SetRemainingTime(slotIndex, 0f);
SetState(slotIndex, STATE_RUNNING);
ScheduleTimer(slotIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public bool IsRunning(ulong timerHandle)
{
2026-04-27 12:06:09 +08:00
int slotIndex = GetSlotIndex(timerHandle);
return slotIndex >= 0 && IsSlotRunning(slotIndex);
2025-12-24 14:34:26 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public float GetLeftTime(ulong timerHandle)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = GetSlotIndex(timerHandle);
if (slotIndex < 0)
2025-12-24 14:34:26 +08:00
{
return 0f;
}
2026-03-24 17:45:15 +08:00
2026-04-27 12:06:09 +08:00
if (!IsSlotRunning(slotIndex))
{
2026-04-27 12:06:09 +08:00
return GetRemainingTime(slotIndex);
2025-12-24 14:34:26 +08:00
}
2026-04-27 12:06:09 +08:00
float leftTime = GetTriggerTime(slotIndex) - GetCurrentTime(IsSlotUnscaled(slotIndex));
return leftTime > 0f ? leftTime : 0f;
2025-12-24 14:34:26 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public void Restart(ulong timerHandle)
2025-12-24 14:34:26 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = GetSlotIndex(timerHandle);
if (slotIndex < 0)
2026-03-24 17:45:15 +08:00
{
return;
2026-03-24 17:45:15 +08:00
}
2026-04-27 12:06:09 +08:00
if (GetQueueIndex(slotIndex) >= 0)
2026-03-24 17:45:15 +08:00
{
2026-04-27 12:06:09 +08:00
GetQueue(IsSlotUnscaled(slotIndex)).Remove(slotIndex);
2026-03-24 17:45:15 +08:00
}
2026-04-27 12:06:09 +08:00
SetTriggerTime(slotIndex, GetCurrentTime(IsSlotUnscaled(slotIndex)) + GetDuration(slotIndex));
SetRemainingTime(slotIndex, 0f);
SetState(slotIndex, STATE_RUNNING);
ScheduleTimer(slotIndex);
2026-03-24 17:45:15 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
public void RemoveTimer(ulong timerHandle)
2026-03-24 17:45:15 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = GetSlotIndex(timerHandle);
if (slotIndex >= 0)
2026-03-24 17:45:15 +08:00
{
2026-04-27 12:06:09 +08:00
ReleaseTimer(slotIndex);
2026-03-24 17:45:15 +08:00
}
2025-12-24 14:34:26 +08:00
}
void IServiceTickable.Tick(float deltaTime)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
RecoverInterruptedExecution();
_scaledQueue.Advance(Time.time);
_unscaledQueue.Advance(Time.unscaledTime);
2025-09-05 19:46:30 +08:00
}
protected override void OnInitialize()
2025-09-05 19:46:30 +08:00
{
}
protected override void OnDestroyService()
2025-12-24 14:34:26 +08:00
{
RemoveAllTimers();
2025-12-24 14:34:26 +08:00
}
public int Order => 0;
2025-09-05 19:46:30 +08:00
2026-04-27 11:01:30 +08:00
void ITimerServiceDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount)
{
activeCount = _activeCount;
2026-04-27 12:06:09 +08:00
poolCapacity = _slotCapacity;
peakActiveCount = _peakActiveCount;
freeCount = _freeCount;
2025-12-24 14:34:26 +08:00
}
2025-09-05 19:46:30 +08:00
2026-04-27 11:01:30 +08:00
int ITimerServiceDebug.GetAllTimers(TimerDebugInfo[] results)
2025-09-05 19:46:30 +08:00
{
if (results == null || results.Length == 0)
{
return 0;
}
2025-12-24 14:34:26 +08:00
int count = _activeCount < results.Length ? _activeCount : results.Length;
float currentTime = Time.time;
float currentUnscaledTime = Time.unscaledTime;
2026-04-27 12:06:09 +08:00
float realtimeSinceStartup = Time.realtimeSinceStartup;
for (int i = 0; i < count; i++)
{
2026-04-27 12:06:09 +08:00
FillDebugInfo(_activeSlots[i], ref results[i], currentTime, currentUnscaledTime, realtimeSinceStartup);
}
2025-12-25 10:50:14 +08:00
return count;
2025-09-05 19:46:30 +08:00
}
2026-04-27 11:01:30 +08:00
int ITimerServiceDebug.GetStaleOneShotTimers(TimerDebugInfo[] results)
2025-09-05 19:46:30 +08:00
{
if (results == null || results.Length == 0)
2025-12-25 10:50:14 +08:00
{
return 0;
2025-12-25 10:50:14 +08:00
}
2025-12-24 14:34:26 +08:00
int count = 0;
float currentTime = Time.time;
float currentUnscaledTime = Time.unscaledTime;
2026-04-27 12:06:09 +08:00
float realtimeSinceStartup = Time.realtimeSinceStartup;
2025-09-05 19:46:30 +08:00
for (int i = 0; i < _activeCount && count < results.Length; i++)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = _activeSlots[i];
if (IsSlotLoop(slotIndex))
2025-12-24 14:34:26 +08:00
{
continue;
}
2026-03-24 17:45:15 +08:00
2026-04-27 12:06:09 +08:00
float age = realtimeSinceStartup - GetCreationTime(slotIndex);
if (age <= LEAK_DETECTION_THRESHOLD)
{
continue;
2025-12-24 14:34:26 +08:00
}
2026-04-27 12:06:09 +08:00
FillDebugInfo(slotIndex, ref results[count], currentTime, currentUnscaledTime, realtimeSinceStartup);
count++;
2025-09-05 19:46:30 +08:00
}
2026-03-24 17:45:15 +08:00
return count;
2025-09-05 19:46:30 +08:00
}
2025-12-24 14:34:26 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private int AcquireSlotIndex()
2025-09-05 19:46:30 +08:00
{
if (_freeCount <= 0)
2025-12-25 10:50:14 +08:00
{
2026-04-27 12:06:09 +08:00
AddPage();
}
2026-03-24 17:45:15 +08:00
2026-04-27 12:06:09 +08:00
return _freeSlots[--_freeCount];
}
2025-12-25 10:50:14 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void InitializeTimer(int slotIndex, float time, bool isLoop, bool isUnscaled)
{
2026-04-27 12:06:09 +08:00
TimerPage page = _pages[slotIndex >> PAGE_SHIFT];
int slotOffset = slotIndex & PAGE_MASK;
float duration = NormalizeDelay(time);
2026-04-27 12:06:09 +08:00
byte state = (byte)(STATE_ACTIVE | STATE_RUNNING);
if (isLoop)
{
state |= STATE_LOOP;
}
2026-04-27 12:06:09 +08:00
if (isUnscaled)
{
state |= STATE_UNSCALED;
}
2025-12-25 10:50:14 +08:00
2026-04-27 12:06:09 +08:00
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;
2025-09-05 19:46:30 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveAllTimers()
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
RecoverInterruptedExecution();
while (_activeCount > 0)
{
2026-04-27 12:06:09 +08:00
ReleaseTimer(_activeSlots[_activeCount - 1]);
}
2025-12-25 10:50:14 +08:00
_scaledQueue.Clear();
_unscaledQueue.Clear();
2025-09-05 19:46:30 +08:00
}
2025-12-24 14:34:26 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void RecoverInterruptedExecution()
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
int slotIndex = _executingSlotIndex;
if (slotIndex < 0)
{
return;
}
_executingSlotIndex = -1;
if (!IsSlotActive(slotIndex) || GetQueueIndex(slotIndex) >= 0)
{
return;
}
if (!IsSlotLoop(slotIndex))
{
2026-04-27 12:06:09 +08:00
ReleaseTimer(slotIndex);
return;
}
2026-04-27 12:06:09 +08:00
if (IsSlotRunning(slotIndex))
{
RescheduleLoopTimer(slotIndex, _executingCurrentTime);
}
}
2026-04-27 12:06:09 +08:00
[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:
2026-04-27 12:06:09 +08:00
InvokeNoArgs(slotIndex);
break;
case HANDLER_GENERIC:
2026-04-27 12:06:09 +08:00
InvokeGeneric(slotIndex);
break;
}
2026-04-27 12:06:09 +08:00
_executingSlotIndex = -1;
if (!IsSlotActive(slotIndex))
{
return;
}
2026-04-27 12:06:09 +08:00
if (GetQueueIndex(slotIndex) >= 0)
{
2025-12-25 10:50:14 +08:00
return;
}
2026-04-27 12:06:09 +08:00
if (!IsSlotLoop(slotIndex))
{
2026-04-27 12:06:09 +08:00
ReleaseTimer(slotIndex);
return;
}
2026-04-27 12:06:09 +08:00
if (IsSlotRunning(slotIndex))
{
2026-04-27 12:06:09 +08:00
RescheduleLoopTimer(slotIndex, currentTime);
}
}
2025-12-25 10:50:14 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void ScheduleTimer(int slotIndex)
{
2026-04-27 12:06:09 +08:00
if (!IsSlotActive(slotIndex) || !IsSlotRunning(slotIndex) || GetQueueIndex(slotIndex) >= 0)
{
2025-12-25 10:50:14 +08:00
return;
}
2025-09-05 19:46:30 +08:00
2026-04-27 12:06:09 +08:00
bool isUnscaled = IsSlotUnscaled(slotIndex);
float currentTime = GetCurrentTime(isUnscaled);
if (GetTriggerTime(slotIndex) <= currentTime)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
SetTriggerTime(slotIndex, currentTime + MINIMUM_DELAY);
2025-09-05 19:46:30 +08:00
}
2026-04-27 12:06:09 +08:00
GetQueue(isUnscaled).Add(slotIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RescheduleLoopTimer(int slotIndex, float currentTime)
{
float duration = GetDuration(slotIndex);
float nextTriggerTime = GetTriggerTime(slotIndex) + duration;
if (nextTriggerTime <= currentTime)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
float overrun = currentTime - nextTriggerTime;
int skipCount = (int)(overrun / duration) + 1;
nextTriggerTime += skipCount * duration;
2025-09-05 19:46:30 +08:00
}
2026-04-27 12:06:09 +08:00
SetTriggerTime(slotIndex, nextTriggerTime);
ScheduleTimer(slotIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void ReleaseTimer(int slotIndex)
{
2026-04-27 12:06:09 +08:00
if (!IsSlotActive(slotIndex))
{
2025-12-25 10:50:14 +08:00
return;
}
2025-12-25 10:50:14 +08:00
2026-04-27 12:06:09 +08:00
if (GetQueueIndex(slotIndex) >= 0)
2025-09-05 19:46:30 +08:00
{
2026-04-27 12:06:09 +08:00
GetQueue(IsSlotUnscaled(slotIndex)).Remove(slotIndex);
2025-09-05 19:46:30 +08:00
}
2026-04-27 12:06:09 +08:00
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)]
2026-04-27 12:06:09 +08:00
private void AddToActiveSet(int slotIndex)
{
2026-04-27 12:06:09 +08:00
EnsureIndexBufferCapacity(ref _activeSlots, _activeCount + 1);
int activeIndex = _activeCount;
_activeSlots[activeIndex] = slotIndex;
SetActiveIndex(slotIndex, activeIndex);
_activeCount = activeIndex + 1;
if (_activeCount > _peakActiveCount)
2025-09-05 19:46:30 +08:00
{
_peakActiveCount = _activeCount;
2025-12-25 10:50:14 +08:00
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void RemoveFromActiveSet(int slotIndex)
2025-12-25 10:50:14 +08:00
{
2026-04-27 12:06:09 +08:00
int activeIndex = GetActiveIndex(slotIndex);
if ((uint)activeIndex >= (uint)_activeCount)
2025-12-25 10:50:14 +08:00
{
return;
}
2026-04-27 12:06:09 +08:00
int lastIndex = --_activeCount;
int lastSlotIndex = _activeSlots[lastIndex];
if (activeIndex != lastIndex)
{
2026-04-27 12:06:09 +08:00
_activeSlots[activeIndex] = lastSlotIndex;
SetActiveIndex(lastSlotIndex, activeIndex);
2025-12-25 10:50:14 +08:00
}
2026-04-27 12:06:09 +08:00
SetActiveIndex(slotIndex, -1);
2025-12-25 10:50:14 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private int GetSlotIndex(ulong timerHandle)
2025-12-25 10:50:14 +08:00
{
2026-04-27 12:06:09 +08:00
uint slotValue = (uint)timerHandle;
if (slotValue == 0U)
2025-12-25 10:50:14 +08:00
{
return -1;
2025-09-05 19:46:30 +08:00
}
2026-04-27 12:06:09 +08:00
int slotIndex = (int)(slotValue - 1U);
if ((uint)slotIndex >= (uint)_slotCapacity)
{
return -1;
}
2026-04-27 12:06:09 +08:00
TimerPage page = _pages[slotIndex >> PAGE_SHIFT];
int slotOffset = slotIndex & PAGE_MASK;
return page.States[slotOffset] != 0 && page.Handles[slotOffset] == timerHandle ? slotIndex : -1;
2025-09-05 19:46:30 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void FillDebugInfo(int slotIndex, ref TimerDebugInfo info, float currentTime, float currentUnscaledTime, float realtimeSinceStartup)
{
2026-04-27 12:06:09 +08:00
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;
}
2025-09-05 19:46:30 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private void AddPage()
{
2026-04-27 12:06:09 +08:00
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++;
2026-04-27 12:06:09 +08:00
if (version == 0U)
{
2026-04-27 12:06:09 +08:00
version = 1U;
}
return version;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float NormalizeDelay(float time)
{
return time > MINIMUM_DELAY ? time : MINIMUM_DELAY;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float GetCurrentTime(bool isUnscaled)
{
return isUnscaled ? Time.unscaledTime : Time.time;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2026-04-27 12:06:09 +08:00
private static int GetExpandedCapacity(int currentCapacity, int requiredCapacity)
{
2026-04-27 12:06:09 +08:00
int newCapacity = currentCapacity;
if (newCapacity < PAGE_SIZE)
{
newCapacity = PAGE_SIZE;
}
2026-04-27 12:06:09 +08:00
while (newCapacity < requiredCapacity)
{
2026-04-27 12:06:09 +08:00
int growth = newCapacity >> 1;
if (growth < PAGE_SIZE)
{
2026-04-27 12:06:09 +08:00
growth = PAGE_SIZE;
}
2026-04-27 12:06:09 +08:00
newCapacity += growth;
}
2026-04-27 12:06:09 +08:00
return newCapacity;
}
private static void EnsureIndexBufferCapacity(ref int[] buffer, int requiredCapacity)
{
if (requiredCapacity <= buffer.Length)
{
2026-04-27 12:06:09 +08:00
return;
}
2026-04-27 12:06:09 +08:00
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)
{
return isUnscaled ? _unscaledQueue : _scaledQueue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InvokeNoArgs(int slotIndex)
{
TimerPage page = _pages[slotIndex >> PAGE_SHIFT];
page.NoArgsHandlers[slotIndex & PAGE_MASK]();
}
[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);
}
2025-09-05 19:46:30 +08:00
}
}