Unity Timer 轻量高效高精度定时器 实测比Dotween带的定时器开销少10倍

8 篇文章 0 订阅
class Timer

Description

A simple and efficient timer class.

Syntax is close to JavaScript.

Note that direct calls do not need to be attached to the scene.

The current timer program runs the true Time for the game unaffected by time.timescale

精度对比

在这里插入图片描述
Dotween的定时器运行情况 - 实际上现在网上大部分的定时器都是这样的情况不够精确只能保证秒级相似 然后下一秒补偿 不能保证帧级的精准度
在这里插入图片描述
而优化的Timer和Unity自带的Invoke 是帧级别的精度
在这里插入图片描述

用法

在这里插入图片描述````

   private void Start()
   {
    
        Timer.SetTimeout( 2f, () =>
        {
            Debug.Log( "Delay Call" );
        } );


        Timer.SetInterval( 1f, () =>
        {
            Debug.Log( "Call per Second" );
        } );

        
        Timer.SetInterval( 0f, () =>
        {
            //call for pre frames 


            //listen any key event
            if( Input.anyKey )
            {

                // listen mouseButton Event
                if ( Input.GetMouseButtonDown( 0 ) )
                {
                    Debug.Log( "hello Timer." );
                }

                if( Input.GetKeyDown( KeyCode.Space ) )
                {
                    Debug.Log( "Clean self all generate time." );
                    Timer.ClearTimer( this );
                }
            }
        } );
    
    }

功能支持
调度任务对象自动回收True
单次延迟调用True
循环定时调度True
可通过对象来清理所有定时器True
进入后台暂停定时器True (可以通过修改 m_isBackground 来开关)
是否有多个线上项目磨练目前有四个线上项目运行 无异常情况

源码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
///
/// Timer 轻量高效定时器
/// 
/// 当前定时器程序为游戏运行真实时间 
/// 内置对象池Task回收
/// 已及更方便灵活的调用方式
/// 
/// Anchor: ChenJC
/// Time: 2022/10/09
/// 原文: https://blog.csdn.net/qq_39162566/article/details/113105351
/// </summary>
public class Timer : MonoBehaviour
{

    //定时器数据类
    public class TimerTask
    {
        public ulong ID;
        public float lifeCycle;
        public float expirationTime;
        public long times;
        public Action func;

        //你可以通过此方法来获取定时器的运行进度  0 ~ 1  1.0表示即将要调用func
        //你可以通过 GetTimer( id ) 获取当前Task的Clone体 
        public float progress
        {
            get
            {
                return 1.0f - Mathf.Clamp01( ( expirationTime - Time.time ) / lifeCycle );
            }
        }

        //获取一个当前TimerTask的副本出来
        public TimerTask Clone()
        {
            var timerTask = new TimerTask();
            timerTask.ID = ID;
            timerTask.expirationTime = expirationTime;
            timerTask.lifeCycle = lifeCycle;
            timerTask.times = times;
            timerTask.func = func;
            return timerTask;
        }

        //释放回收当前定时器
        public void Destory()
        {
            freeTaskCls.Enqueue( this );
        }

        //重置
        public void Reset()
        {
            expirationTime = Time.time + lifeCycle;
        }
    }


    #region Member property

    protected static List<TimerTask> activeTaskCls = new List<TimerTask>();//激活中的TimerTask对象
    protected static Queue<TimerTask> freeTaskCls = new Queue<TimerTask>();//闲置TimerTask对象
    protected static HashSet<Action> lateChannel = new HashSet<Action>();//确保callLate调用的唯一性
    protected static ulong idOffset = 1000; //timer的唯一标识
    protected static bool inited = false; //避免重复创建 防呆设计
    #endregion


    #region Enable timer methods

    //每帧结束时执行回调 : 当前帧内的多次调用仅在当前帧结束的时候执行一次
    public static void CallerLate( Action func )
    {
        if ( !lateChannel.Contains( func ) )
        {
            lateChannel.Add( func );
            SetTimeout( 0f, func );
        }
    }


    //delay秒后 执行一次回调
    public static ulong SetTimeout( float delay, Action func )
    {
        return SetInterval( delay, func, false, 1 );
    }

