Unity 时间定时调度系统

C# Unity 时间定时调度系统

之前的文章也有写过时间调度系统,但是没有支持异步调度只有回调调度,而且效率和代码可读性不是很好,下面介绍一种更优质的时间调度系统

1.TimerAction

首先需要定义一个时间行为,每次延时后需要干什么,延迟的时间类型是什么都需要使用TimerAction

csharp 复制代码
public sealed class TimerAction : IDisposable
{
    private static long _id;//每个时间任务的ID生成
    public long Id;//每个时间任务的ID
    public long Time;//需要等待的时间(只有设置为Repeated的类型才有用)
    public object CallBack;//定时回调(不同类型的时间调度不同的类型可能是异步可能是委托所以用object类型)
    public TimerType TimerType;//时间调度类型
    public static TimerAction Create()
    {
        TimerAction timerAction = Pool<TimerAction>.Rent(); // 从引用池中创建时间调度任务减轻GC
        timerAction.Id = _id++;
        return timerAction;
    }
    public void Dispose()
    {
        Id = 0;
        Time = 0;
        CallBack = null;
        this.TimerType = TimerType.None;
        Pool<TimerAction>.Return(this);
    }
}

2.时间调度类型TimerType

csharp 复制代码
public enum TimerType 
{
    None,
    OnceTimer,//单次回调委托类型的时间调度
    OnceWaitTimer,//单次异步时间调度(await/async)
    RepeatedTimer,//重复的时间调度
}

3.TimerScheduler时间调度器

这个调度器是个单例,单例在此不讲解实现可以看之前的文章有提

(1)获取当前时间

由于是在Unity环境下则使用Unity获取时间的方式,若在服务器或者纯C#的环境该时间的获取需要根据对应时间填入即可

csharp 复制代码
    private long GetNowTime()
    {
        return (long)Time.time * 1000;
    }

(2)执行一个时间调度

csharp 复制代码
    public long OnceTimer(long time, Action action)
    {
        return OnceTillTimer(GetNowTime() + time, action);
    }
    public long OnceTillTimer(long tillTime, Action action)
    {
        if (tillTime < GetNowTime())
            Debug.LogError($"new once time too small tillTime:{tillTime} Now:{GetNowTime()}");
        TimerAction timerAction = TimerAction.Create();
        timerAction.CallBack = action;
        timerAction.TimerType = TimerType.OnceTimer;
        AddTimer(tillTime, timerAction);
        return timerAction.Id;
    }
  • 这里说明一下_minTime 这个字段用于辨别最小时间是否达到,如果连最小时间都没有达到就不执行所有时间调度的检测
csharp 复制代码
    private void AddTimer(long tillTime, TimerAction timerAction)
    {
        _timerActionDic.Add(timerAction.Id, timerAction);
        _timeIdDic.Add(tillTime, timerAction.Id);
        if (tillTime < _minTime)
            _minTime = tillTime;
    }

(3)执行时间调度检测

csharp 复制代码
    private long _minTime;
    private readonly Queue<long> _timeOutTimeQueue = new();//tillTime
    private readonly Queue<long> _timeOutIdsQueue = new();//id
    private readonly Dictionary<long, TimerAction> _timerActionDic = new();//Key: id
    private readonly SortedOneToManyList<long, long> _timeIdDic = new();//Key: tillTime  Value: id
