Unity中实现Timer(2)

需求背景

上次做的timer,在实际开发中遇到了error,报错为:在遍历dictionary中,不能对collection进行更改。后面我尝试过使用lock字段锁住字典,但上网查询才知道lock常用于多线程中,所以在我的需求环境中是无法生效的。另外上一次是使用的UpdateRegister对timer进行更新的,此种调用方式不灵活。所以此次的需求就是对timer进行优化
这是上一次timer:https://www.cnblogs.com/JunJiang-Blog/p/16948170.html

设计思路

外部想要加入到更新中的timer列表中,就需要调用add和remove方法。上一次是通过add和remove直接对更新列表进行操作,错误原因也在这里:我无法保证在遍历中,不对更新列表进行修改。

  1. 在add方法中,新建一个容器waitAddTimers,将准备加入到更新列表中的timer存储起来,在每次遍历之前,将waitAddTimers中的元素复制到更新列表中去,然后清空waitAddTimers,这样就能保证在遍历中不会操作更新列表
  2. 在remove方法中,新建一个容器waitRemoveTimers,将准备从更新列表中移除的timer的flag记录下来,在每次遍历之前,将waitRemoveTimers的flag取出,并在更新列表中移除。
  3. timer更新可以使用自身的静态更新方法,而不必注册到UpdateRegister中。这样我的timer更新方法想放在哪里就放在哪里,更加灵活。

具体实现

Timer的实例方法

构造函数
        public Timer(int _repeatTimes, float _repeatDeltaTime, Action _callback)
        {
            this.isLoop = false;
            this.repeatTimes = _repeatTimes;
            this.repeatDeltaTime = _repeatDeltaTime;
            this.callback = _callback;
        }

        public Timer(bool _isLoop, float _repeatDeltaTime, Action _callback)
        {
            if (!_isLoop)
            {
                this.repeatTimes = 1;
            }
            this.isLoop = _isLoop;
            this.repeatDeltaTime = _repeatDeltaTime;
            this.callback = _callback;
        }

        public Timer(float _repeatDeltaTime, Action _callback)
        {
            this.isLoop = false;
            this.repeatTimes = 1;
            this.repeatDeltaTime = _repeatDeltaTime;
            this.callback = _callback;
        }
对外接口
        /// <summary>
        /// 开始计时器
        /// </summary>
        public void Start()
        {
            if (isPlaying)
            {
                Debug.LogWarning("timer is alreay play, so this action will not works!!!");
                return;
            }
            isPlaying = true;
            curTime = 0f;
            preInvokeTime = 0f;
            curRepeatTimes = 0;
            flag = Timer.AddTimer(this);
        }

        /// <summary>
        /// 停止计时器(如果只是想暂停可用Pause,Stop操作不可回复)
        /// </summary>
        public void Stop()
        {
            if (!isPlaying)
            {
                Debug.LogWarning("timer is not playing, so this action will not works!!!");
                return;
            }
            isPlaying = false;
            Timer.RemoveTimer(flag);
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public void Pause()
        {
            isPlaying = false;
        }

        /// <summary>
        /// 继续
        /// </summary>
        public void Resume()
        {
            isPlaying = true;
        }

        public void Complete()
        {
            if (Timer.ContainsTimer(timerFlag))
            {
                Timer.RemoveTimer(timerFlag);
            }
            callback?.Invoke();
            isPlaying = false;
        }

        /// <summary>
        /// 注销
        /// </summary>
        public void Dispose()
        {
            callback = null;
        }
更新
        private void Update(float deltaTime)
        {
            if (!isPlaying)
            {
                return;
            }
            curTime += deltaTime;
            if (curTime - preInvokeTime >= repeatDeltaTime)
            {
                callback?.Invoke();
                preInvokeTime = curTime;
                if (!isLoop)
                {
                    ++curRepeatTimes;
                    if (curRepeatTimes >= repeatTimes)
                    {
                        Stop();
                    }
                }
            }
        }

Timer的静态方法

对外接口
        /// <summary>
        /// 判断是否包含此timer
        /// </summary>
        /// <param name="flag">timer唯一标识</param>
        /// <returns></returns>
        public static bool ContainsTimer(uint flag)
        {
            return allTimers.ContainsKey(flag) || waitAddTimers.ContainsKey(flag);
        }

        /// <summary>
        /// 添加timer到更新列表中
        /// </summary>
        /// <param name="timer"></param>
        /// <returns></returns>
        public static uint AddTimer(Timer timer)
        {
            uint result = timerFlag;
            if (!allTimers.ContainsKey(result))
            {
                waitAddTimers.Add(result, timer);
                ++timerFlag;
                return result;
            }
            Debug.LogError(string.Format("this flag{0} alreay exsit in cache, please check!!!", result));
            return 0;
        }

        /// <summary>
        /// 移除timer
        /// </summary>
        /// <param name="targetFlag">timer唯一标识</param>
        public static void RemoveTimer(uint targetFlag)
        {
            waitRemoveTimers.Add(targetFlag);
        }
		
        /// <summary>
        /// 清理所有timer
        /// </summary>
        public static void ClearAllTimers()
        {
            lock (allTimers)
            {
                using (var e = allTimers.GetEnumerator())
                {
                    while (e.MoveNext())
                    {
                        e.Current.Value.Stop();
                    }
                }
            }
            allTimers.Clear();
        }
更新
        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="deltaTime">每次更新间隔时长</param>
        public static void Tick(float deltaTime)
        {
            if (waitAddTimers.Count > 0)
            {
                ClearWaitAddTimers();
            }
            if (waitRemoveTimers.Count > 0)
            {
                ClearWaitRemoveTimers();
            }
            using (var e = allTimers.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    e.Current.Value.Update(deltaTime);
                }
            }
        }
