com.alicizax.unity.framework/Runtime/Timer/TimerService.cs
陈思海 f4f0ea1754 [Optimization] TimerService&TimerDebug&AudioService
[Optimization] TimerService&TimerDebug&AudioService
2026-04-24 20:50:13 +08:00

795 lines
24 KiB
C#

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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);
}
}
[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;
#if UNITY_EDITOR
public float CreationTime;
#endif
[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;
#if UNITY_EDITOR
CreationTime = 0f;
#endif
}
}
[UnityEngine.Scripting.Preserve]
[Il2CppSetOption(Option.NullChecks, false)]
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
internal sealed class TimerService : ServiceBase, ITimerService, IServiceTickable, ITimerServiceDebugView
{
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 float MINIMUM_DELAY = 0.0001f;
private const byte HANDLER_NO_ARGS = 0;
private const byte HANDLER_GENERIC = 1;
#if UNITY_EDITOR
private const float LEAK_DETECTION_THRESHOLD = 300f;
#endif
private readonly TimerInfo[] _timerPool;
private readonly int[] _freeIndices;
private readonly int[] _activeIndices;
private readonly TimerQueue _scaledQueue;
private readonly TimerQueue _unscaledQueue;
private int _freeCount;
private int _activeCount;
private int _peakActiveCount;
private sealed class TimerQueue
{
private readonly TimerService _owner;
private readonly int[] _heap;
private int _count;
public TimerQueue(TimerService owner)
{
_owner = owner;
_heap = new int[MAX_CAPACITY];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int poolIndex)
{
ref TimerInfo timer = ref _owner._timerPool[poolIndex];
int index = _count++;
_heap[index] = poolIndex;
timer.QueueIndex = index;
BubbleUp(index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Remove(int poolIndex)
{
ref TimerInfo timer = ref _owner._timerPool[poolIndex];
int index = timer.QueueIndex;
if ((uint)index >= (uint)_count)
{
timer.QueueIndex = -1;
return;
}
int lastIndex = --_count;
int lastPoolIndex = _heap[lastIndex];
timer.QueueIndex = -1;
if (index == lastIndex)
{
return;
}
_heap[index] = lastPoolIndex;
_owner._timerPool[lastPoolIndex].QueueIndex = index;
if (!BubbleUp(index))
{
BubbleDown(index);
}
}
public void Advance(float currentTime)
{
while (_count > 0)
{
int poolIndex = _heap[0];
ref TimerInfo timer = ref _owner._timerPool[poolIndex];
if (!timer.IsActive || !timer.IsRunning)
{
RemoveRoot();
continue;
}
if (timer.TriggerTime > currentTime)
{
break;
}
RemoveRoot();
_owner.ProcessDueTimer(poolIndex, currentTime);
}
}
public void Clear()
{
while (_count > 0)
{
int poolIndex = _heap[--_count];
_owner._timerPool[poolIndex].QueueIndex = -1;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveRoot()
{
int rootPoolIndex = _heap[0];
int lastIndex = --_count;
_owner._timerPool[rootPoolIndex].QueueIndex = -1;
if (lastIndex <= 0)
{
return;
}
int lastPoolIndex = _heap[lastIndex];
_heap[0] = lastPoolIndex;
_owner._timerPool[lastPoolIndex].QueueIndex = 0;
BubbleDown(0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool BubbleUp(int index)
{
bool moved = false;
while (index > 0)
{
int parent = (index - 1) >> 1;
if (!Less(_heap[index], _heap[parent]))
{
break;
}
Swap(index, parent);
index = parent;
moved = true;
}
return moved;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BubbleDown(int index)
{
while (true)
{
int left = (index << 1) + 1;
if (left >= _count)
{
return;
}
int right = left + 1;
int smallest = left;
if (right < _count && Less(_heap[right], _heap[left]))
{
smallest = right;
}
if (!Less(_heap[smallest], _heap[index]))
{
return;
}
Swap(index, smallest);
index = smallest;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool Less(int leftPoolIndex, int rightPoolIndex)
{
ref TimerInfo left = ref _owner._timerPool[leftPoolIndex];
ref TimerInfo right = ref _owner._timerPool[rightPoolIndex];
if (left.TriggerTime < right.TriggerTime)
{
return true;
}
if (left.TriggerTime > right.TriggerTime)
{
return false;
}
return left.TimerId < right.TimerId;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Swap(int leftIndex, int rightIndex)
{
int leftPoolIndex = _heap[leftIndex];
int rightPoolIndex = _heap[rightIndex];
_heap[leftIndex] = rightPoolIndex;
_heap[rightIndex] = leftPoolIndex;
_owner._timerPool[leftPoolIndex].QueueIndex = rightIndex;
_owner._timerPool[rightPoolIndex].QueueIndex = leftIndex;
}
}
public TimerService()
{
_timerPool = new TimerInfo[MAX_CAPACITY];
_freeIndices = new int[MAX_CAPACITY];
_activeIndices = new int[MAX_CAPACITY];
_scaledQueue = new TimerQueue(this);
_unscaledQueue = new TimerQueue(this);
_freeCount = MAX_CAPACITY;
for (int i = 0; i < MAX_CAPACITY; i++)
{
_freeIndices[i] = i;
_timerPool[i].QueueIndex = -1;
_timerPool[i].ActiveIndex = -1;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false)
{
if (callback == null)
{
return 0;
}
int poolIndex = AcquireTimerIndex();
if (poolIndex < 0)
{
return 0;
}
InitializeTimer(poolIndex, time, isLoop, isUnscaled);
ref TimerInfo timer = ref _timerPool[poolIndex];
timer.HandlerType = HANDLER_NO_ARGS;
timer.NoArgsHandler = callback;
AddToActiveSet(poolIndex);
ScheduleTimer(poolIndex);
return timer.TimerId;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int AddTimer<T>(Action<T> callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class
{
if (callback == null)
{
return 0;
}
int poolIndex = AcquireTimerIndex();
if (poolIndex < 0)
{
return 0;
}
InitializeTimer(poolIndex, time, isLoop, isUnscaled);
ref TimerInfo timer = ref _timerPool[poolIndex];
timer.HandlerType = HANDLER_GENERIC;
timer.GenericInvoker = TimerGenericInvokerCache<T>.Invoke;
timer.GenericHandler = callback;
timer.GenericArg = arg;
AddToActiveSet(poolIndex);
ScheduleTimer(poolIndex);
return timer.TimerId;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Stop(int timerId)
{
int poolIndex = GetPoolIndex(timerId);
if (poolIndex < 0)
{
return;
}
ref TimerInfo timer = ref _timerPool[poolIndex];
if (!timer.IsRunning)
{
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;
}
else
{
timer.RemainingTime = timer.IsLoop ? timer.Duration : MINIMUM_DELAY;
}
timer.IsRunning = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Resume(int timerId)
{
int poolIndex = GetPoolIndex(timerId);
if (poolIndex < 0)
{
return;
}
ref TimerInfo timer = ref _timerPool[poolIndex];
if (timer.IsRunning)
{
return;
}
float delay = timer.RemainingTime > MINIMUM_DELAY ? timer.RemainingTime : MINIMUM_DELAY;
timer.TriggerTime = GetCurrentTime(timer.IsUnscaled) + delay;
timer.RemainingTime = 0f;
timer.IsRunning = true;
ScheduleTimer(poolIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsRunning(int timerId)
{
int poolIndex = GetPoolIndex(timerId);
return poolIndex >= 0 && _timerPool[poolIndex].IsRunning;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float GetLeftTime(int timerId)
{
int poolIndex = GetPoolIndex(timerId);
if (poolIndex < 0)
{
return 0f;
}
ref TimerInfo timer = ref _timerPool[poolIndex];
if (!timer.IsRunning)
{
return timer.RemainingTime;
}
float leftTime = timer.TriggerTime - GetCurrentTime(timer.IsUnscaled);
return leftTime > 0f ? leftTime : 0f;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Restart(int timerId)
{
int poolIndex = GetPoolIndex(timerId);
if (poolIndex < 0)
{
return;
}
ref TimerInfo timer = ref _timerPool[poolIndex];
if (timer.QueueIndex >= 0)
{
GetQueue(timer.IsUnscaled).Remove(poolIndex);
}
timer.TriggerTime = GetCurrentTime(timer.IsUnscaled) + timer.Duration;
timer.RemainingTime = 0f;
timer.IsRunning = true;
ScheduleTimer(poolIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveTimer(int timerId)
{
int poolIndex = GetPoolIndex(timerId);
if (poolIndex >= 0)
{
ReleaseTimer(poolIndex);
}
}
void IServiceTickable.Tick(float deltaTime)
{
_scaledQueue.Advance(Time.time);
_unscaledQueue.Advance(Time.unscaledTime);
}
protected override void OnInitialize()
{
}
protected override void OnDestroyService()
{
RemoveAllTimers();
}
public int Order => 0;
void ITimerServiceDebugView.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount)
{
activeCount = _activeCount;
poolCapacity = MAX_CAPACITY;
peakActiveCount = _peakActiveCount;
freeCount = _freeCount;
}
int ITimerServiceDebugView.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;
for (int i = 0; i < count; i++)
{
FillDebugInfo(_activeIndices[i], ref results[i], currentTime, currentUnscaledTime);
}
return count;
}
#if UNITY_EDITOR
int ITimerServiceDebugView.GetStaleOneShotTimers(TimerDebugInfo[] results)
{
if (results == null || results.Length == 0)
{
return 0;
}
int count = 0;
float realtimeSinceStartup = Time.realtimeSinceStartup;
float currentTime = Time.time;
float currentUnscaledTime = Time.unscaledTime;
for (int i = 0; i < _activeCount && count < results.Length; i++)
{
int poolIndex = _activeIndices[i];
ref TimerInfo timer = ref _timerPool[poolIndex];
if (timer.IsLoop)
{
continue;
}
if (realtimeSinceStartup - timer.CreationTime <= LEAK_DETECTION_THRESHOLD)
{
continue;
}
FillDebugInfo(poolIndex, ref results[count], currentTime, currentUnscaledTime);
count++;
}
return count;
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AcquireTimerIndex()
{
if (_freeCount <= 0)
{
return -1;
}
return _freeIndices[--_freeCount];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InitializeTimer(int poolIndex, float time, bool isLoop, bool isUnscaled)
{
ref TimerInfo timer = ref _timerPool[poolIndex];
float duration = NormalizeDelay(time);
float currentTime = GetCurrentTime(isUnscaled);
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 UNITY_EDITOR
timer.CreationTime = Time.realtimeSinceStartup;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveAllTimers()
{
while (_activeCount > 0)
{
ReleaseTimer(_activeIndices[_activeCount - 1]);
}
_scaledQueue.Clear();
_unscaledQueue.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessDueTimer(int poolIndex, float currentTime)
{
ref TimerInfo timer = ref _timerPool[poolIndex];
if (!timer.IsActive)
{
return;
}
bool shouldRemoveAfterCallback = !timer.IsLoop;
switch (timer.HandlerType)
{
case HANDLER_NO_ARGS:
timer.NoArgsHandler();
break;
case HANDLER_GENERIC:
timer.GenericInvoker(timer.GenericHandler, timer.GenericArg);
break;
}
if (!timer.IsActive)
{
return;
}
if (timer.QueueIndex >= 0)
{
return;
}
if (shouldRemoveAfterCallback)
{
ReleaseTimer(poolIndex);
}
else if (timer.IsRunning)
{
timer.TriggerTime = currentTime + timer.Duration;
ScheduleTimer(poolIndex);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ScheduleTimer(int poolIndex)
{
ref TimerInfo timer = ref _timerPool[poolIndex];
if (!timer.IsActive || !timer.IsRunning)
{
return;
}
if (timer.QueueIndex >= 0)
{
return;
}
float currentTime = GetCurrentTime(timer.IsUnscaled);
if (timer.TriggerTime <= currentTime)
{
timer.TriggerTime = currentTime + MINIMUM_DELAY;
}
GetQueue(timer.IsUnscaled).Add(poolIndex);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReleaseTimer(int poolIndex)
{
ref TimerInfo timer = ref _timerPool[poolIndex];
if (!timer.IsActive)
{
return;
}
if (timer.QueueIndex >= 0)
{
GetQueue(timer.IsUnscaled).Remove(poolIndex);
}
RemoveFromActiveSet(poolIndex);
timer.Clear();
_freeIndices[_freeCount++] = poolIndex;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddToActiveSet(int poolIndex)
{
int position = _activeCount;
_activeIndices[position] = poolIndex;
_timerPool[poolIndex].ActiveIndex = position;
_activeCount = position + 1;
if (_activeCount > _peakActiveCount)
{
_peakActiveCount = _activeCount;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void RemoveFromActiveSet(int poolIndex)
{
ref TimerInfo timer = ref _timerPool[poolIndex];
int position = timer.ActiveIndex;
if ((uint)position >= (uint)_activeCount)
{
return;
}
int lastPosition = --_activeCount;
int lastPoolIndex = _activeIndices[lastPosition];
if (position != lastPosition)
{
_activeIndices[position] = lastPoolIndex;
_timerPool[lastPoolIndex].ActiveIndex = position;
}
timer.ActiveIndex = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetPoolIndex(int timerId)
{
if (timerId <= 0)
{
return -1;
}
int poolIndex = (timerId & HANDLE_INDEX_MASK) - 1;
if ((uint)poolIndex >= MAX_CAPACITY)
{
return -1;
}
ref TimerInfo timer = ref _timerPool[poolIndex];
return timer.IsActive && timer.TimerId == timerId ? poolIndex : -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ComposeTimerId(int poolIndex, int version)
{
return (version << HANDLE_INDEX_BITS) | (poolIndex + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int NextVersion(int version)
{
version++;
if (version > HANDLE_VERSION_MASK)
{
version = 1;
}
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 TimerQueue GetQueue(bool isUnscaled)
{
return isUnscaled ? _unscaledQueue : _scaledQueue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FillDebugInfo(int poolIndex, ref TimerDebugInfo info, float currentTime, float currentUnscaledTime)
{
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;
}
info.TimerId = timer.TimerId;
info.LeftTime = leftTime;
info.Duration = timer.Duration;
info.IsLoop = timer.IsLoop;
info.IsRunning = timer.IsRunning;
info.IsUnscaled = timer.IsUnscaled;
#if UNITY_EDITOR
info.CreationTime = timer.CreationTime;
#endif
}
}
}