    /// <summary>
    /// 周期性定时器 间隔一段时间调用一次
    /// </summary>
    /// <param name="interval"> 间隔时长: 秒</param>
    /// <param name="func"> 调用的方法回调 </param>
    /// <param name="immediate"> 是否立即执行一次 </param>
    /// <param name="times"> 调用的次数: 默认永久循环 当值<=0时会一直更新调用 当值>0时 循环指定次数后 停止调用 </param>
    /// <returns></returns>
    public static ulong SetInterval( float interval, Action func, bool immediate = false, int times = 0 )
    {
        //从free池中 获取一个闲置的TimerTask对象
        var timer = GetFreeTimerTask();
        timer.lifeCycle = interval;
        timer.Reset();
        timer.func = func;
        timer.times = times;
        timer.ID = ++idOffset;

        //尝试初始化
        Init();

        //立即执行一次
        if ( immediate )
        {
            --timer.times;
            func?.Invoke();
            if ( timer.times == 0 )
            {

                timer.Destory();
            }
            else
            {
                //添加到激活池中
                activeTaskCls.Add( timer );
            }
        }
        else
        {
            //添加到激活池中
            activeTaskCls.Add( timer );
        }

        return idOffset;
    }

    #endregion


    #region Get Timer methods

    /// <summary>
    /// 通过Tag获取定时器对象
    /// </summary>
    /// <param name="ID"></param>
    /// <returns></returns>
    public static TimerTask GetTimer( ulong ID )
    {
        return activeTaskCls.Find( ( TimerTask t ) =>
         {
             return t.ID == ID;
         } )?.Clone();
    }

    /// <summary>
    /// 通过方法获取定时器对象
    /// </summary>
    /// <param name="tag"></param>
    /// <returns></returns>
    public static List<TimerTask> GetTimer( Action func )
    {
        return activeTaskCls.FindAll( t =>
         {
             return t.func == func;
         } );
    }

    /// <summary>
    /// 通过对用对象获取定时器对象
    /// </summary>
    /// <param name="tag"></param>
    /// <returns></returns>
    public static List<TimerTask> GetTimer( object target )
    {
        return activeTaskCls.FindAll( t =>
        {
            return t.func.Target == target;
        } );
    }
    #endregion


    #region Clean Timer methods

    /// <summary>
    /// 通过ID 清理定时器
    /// </summary>
    /// <param name="ID">定时器标签</param>
    /// <returns></returns>
    public static void ClearTimer( ulong ID )
    {
        int index = activeTaskCls.FindIndex( ( TimerTask t ) =>
         {
             return t.ID == ID;
         } );

        if ( index != -1 )
        {
            var timerTask = activeTaskCls[ index ];
            ClearTimers( new List<TimerTask>() { timerTask } );
        }
    }


    /// <summary>
    /// 通过类型来ClearTimer
    /// @ps: 移除同类型的所有成员方法定时器  包含( lambda 和 其它类实体 )
    /// </summary>
    /// <param name="clsType"></param>
    public static void ClearTimer<T>()
    {
        var type = typeof( T );
        var clsName = type.FullName;

        var allMatchTask = activeTaskCls.FindAll( t =>
        {
            if ( null != t.func && null != t.func.Target )
            {
                var fullname = t.func.Target.GetType().FullName;
                var currentClsNameClip = fullname.Split( '+' );
                if ( currentClsNameClip.Length > 0 )
                {
                    if ( currentClsNameClip[ 0 ] == clsName )
                    {
                        return true;
                    }
                }
            }
            return false;
        } );

        ClearTimers( allMatchTask );
    }

    /// <summary>
    /// 通过方法 清理定时器
    /// </summary>
    /// <param name="func">处理方法</param>
    /// <returns></returns>
    public static void ClearTimer( Action func )
    {
        var allMatchTask = activeTaskCls.FindAll( t => t.func == func );
        ClearTimers( allMatchTask );
    }


