236 lines
7.8 KiB
C#
236 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using AlicizaX;
|
|
using UnityEngine;
|
|
|
|
namespace AlicizaX.Timer.Runtime
|
|
{
|
|
public delegate void TimerHandler(params object[] args);
|
|
|
|
[Serializable]
|
|
internal class Timer : IMemory
|
|
{
|
|
public int TimerId;
|
|
public float TriggerTime;
|
|
public float Interval;
|
|
public TimerHandler Handler;
|
|
public bool IsLoop;
|
|
public bool IsRunning;
|
|
public bool IsUnscaled;
|
|
public bool IsActive; // 标记定时器是否有效
|
|
public object[] Args;
|
|
public LinkedListNode<Timer> Node; // 用于时间轮中的链表节点
|
|
|
|
public void Clear()
|
|
{
|
|
Handler = null;
|
|
Args = null;
|
|
Node = null;
|
|
IsActive = false;
|
|
}
|
|
}
|
|
|
|
[UnityEngine.Scripting.Preserve]
|
|
internal sealed class TimerModule : ITimerModule
|
|
{
|
|
private int _curTimerId;
|
|
private TimeWheel _scaledTimeWheel;
|
|
private TimeWheel _unscaledTimeWheel;
|
|
private readonly Dictionary<int, Timer> _activeTimers = new Dictionary<int, Timer>();
|
|
private readonly Action<Timer> _processTimerDelegate;
|
|
private class TimeWheel
|
|
{
|
|
private readonly float _slotInterval; // 每个槽的时间间隔(秒)
|
|
private readonly int _slotCount; // 槽的数量
|
|
private readonly LinkedList<Timer>[] _slots;
|
|
private float _currentTime;
|
|
private int _currentSlotIndex;
|
|
|
|
public TimeWheel(float slotInterval, int slotCount)
|
|
{
|
|
_slotInterval = slotInterval;
|
|
_slotCount = slotCount;
|
|
_slots = new LinkedList<Timer>[slotCount];
|
|
for (int i = 0; i < slotCount; i++)
|
|
_slots[i] = new LinkedList<Timer>();
|
|
_currentTime = 0f;
|
|
_currentSlotIndex = 0;
|
|
}
|
|
|
|
public void AddTimer(Timer timer, float currentTime)
|
|
{
|
|
if (!timer.IsActive) return;
|
|
|
|
float triggerTime = timer.TriggerTime;
|
|
float delta = triggerTime - currentTime;
|
|
|
|
if (delta < 0)
|
|
{
|
|
// 立即触发
|
|
delta = 0;
|
|
}
|
|
|
|
int slotsToAdvance = Mathf.FloorToInt(delta / _slotInterval);
|
|
int targetSlot = (_currentSlotIndex + slotsToAdvance) % _slotCount;
|
|
|
|
timer.Node = _slots[targetSlot].AddLast(timer);
|
|
timer.IsRunning = true;
|
|
}
|
|
|
|
public void Advance(float currentTime, Action<Timer> processTimer)
|
|
{
|
|
float timeDelta = currentTime - _currentTime;
|
|
if (timeDelta <= 0) return;
|
|
|
|
int steps = Mathf.FloorToInt(timeDelta / _slotInterval);
|
|
for (int i = 0; i < steps; i++)
|
|
{
|
|
_currentSlotIndex = (_currentSlotIndex + 1) % _slotCount;
|
|
_currentTime += _slotInterval;
|
|
|
|
LinkedList<Timer> currentSlot = _slots[_currentSlotIndex];
|
|
LinkedListNode<Timer> currentNode = currentSlot.First;
|
|
while (currentNode != null)
|
|
{
|
|
LinkedListNode<Timer> nextNode = currentNode.Next;
|
|
Timer timer = currentNode.Value;
|
|
currentSlot.Remove(currentNode);
|
|
|
|
if (timer.IsActive && timer.IsRunning)
|
|
{
|
|
processTimer(timer);
|
|
}
|
|
|
|
currentNode = nextNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public TimerModule()
|
|
{
|
|
_scaledTimeWheel = new TimeWheel(1f, 60);
|
|
_unscaledTimeWheel = new TimeWheel(1f, 60);
|
|
_processTimerDelegate = ProcessTimer; // 初始化时创建委托一次
|
|
}
|
|
|
|
|
|
public int AddTimer(TimerHandler callback, float time, bool isLoop = false,
|
|
bool isUnscaled = false, params object[] args)
|
|
{
|
|
Timer timer = GetTimerFromPool();
|
|
timer.TimerId = ++_curTimerId;
|
|
timer.TriggerTime = (isUnscaled ? Time.unscaledTime : Time.time) + time;
|
|
timer.Interval = isLoop ? time : 0f;
|
|
timer.Handler = callback;
|
|
timer.IsLoop = isLoop;
|
|
timer.IsRunning = true;
|
|
timer.IsUnscaled = isUnscaled;
|
|
timer.Args = args;
|
|
timer.IsActive = true;
|
|
|
|
_activeTimers.Add(timer.TimerId, timer);
|
|
TimeWheel targetWheel = isUnscaled ? _unscaledTimeWheel : _scaledTimeWheel;
|
|
targetWheel.AddTimer(timer, isUnscaled ? Time.unscaledTime : Time.time);
|
|
return timer.TimerId;
|
|
}
|
|
|
|
public void Stop(int timerId)
|
|
{
|
|
if (_activeTimers.TryGetValue(timerId, out Timer timer))
|
|
timer.IsRunning = false;
|
|
}
|
|
|
|
public void Resume(int timerId)
|
|
{
|
|
if (_activeTimers.TryGetValue(timerId, out Timer timer))
|
|
timer.IsRunning = true;
|
|
}
|
|
|
|
public bool IsRunning(int timerId) =>
|
|
_activeTimers.TryGetValue(timerId, out Timer timer) && timer.IsRunning;
|
|
|
|
public float GetLeftTime(int timerId) =>
|
|
_activeTimers.TryGetValue(timerId, out Timer timer)
|
|
? Mathf.Max(timer.TriggerTime - (timer.IsUnscaled ? Time.unscaledTime : Time.time), 0)
|
|
: 0;
|
|
|
|
public void Restart(int timerId)
|
|
{
|
|
if (_activeTimers.TryGetValue(timerId, out Timer timer))
|
|
{
|
|
timer.TriggerTime = (timer.IsUnscaled ? Time.unscaledTime : Time.time) + timer.Interval;
|
|
TimeWheel targetWheel = timer.IsUnscaled ? _unscaledTimeWheel : _scaledTimeWheel;
|
|
targetWheel.AddTimer(timer, timer.IsUnscaled ? Time.unscaledTime : Time.time);
|
|
}
|
|
}
|
|
|
|
public void RemoveTimer(int timerId)
|
|
{
|
|
if (_activeTimers.TryGetValue(timerId, out Timer timer))
|
|
{
|
|
timer.IsActive = false; // 标记为无效
|
|
_activeTimers.Remove(timerId);
|
|
ReturnTimerToPool(timer);
|
|
}
|
|
}
|
|
|
|
public void RemoveAllTimer()
|
|
{
|
|
foreach (var timer in _activeTimers.Values)
|
|
{
|
|
timer.IsActive = false;
|
|
ReturnTimerToPool(timer);
|
|
}
|
|
|
|
_activeTimers.Clear();
|
|
}
|
|
|
|
private Timer GetTimerFromPool() => MemoryPool.Acquire<Timer>();
|
|
|
|
|
|
private void ReturnTimerToPool(Timer timer)
|
|
{
|
|
MemoryPool.Release(timer);
|
|
}
|
|
|
|
void IModuleUpdate.Update(float elapseSeconds, float realElapseSeconds)
|
|
{
|
|
float scaledTime = Time.time;
|
|
_scaledTimeWheel.Advance(scaledTime, _processTimerDelegate);
|
|
|
|
float unscaledTime = Time.unscaledTime;
|
|
_unscaledTimeWheel.Advance(unscaledTime, _processTimerDelegate);
|
|
}
|
|
|
|
private void ProcessTimer(Timer timer)
|
|
{
|
|
if (!timer.IsActive || !timer.IsRunning) return;
|
|
|
|
try
|
|
{
|
|
timer.Handler?.Invoke(timer.Args);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error($"Timer callback error: {e}");
|
|
}
|
|
|
|
if (timer.IsLoop)
|
|
{
|
|
timer.TriggerTime += timer.Interval;
|
|
TimeWheel targetWheel = timer.IsUnscaled ? _unscaledTimeWheel : _scaledTimeWheel;
|
|
targetWheel.AddTimer(timer, timer.IsUnscaled ? Time.unscaledTime : Time.time);
|
|
}
|
|
else
|
|
{
|
|
RemoveTimer(timer.TimerId);
|
|
}
|
|
}
|
|
|
|
void IModule.Dispose() => RemoveAllTimer();
|
|
|
|
public int Priority => 0;
|
|
}
|
|
}
|