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 where T : class { public static readonly TimerGenericInvoker Invoke = InvokeGeneric; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void InvokeGeneric(object handler, object arg) { ((Action)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(Action 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.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); } } }