    /// <summary>
    /// 清理当前类的所有方法
    /// 
    /// 支持清理类的成员方法相关定时器清理 也包含有限lambda的释放
    /// 
    /// 支持的lambda:
    /// 
    /// 
    /// 
    //class Mtest
    //{
    //    string str_n = "123";
    //    public Mtest()
    //    {
    //        test(); //此方法内的闭包调用支持
    //        test2(); //此方法内的闭包调用不支持
    //        Timer.SetInterval( 1f,UpdatePerSecond ); //成员方法支持
    //    }
    //    //这个方法内的闭包支持
    //    private void test()
    //    {
    //        Timer.SetTimeout( 1.0f, () =>
    //        {
    //            //在lambda内部定义的变量
    //            string t = "12313213";
    //            //在lambda内部访问和修改类成员变量行为
    //            str_n = t;
    //        } );
    //    }
    //    //类成员支持
    //    private void UpdatePerSecond()
    //    {
    //        Debug.Log( Time.realtimeSinceStartup );
    //    }
    //    //这个方法内的闭包不支持
    //    private void test2()
    //    {
    //        //在lambda外定义的变量
    //        string t = "12313213";
    //        Timer.SetTimeout( 1.0f, () =>
    //        {
    //            //在lambda内部访问lambda外部的变量行为会让当前闭包的 Target变成一个新的类
    //            t = "1231";
    //            //在lambda内部访问和修改类成员变量行为
    //            str_n = t;
    //        } );
    //    }
    //    //清理这个类的所有定时器调度
    //    private void clearTime()
    //    {
    //        Timer.ClearTimer( this );
    //    }
    //}
    /// 
    /// 
    /// </summary>
    /// <param name="func">处理方法</param>
    /// <returns></returns>
    public static void ClearTimer( object target )
    {
        var allMatchTask = activeTaskCls.FindAll( t => t.func.Target == target );
        ClearTimers( allMatchTask );
    }


    /// <summary>
    /// 清理所有定时器
    /// </summary>
    public static void ClearTimers()
    {
        lateChannel.Clear();
        activeTaskCls.ForEach( timer => freeTaskCls.Enqueue( timer ) );
        activeTaskCls.Clear();
    }


    /// <summary>
    /// 批量清理定时器
    /// </summary>
    /// <param name="allMatchTask"></param>
    public static void ClearTimers( List<TimerTask> allMatchTask )
    {
        allMatchTask?.ForEach( task =>
        {
            if ( lateChannel.Count != 0 && lateChannel.Contains( task.func ) )
            {
                lateChannel.Remove( task.func );
            }
            activeTaskCls.Remove( task );
            freeTaskCls.Enqueue( task );
        } );
    }

    #endregion


    #region System methods

    //Update更新之前
    private void Awake()
    {
        DontDestroyOnLoad( gameObject );
        StartCoroutine( TimeElapse() );
    }

    //定时器调度
    private IEnumerator TimeElapse()
    {
        TimerTask t = null;
        while ( true )
        {
            if ( activeTaskCls.Count > 0 )
            {
                float time = Time.time;
                for ( int i = 0; i < activeTaskCls.Count; ++i )
                {
                    t = activeTaskCls[ i ];

                    if ( t.expirationTime <= time )
                    {
                        if ( t.times == 1 )
                        {
                            activeTaskCls.RemoveAt( i-- );
                            t.Destory();

                            if ( lateChannel.Count != 0 && lateChannel.Contains( t.func ) )
                            {
                                lateChannel.Remove( t.func );
                            }
                        }

                        t.Reset();
                        --t.times;
                        t.func();
                    }
                }
            }
            yield return 0;
        }
    }

    //初始化
    protected static void Init()
    {
        if ( !inited )
        {
            inited = true;
            var inst = new GameObject( "TimerNode" );
            inst.AddComponent<Timer>();
        }
    }

    //获取闲置定时器
    protected static TimerTask GetFreeTimerTask()
    {
        if ( freeTaskCls.Count > 0 )
        {
            return freeTaskCls.Dequeue();
        }
        return new TimerTask();
    }

    #endregion

}





更新日志

最近更新日志 2023/05/26

04/09更新的清理类中闭包发现的新bug ClearTime会清理所有同类型类中的定时器

所以我将它拆分了两个方法:

ClearTimer()方法过于变态 请根据实际情况使用*

  1. 当此类型有限且运行时仅存一份时 你可以使用
  2. 当此类型是控制类 如Manager,Controller,Panel等 在同一时刻仅存一份 你可以随意使用
  3. 此方法的本质是获取类型名称 通过算法匹配所有相关类型的方法 包含类中新闭包生成的新类型也能会正确捕获到
  4. 建议看一下 底下这个案例中 两个闭包方法的 Target有何区别
  5. 如果你有好的想法请联系我 或者 CSDN私信我 感谢
	/// <summary>
    /// 通过类型来ClearTimer   此方法会移除所有和此类相关的包含所有labmda类型   
	/// 移除同类型的所有成员方法定时器  包含( lambda 和 其它类实体 )
    /// </summary>
    /// <param name="clsType"></param>
    public static void ClearTimer<T>()
    
    /// <summary>
    /// 清理Target相关的定时器 ( 包含 类成员方法的调度定时器,有限lambda支持)
    /// </summary>
    public static void ClearTimer( object target )