对内接口
        /// <summary>
        /// 清理等待添加的timer队列
        /// </summary>
        private static void ClearWaitAddTimers()
        {
            using (var e = waitAddTimers.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    allTimers.Add(e.Current.Key, e.Current.Value);
                }
            }
            waitAddTimers.Clear();
        }

        /// <summary>
        /// 清理等待移除的timer队列
        /// </summary>
        private static void ClearWaitRemoveTimers()
        {
            foreach (var flag in waitRemoveTimers)
            {
                if (allTimers.ContainsKey(flag))
                {
                    allTimers.Remove(flag);
                }
            }
            waitRemoveTimers.Clear();
        }

Timer代码

点击查看代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

namespace JunFramework
{
    public class Timer:IDisposable
    {
        private uint flag;

        private float curTime;
        private float preInvokeTime;

        private bool isLoop;

        private int repeatTimes;
        private int curRepeatTimes;

        private float repeatDeltaTime;

        private Action callback;

        private bool isPlaying;

        public bool IsPlaying
        {
            get => isPlaying;
        }

        public Timer(int _repeatTimes, float _repeatDeltaTime, Action _callback)
        {
            this.isLoop = false;
            this.repeatTimes = _repeatTimes;
            this.repeatDeltaTime = _repeatDeltaTime;
            this.callback = _callback;
        }

        public Timer(bool _isLoop, float _repeatDeltaTime, Action _callback)
        {
            if (!_isLoop)
            {
                this.repeatTimes = 1;
            }
            this.isLoop = _isLoop;
            this.repeatDeltaTime = _repeatDeltaTime;
            this.callback = _callback;
        }

        public Timer(float _repeatDeltaTime, Action _callback)
        {
            this.isLoop = false;
            this.repeatTimes = 1;
            this.repeatDeltaTime = _repeatDeltaTime;
            this.callback = _callback;
        }

        private void Update(float deltaTime)
        {
            if (!isPlaying)
            {
                return;
            }
            curTime += deltaTime;
            if (curTime - preInvokeTime >= repeatDeltaTime)
            {
                callback?.Invoke();
                preInvokeTime = curTime;
                if (!isLoop)
                {
                    ++curRepeatTimes;
                    if (curRepeatTimes >= repeatTimes)
                    {
                        Stop();
                    }
                }
            }
        }

        /// <summary>
        /// 开始计时器
        /// </summary>
        public void Start()
        {
            if (isPlaying)
            {
                Debug.LogWarning("timer is alreay play, so this action will not works!!!");
                return;
            }
            isPlaying = true;
            curTime = 0f;
            preInvokeTime = 0f;
            curRepeatTimes = 0;
            flag = Timer.AddTimer(this);
        }

