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