定时器通过调用者来清理所有定时器的支持列表:

class Mtest
    {
        string str_n = "123";


        public Mtest()
        {

            test(); //此方法内的闭包调用支持

            test2(); //此方法内的闭包调用不支持

            Timer.SetInterval( 1f,UpdatePerSecond ); //成员方法支持
        }

        //这个方法内的闭包支持
        private void test()
        {
            Timer.SetTimeout( 1.0f, () =>
            {
                //在lambda内部定义的变量
                string t = "12313213";

                //在lambda内部访问和修改类成员变量行为
                str_n = t;

            } );
        }

        //类成员支持
        private void UpdatePerSecond()
        {
            Debug.Log( Time.realtimeSinceStartup );
        }



        //这个方法内的闭包不支持
        private void test2()
        {
            //在lambda外定义的变量
            string t = "12313213";

            Timer.SetTimeout( 1.0f, () =>
            {
                //在lambda内部访问lambda外部的变量行为会让当前闭包的 Target变成一个新的类
                t = "1231";

                //在lambda内部访问和修改类成员变量行为
                str_n = t;

            } );
        }

        //清理这个类的所有定时器调度
        private void clearTime()
        {
            Timer.ClearTimer( this );
        }
    }

最近更新日志 2023/04/11

Timer的精度存在 1到2帧 的误差 于此 我修复了它 让它和 Unity内置的Invoke进行对比 确保了一致性
现在 它是完全的保持和 Invoke在一帧内的同步

最近更新日志 2023/04/09

ClearTime 在清理当前类 lambda表达式时 有些bug 一些闭包方法Target存在差异 所以我修复了它

/// <summary>
    /// 移除 类实例target的所有成员方法定时器
    /// </summary>
    /// <param name="func">处理方法</param>
    /// <returns></returns>
    public static void ClearTimer( object target )
    {
        //var allMatchTask = m_activeTaskCls.FindAll( t => t.func.Target == target );
        var clsName = target.GetType().FullName;
        var allMatchTask = m_activeTaskCls.FindAll( t =>
        {
            if ( null != t.func && null != t.func.Target )
            {
                var fullname = t.func.Target.GetType().FullName;
                var currentClsNameClip = fullname.Split( '+' );
                if ( currentClsNameClip.Length > 0 )
                {
                    if ( currentClsNameClip[ 0 ] == clsName )
                    {
                        return true;
                    }
                }
            }
            return false;
        } );

        allMatchTask?.ForEach( task =>
        {
            if ( lateChannel.Count != 0 && lateChannel.Contains( task.func ) )
            {
                lateChannel.Remove( task.func );
            }
            m_activeTaskCls.Remove( task );
            m_freeTaskCls.Enqueue( task );
        } );
    }

最近更新日志 2023/03/14

增加 可以通过调用对象来移除所有bind的方法
void ClearTimer( object target )

/// <summary>
    /// 移除 类实例target的所有成员方法定时器
    /// </summary>
    /// <param name="func">处理方法</param>
    /// <returns></returns>
    public static void ClearTimer( object target )
    {
        var allMatchTask = m_activeTaskCls.FindAll( t => t.func.Target.Equals( target ) );

        allMatchTask?.ForEach( task =>
        {
            if ( lateChannel.Count != 0 && lateChannel.Contains( task.func ) )
            {
                lateChannel.Remove( task.func );
            }
            m_activeTaskCls.Remove( task );
            m_freeTaskCls.Enqueue( task );
        } );
    }

新增后的好处是:
以前需要移除当前类的所有定时器

现在需要移除当前类的所有定时器

在这里插入图片描述
简单 快捷 高效 easy!

最近更新日志 2023/03/09

ClearTimer 方法更新内容:
移除的时候 匹配所有相同方法 全部移除

    public static void ClearTimer( Action func )
    {

        var allMatchTask = m_activeTaskCls.FindAll( t => t.func == func );

        allMatchTask?.ForEach( task =>
        {
            if ( lateChannel.Count != 0 && lateChannel.Contains( task.func ) )
            {
                lateChannel.Remove( task.func );
            }
            m_activeTaskCls.Remove( task );
            m_freeTaskCls.Enqueue( task );
        } );
    }
  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值