        /// <summary>
        /// 停止计时器(如果只是想暂停可用Pause,Stop操作不可回复)
        /// </summary>
        public void Stop()
        {
            if (!isPlaying)
            {
                Debug.LogWarning("timer is not playing, so this action will not works!!!");
                return;
            }
            isPlaying = false;
            Timer.RemoveTimer(flag);
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public void Pause()
        {
            isPlaying = false;
        }

        /// <summary>
        /// 继续
        /// </summary>
        public void Resume()
        {
            isPlaying = true;
        }

        public void Complete()
        {
            if (Timer.ContainsTimer(timerFlag))
            {
                Timer.RemoveTimer(timerFlag);
            }
            callback?.Invoke();
            isPlaying = false;
        }

        /// <summary>
        /// 注销
        /// </summary>
        public void Dispose()
        {
            callback = null;
        }

        #region static

        private static uint timerFlag = 0;
        private static Dictionary<uint, Timer> allTimers = new Dictionary<uint, Timer>();
        private static Dictionary<uint, Timer> waitAddTimers = new Dictionary<uint, Timer>();
        private static List<uint> waitRemoveTimers = new List<uint>();

        /// <summary>
        /// 判断是否包含此timer
        /// </summary>
        /// <param name="flag">timer唯一标识</param>
        /// <returns></returns>
        public static bool ContainsTimer(uint flag)
        {
            return allTimers.ContainsKey(flag) || waitAddTimers.ContainsKey(flag);
        }

        /// <summary>
        /// 添加timer到更新列表中
        /// </summary>
        /// <param name="timer"></param>
        /// <returns></returns>
        public static uint AddTimer(Timer timer)
        {
            uint result = timerFlag;
            if (!allTimers.ContainsKey(result))
            {
                waitAddTimers.Add(result, timer);
                ++timerFlag;
                return result;
            }
            Debug.LogError(string.Format("this flag{0} alreay exsit in cache, please check!!!", result));
            return 0;
        }

        /// <summary>
        /// 移除timer
        /// </summary>
        /// <param name="targetFlag">timer唯一标识</param>
        public static void RemoveTimer(uint targetFlag)
        {
            waitRemoveTimers.Add(targetFlag);
        }

        /// <summary>
        /// 更新
        /// </summary>
        /// <param name="deltaTime">每次更新间隔时长</param>
        public static void Tick(float deltaTime)
        {
            if (waitAddTimers.Count > 0)
            {
                ClearWaitAddTimers();
            }
            if (waitRemoveTimers.Count > 0)
            {
                ClearWaitRemoveTimers();
            }
            using (var e = allTimers.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    e.Current.Value.Update(deltaTime);
                }
            }
        }

        /// <summary>
        /// 清理所有timer
        /// </summary>
        public static void ClearAllTimers()
        {
            lock (allTimers)
            {
                using (var e = allTimers.GetEnumerator())
                {
                    while (e.MoveNext())
                    {
                        e.Current.Value.Stop();
                    }
                }
            }
            allTimers.Clear();
        }

        /// <summary>
        /// 清理等待添加的timer队列
        /// </summary>
        private static void ClearWaitAddTimers()
        {
            using (var e = waitAddTimers.GetEnumerator())
            {
                while (e.MoveNext())
                {
                    allTimers.Add(e.Current.Key, e.Current.Value);
                }
            }
            waitAddTimers.Clear();
        }

        /// <summary>
        /// 清理等待移除的timer队列
        /// </summary>
        private static void ClearWaitRemoveTimers()
        {
            foreach (var flag in waitRemoveTimers)
            {
                if (allTimers.ContainsKey(flag))
                {
                    allTimers.Remove(flag);
                }
            }
            waitRemoveTimers.Clear();
        }
        
        #endregion
    }
}

应用场景

回调队列的实现

https://www.cnblogs.com/JunJiang-Blog/p/17207901.html

延迟调用方法

总结

上一次的Timer居然有bug,这是我没想到的。原因时忽略了collection在遍历途中不能修改的性质。这一次的Timer目前没遇到问题,还在不断完善中,欢迎指正

posted @ 2023-03-12 11:50  军酱不是酱  阅读(85)  评论(0编辑  收藏  举报