csharp 复制代码
    public void Update()
    {
        try
        {
            long currTime = GetNowTime();
            if (_timeIdDic.Count == 0)
                return;
            if (currTime < _minTime)
                return;
            _timeOutTimeQueue.Clear();
            _timeOutIdsQueue.Clear();

            foreach (var (key, value) in _timeIdDic)
            {
                if (key > currTime)
                {
                    _minTime = key;
                    break;
                }
                _timeOutTimeQueue.Enqueue(key);
                
            }

            while (_timeOutTimeQueue.TryDequeue(out long tillTime))
            {
                foreach (long timerId in _timeIdDic[tillTime])
                {
                    _timeOutIdsQueue.Enqueue(timerId);
                }
                _timeIdDic.RemoveKey(tillTime);
            }

            while (_timeOutIdsQueue.TryDequeue(out long timerId))
            {
                if (!_timerActionDic.TryGetValue(timerId, out TimerAction timerAction))
                    continue;
                _timerActionDic.Remove(timerId);
                switch (timerAction.TimerType)
                {
                    case TimerType.OnceTimer:
                        {
                            Action action = (Action)timerAction.CallBack;
                            timerAction.Dispose();
                            if (action == null)
                            {
                                Debug.LogError("Call Back Is Null");
                                break;
                            }
                            action();
                        }
                        break;
                    case TimerType.RepeatedTimer:
                        {
                            Action action = (Action)timerAction.CallBack;
                            AddTimer(GetNowTime() + timerAction.Time, timerAction);
                            if (action == null)
                            {
                                Debug.LogError("Call Back Is Null");
                                break;
                            }
                            action();
                        }
                        break;
                    case TimerType.OnceWaitTimer:
                        {
                            TaskCompletionSource<bool> taskCompletionSource = (TaskCompletionSource<bool>)timerAction.CallBack;
                            timerAction.Dispose();
                            taskCompletionSource.SetResult(true);
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError(ex.Message);
        }
    }

(4)TimerScheduler完整代码

csharp 复制代码
public class TimerScheduler : Singleton<TimerScheduler>, IUpdateSingleton
{
    private long _minTime;
    private readonly Queue<long> _timeOutTimeQueue = new();//tillTime
    private readonly Queue<long> _timeOutIdsQueue = new();//id
    private readonly Dictionary<long, TimerAction> _timerActionDic = new();//Key: id
    private readonly SortedOneToManyList<long, long> _timeIdDic = new();//Key: tillTime  Value: id
    private long GetNowTime()
    {
        return (long)Time.time * 1000;
    }
    private void AddTimer(long tillTime, TimerAction timerAction)
    {
        _timerActionDic.Add(timerAction.Id, timerAction);
        _timeIdDic.Add(tillTime, timerAction.Id);
        if (tillTime < _minTime)
            _minTime = tillTime;
    }
    public long OnceTimer(long time, Action action)
    {
        return OnceTillTimer(GetNowTime() + time, action);
    }
    public long OnceTillTimer(long tillTime, Action action)
    {
        if (tillTime < GetNowTime())
            Debug.LogError($"new once time too small tillTime:{tillTime} Now:{GetNowTime()}");
        TimerAction timerAction = TimerAction.Create();
        timerAction.CallBack = action;
        timerAction.TimerType = TimerType.OnceTimer;
        AddTimer(tillTime, timerAction);
        return timerAction.Id;
    }
    public long RepeatedTimer(long time,Action action)
    {
        if (time <= 0)
        {
            throw new Exception("repeated time <= 0");
        }
        long tillTime = GetNowTime() + time;
        TimerAction timerAction = TimerAction.Create();
        timerAction.CallBack = action;
        timerAction.TimerType = TimerType.RepeatedTimer;
        timerAction.Time = time;
        AddTimer(tillTime, timerAction);
        return timerAction.Id;
    }
    public void Remove(long timerId)
    {
        if (!_timerActionDic.Remove(timerId, out TimerAction timerAction))
            return;
        timerAction?.Dispose();
    }
    public void Update()
    {
        try
        {
            long currTime = GetNowTime();
            if (_timeIdDic.Count == 0)
                return;
            if (currTime < _minTime)
                return;
            _timeOutTimeQueue.Clear();
            _timeOutIdsQueue.Clear();

            foreach (var (key, value) in _timeIdDic)
            {
                if (key > currTime)
                {
                    _minTime = key;
                    break;
                }
                _timeOutTimeQueue.Enqueue(key);
                
            }

            while (_timeOutTimeQueue.TryDequeue(out long tillTime))
            {
                foreach (long timerId in _timeIdDic[tillTime])
                {
                    _timeOutIdsQueue.Enqueue(timerId);
                }
                _timeIdDic.RemoveKey(tillTime);
            }

            while (_timeOutIdsQueue.TryDequeue(out long timerId))
            {
                if (!_timerActionDic.TryGetValue(timerId, out TimerAction timerAction))
                    continue;
                _timerActionDic.Remove(timerId);
                switch (timerAction.TimerType)
                {
                    case TimerType.OnceTimer:
                        {
                            Action action = (Action)timerAction.CallBack;
                            timerAction.Dispose();
                            if (action == null)
                            {
                                Debug.LogError("Call Back Is Null");
                                break;
                            }
                            action();
                        }
                        break;
                    case TimerType.RepeatedTimer:
                        {
                            Action action = (Action)timerAction.CallBack;
                            AddTimer(GetNowTime() + timerAction.Time, timerAction);
                            if (action == null)
                            {
                                Debug.LogError("Call Back Is Null");
                                break;
                            }
                            action();
                        }
                        break;
                    case TimerType.OnceWaitTimer:
                        {
                            TaskCompletionSource<bool> taskCompletionSource = (TaskCompletionSource<bool>)timerAction.CallBack;
                            timerAction.Dispose();
                            taskCompletionSource.SetResult(true);
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError(ex.Message);
        }
    }
    public async Task<bool> WaitAsync(long time, CancellationAction cancellationAction = null)
    {
        return await WaitTillAsync(GetNowTime() + time, cancellationAction);
    }
    public async Task<bool> WaitTillAsync(long tillTime, CancellationAction cancellationAction = null)
    {
        if (GetNowTime() > tillTime)
            return true;
        TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
        TimerAction timerAction = TimerAction.Create();
        long timerId = timerAction.Id;
        timerAction.CallBack = taskCompletionSource;
        timerAction.TimerType = TimerType.OnceWaitTimer;

        void CancelAction()
        {
            if (!_timerActionDic.ContainsKey(timerId))
                return;
            Remove(timerId);
            taskCompletionSource.SetResult(false);
        }

        bool b;
        try
        {
            cancellationAction?.Add(CancelAction);
            AddTimer(tillTime, timerAction);
            b = await taskCompletionSource.Task;
        }
        catch(Exception ex)
        {
            Debug.LogError(ex.Message);
            return true;
        }

        return b;
    }

    protected override void Load(int assemblyName)
    {
    }

    protected override void UnLoad(int assemblyName)
    {
    }
}

4.测试

  • 提供一下CancellationAction代码实现,用于一切的取消事件(非时间调度器专属,任何取消操作都可参考)
csharp 复制代码
public sealed class CancellationAction
{
    private HashSet<Action> _actions = new HashSet<Action>();
    public bool IsCanel => _actions == null;

    public void Add(Action action)
    {
        _actions.Add(action);
    }

    public void Remove(Action action)
    {
        _actions.Remove(action);
    }

    public void Cancel()
    {
        if (_actions == null)
            return;

        foreach (Action action in _actions)
        {
            try
            {
                action?.Invoke();
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
            }
        }

        _actions.Clear();
        _actions = null;
    }
}
csharp 复制代码
public class TestTimer : MonoBehaviour
{
    private long repeatedId;
    private CancellationAction timerCancelAction = new CancellationAction();
    async void Start()
    {
        SingletonSystem.Initialize();
        AssemblyManager.Initialize();

        TimerScheduler.Instance.OnceTimer(5000, () =>
        {
            Debug.Log("第一个5s后了!!!");
        });
        TimerScheduler.Instance.OnceTimer(5000, () =>
        {
            Debug.Log("第二个5s后了!!!");
        });
        TimerScheduler.Instance.OnceTimer(6000, () =>
        {
            Debug.Log("6s后了!!!");
        });

        repeatedId = TimerScheduler.Instance.RepeatedTimer(2000, () =>
        {
            Debug.Log("每两秒重复运行!!!");
        });

        await TimerScheduler.Instance.WaitAsync(3000);
        Debug.Log("这是第一个3s以后");
        await TimerScheduler.Instance.WaitAsync(3000);
        Debug.Log("这是第二个3s以后");

        bool isCanel = await TimerScheduler.Instance.WaitAsync(5000, timerCancelAction);
        if (!isCanel)
        {
            Debug.Log("取消定时");
        }
        else
        {
            Debug.Log("五秒后执行!!!");
        }
    }

    void Update()
    {
        SingletonSystem.Update();
        if (Input.GetKeyDown(KeyCode.P))
        {
            if (!timerCancelAction.IsCanel)
            {
                timerCancelAction.Cancel();
            }
        }
    }
}
相关推荐
leo__52015 分钟前
单载波中继系统资源分配算法MATLAB仿真程序
算法·matlab·unity
努力长头发的程序猿1 小时前
Unity使用ScriptableObject序列化资源
unity·游戏引擎
mxwin2 小时前
Unity Shader 手写基于 PBR 的 URP Lit Shader 核心光照计算
unity·游戏引擎·shader
小贺儿开发2 小时前
Unity3D 智能云端数字标牌系统
unity·阿里云·人机交互·视频·oss·广告·互动
魔士于安2 小时前
Unity windows 同步 异步 打开文件文件夹工具
游戏·unity·游戏引擎·贴图·模型
笑虾3 小时前
cocos2d-x lua 加载 Cocos Studio 导出的 csb
游戏引擎·lua·cocos2d
魔士于安3 小时前
unity lowpoly 风格 城市 建筑 道路 交通标志
游戏·unity·游戏引擎·贴图·模型
mxwin3 小时前
Unity GPU Shader 性能优化指南
unity·游戏引擎·shader
董董女友15 小时前
unity mcp 配置指南
unity·游戏引擎
垂葛酒肝汤20 小时前
Unity的可视化网格和文字标签
unity·游戏引擎