因为项目中,UI的所有模块都没有MonBehaviour类(纯粹的C#类),只有像NGUI的基本组件的类是继承MonoBehaviour。因为没有继承MonoBehaviour,这也不能使用Update,InVoke,StartCoroutine等方法,这样就会显得很蹩脚。后来一个同事添加vp_Timer和vp_TimeUtility这两个类。后来研究了下vp_Timer至少可以弥补没有Update,InVoke,InVokeRepeat的不足。

之前用的时候,就粗略的研究过vp_Timer这个类,一直就想仔细剖析下,但是由于不仅工作任务中,自己也有很多要琐碎的事情,我都很久自己好好学习了,虽然还有一堆事情要做,但是憋了太久了,先满足下自己,所以才有开始认真分析vp_Timer的代码并才有这篇博客。

vp_Timer 一共有3个class,都各司其职:vp_Timer,Event,Handle

1)vp_Timer:提供的使用接口,通过静态方法vp_Timer.In(),加入定时器事件(函数,这里将传入的函数称为事件)

2)Event:用来封装传入的事件(函数),保持事件的状态

3)Handle:对事件状态提供查询接口(事件执行了多长时间,结束时间,是否还是Active)以及提供 Excute(立即执行事件),Cancel(取消事件),Pause(暂停事件)等操作

很容易就可以理清这三者的关系,通过vp_Timer.In方法将传入的事件(函数)封装为Event对象,然后返回(虽然是通过参数)Handle对象让调用者可以查询事件的状态和进行相关操作。

vp_Timer:

先看下vp_Timer的成员变量(c#应该称为filed)

GameObject m_MainObject:vp_Timer是一个MonoBehaviour,就一定要挂载GameObject上,m_MainObject会在第一次调用vp_Timer.In方法时创建:

  1. // setup main gameobject
  2. if (m_MainObject == null)
  3. {
  4. m_MainObject = new GameObject("Timers");
  5. m_MainObject.AddComponent<vp_Timer>();
  6. UnityEngine.Object.DontDestroyOnLoad(m_MainObject);
  7.  
  8. #if (UNITY_EDITOR && !DEBUG)
  9. m_MainObject.gameObject.hideFlags = HideFlags.HideInHierarchy;
  10. #endif
  11. }

  

List<Event> m_Active 和 List<Event> m_Pool :这个List都是Event的缓存,其中,m_Active缓存Active的Event,m_Pool缓存无效的Event,这里的Acitive是事件仍然需要执行,无效说明不会再被调用。之所有要缓存无效的Event,是为了节省创建Event对象的消耗。m_Pool就好比垃圾箱,m_Active是一个成品工厂,每次m_Active要生产(Add)新的Event,都去m_Pool取没用的原料(Event),当m_Active的成品没用了,用放会m_Pool中去,这样就达到了循环利用作用。

Event m_NewEvent :在Schedule方法里使用的变量,其实完全可以声明为Schedule的局部变量,为了节省重复创建和销毁的消耗,vp_Timer就声明一个成员变量。

private static int m_EventCount = 0;

// variables for the Update method

int m_EventBatch 和int m_EventIterator:在Update使用的变量,m_EventBatch记录在一次Update中执行事件的次数,m_EventIterator记录是每次执行事件在m_Active的索引。

int MaxEventsPerFrame :一次循环(Update)执行事件最大次数

假设MaxEventPerFarme = 10 , m_Active.Count = 5,那么每次Update都会遍历2次m_Active的Event,看是否可以执行(调用Excute函数)。这样就可以理解这三个参数的具体含义了。

Event:

  1. private class Event
  2. {
  3.  
  4. public int Id; //标记Event,如果Id = 0 ,表示该Event已经无效,就被Add进m_Pool中,Handle对象和Evnt就是通过Id来关联的
  5.  
  6. public Callback Function = null; //函数委托 Callback和ArgCallback是vp.Timer定义的函数委托(原型)
  7. public ArgCallback ArgFunction = null;
  8. public object Arguments = null;
  9.  
  10. public int Iterations = 1; //事件的迭代(执行次数)
  11. public float Interval = -1.0f; //执行时间间隔
  12. public float DueTime = 0.0f; //下一个事件执行的时间 DueTime = Time.time + Time.deltaTime
  13. public float StartTime = 0.0f; //事件开始执行事件 StartTime = Time.Time + delayTime
  14. public float LifeTime = 0.0f; //事件累积的总时间 LifeTime += Time.deltaTime
  15. public bool Paused = false;
  16.  
  17. #if (DEBUG && UNITY_EDITOR)
  18. private string m_CallingMethod = "";
  19. #endif
  20. //省略其他代码
  21. }

  

当然还有几个方法:

Excute():执行Function和ArgFunction

Recyle():

  1. private void Recycle()
  2. {
  3.  
  4. Id = ;
  5. DueTime = 0.0f;
  6. StartTime = 0.0f;
  7.  
  8. Function = null;
  9. ArgFunction = null;
  10. Arguments = null;
  11.  
  12. if (vp_Timer.m_Active.Remove(this)) //从m_Active进入m_Pool
  13. m_Pool.Add(this);
  14.  
  15. #if (UNITY_EDITOR && DEBUG)
  16. EditorRefresh();
  17. #endif
  18.  
  19. }

MethodName:由于D.S.Qiu对delegate还没有深入研究理解,目前还说不清如何比较两个委托是否相等,但是得到一个经验就是不能用 函数 来比较,所以看到很多插件(最典型的就是Unity的StopCoroutine只有字符串作为参数和NGUI的EventDelegate)都使用的字符串来标记delegate,看下面的代码:

  1. public string MethodName
  2. {
  3. get
  4. {
  5. if (Function != null)
  6. {
  7. if (Function.Method != null)
  8. {
  9. if (Function.Method.Name[] == '<')
  10. return "delegate";
  11. else return Function.Method.Name;
  12. }
  13. }
  14. else if (ArgFunction != null)
  15. {
  16. if (ArgFunction.Method != null)
  17. {
  18. if (ArgFunction.Method.Name[] == '<')
  19. return "delegate";
  20. else return ArgFunction.Method.Name;
  21. }
  22. }
  23. return null;
  24. }
  25. }

这样vp_Timer才有Cancel(string methodName)的方法:

  1. public static void CancelAll(string methodName)
  2. {
  3. for (int t = vp_Timer.m_Active.Count - ; t > -; t--)
  4. {
  5. if (vp_Timer.m_Active[t].MethodName == methodName)
  6. vp_Timer.m_Active[t].Id = ;
  7. }
  8. }

Handle:

前面介绍过,Handle是用来查询和操作Event的对象,Handle对象和Event桶Id关联起来。

  1. public int Id
  2. {
  3. get
  4. {
  5. return m_Id;
  6. }
  7. set
  8. {
  9. m_Id = value;
  10.  
  11. if (m_Id == 0)
  12. {
  13. m_Event.DueTime = 0.0f;
  14. return;
  15. }
  16.  
  17. m_Event = null;
  18. for (int t = vp_Timer.m_Active.Count - 1; t > -1; t--)
  19. {
  20. if (vp_Timer.m_Active[t].Id == m_Id)
  21. {
  22. m_Event = vp_Timer.m_Active[t];
  23. break;
  24. }
  25. }
  26. if (m_Event == null)
  27. UnityEngine.Debug.LogError("Error: (vp_Timer.Handle) Failed to assign event with Id '" + m_Id + "'.");
  28.  
  29. // store some initial event info
  30. m_StartIterations = m_Event.Iterations;
  31. m_FirstDueTime = m_Event.DueTime;
  32.  
  33. }
  34. }

  

小结:

D.S.Qiu觉得在项目中很有必要有“管理”的思想,很多功能都是用一个类实现的,其他人只要调用就可以了,具体的逻辑只需要在一个类内部维护,可以做的统一控制,可以做到更自如,就拿vp_Timer和MonoBehaviour的InVokeRepeat方法来对比就有明显的优势:

1)vp_Timer可以随时查询事件的状态(事件被执行了次数等)还可以暂停事件,而InVokeRepeat做不到的

2)vp_Timer可以设置时间delatTime受不受Time.timeScale影响,而InVokeRepeat是没有这个参数设置的

3)vp_Timer可以对事件进行统一的管理,如果暂停所有事件的执行,这个点当Time.timeScale = 0 时特别管用,而InVokeRepeat是分散的,没有统一管理其他。

这有点“一夫当万夫莫开”的感觉。

不管是InVokeRepeat方法,MonoBehaviour的很多方法都有类似的缺陷,因为每一个MonoBehaviour都可以调用这些方法,就不能统一起来管理了,所以如果Unity当初能写一个专门的类我想会方便很多。

虽然觉得vp_Timer用的很爽,但是D.S.Qiu还是觉得有很多可以改进的地方:

1)vp_Timer提供Pause(string methodName)和PauseAll()的方法,从“管理”的角度上就更加完美了,当然还有对应的Play方法。

2)当Event的参数: Iterations 和  Interval 没有很好处理 Interval 和 Time.deltaTime 的具体情况,假设我们的 Iterations =100 , interval = 0.01f  即我需要达到1s内执行100次的目的,但按照vp_Timer的实现结果是执行了100次,但是时间一定是>= 1s,即当Time.deltaTime > interval 时,还是只执行一次,例如 Time.deltaTime = 0.02f, 理论上我们希望能执行两次,但是却只执行了一次。

3)vp_Timer要是提供 string methodName 到 Event 或 Handle 的查询接口就更加完美了。

4)vp_Timer虽然用了很多设计,对象的重复利用避免 new 和销毁对象的系统开销,但是专门用Handle专门管理Event,Handle的的功能只是对Event的一个封装,其实完全没有必要,完全可以让Event自己充当Handle的角色,直接返回Event对象会更直观,只有在回调的时候用参数返回关联的对象,要不然采用直接返回会更明白。

Vision Timer下载地址:

https://item.taobao.com/item.htm?spm=0.7095261.0.0.70991debBfRIWJ&id=565519569907

Unity时钟定时器插件——Vision Timer源码分析之一的更多相关文章

  1. Unity时钟定时器插件——Vision Timer源码分析之二

      Unity时钟定时器插件——Vision Timer源码分析之二 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面的已经介绍了vp_T ...

  2. Unity时钟定时器插件

    Unity时钟定时器插件 http://dsqiu.iteye.com/blog/2020603https://github.com/joserocha3/KillerCircles/blob/67a ...

  3. Android Small插件化框架源码分析

    Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...

  4. Mybatis 插件使用及源码分析

    Mybatis 插件 Mybatis插件主要是通过JDK动态代理实现的,插件可以针对接口中的方法进行代理增强,在Mybatis中比较重要的接口如下: Executor :sql执行器,包含多个实现类, ...

  5. Java并发编程笔记之Timer源码分析

    timer在JDK里面,是很早的一个API了.具有延时的,并具有周期性的任务,在newScheduledThreadPool出来之前我们一般会用Timer和TimerTask来做,但是Timer存在一 ...

  6. 并发编程 —— Timer 源码分析

    前言 在平时的开发中,肯定需要使用定时任务,而 Java 1.3 版本提供了一个 java.util.Timer 定时任务类.今天一起来看看这个类. 1.API 介绍 Timer 相关的有 3 个类: ...

  7. schedule与scheduleAtFixedRate之Timer源码分析

    执行Timer任务调度方法有如下几种: 这些方法最后调用的都是这个方法: private void sched(TimerTask task, long time, long period)   这个 ...

  8. Jenkins插件hyper slaves源码分析

    1.public class HyperSlaves extends Plugin implements Describable<HyperSlaves> (1).init():初始化co ...

  9. storm定时器timer源码分析-timer.clj

    storm定时器与java.util.Timer定时器比较相似.java.util.Timer定时器实际上是个线程,定时调度所拥有的TimerTasks:storm定时器也有一个线程负责调度所拥有的& ...

随机推荐

  1. android stuido的代码排版的快捷建CTRL+ALT+L

    CTRL+ALT+L 需要主要留意的地方是QQ的与其冲突的 我将qq的中快捷方式给去除

  2. USB之HID类Set_Report Request[调试手记1]

    请翻开<Device Class Definition for Human Interface Devices (HID) Version 1.11 >7.2.2 Set_Report R ...

  3. 解决git中文乱码问题

    三条命令fix乱码问题: git config --global gui.encoding utf-8 git config --global i18n.commitencoding utf-8 gi ...

  4. day01-Python输出

    输出 用print加上字符串,就可以向屏幕上输出指定的文字.比如输出'hello, world'>>>print 'hello, world' print语句也可以跟上多个字符串,用 ...

  5. django--用户认证组件

    用户认证组件 用户认证组件: 功能:用session记录登录验证状态 前提:用户表:django自带的auth_user 创建超级用户: python3 manage.py createsuperus ...

  6. leetcode 题解 word search。递归可以更简洁。

    先写上我的代码: 我总是不知道何时把任务交给下一个递归.以致于,写出的代码很臃肿! 放上别人递归的简洁代码: bool exist(char** board, int m, int n, char* ...

  7. docker-compose初试及命令基础

    转自:https://www.cnblogs.com/jsonhc/p/7811929.html 以一个简单的lnmp.yaml的配置文件进行讲解docker-compose命令的基础讲解,熟练掌握命 ...

  8. css给文字加下划线

    语法:linear-gradient(direction, color-stop 1, color-stop 2,……) 简单用法:background-image: linear-gradient( ...

  9. Haskell语言学习笔记(76)Data.Tree

    Data.Tree data Tree a = Node { rootLabel :: a, subForest :: Forest a } deriving (Eq, Read, Show) typ ...

  10. Spring+Quartz 实现定时任务的配置方法

    Spring+Quartz 实现定时任务的配置方法 整体介绍 一.Quartz介绍 在企业应用中,我们经常会碰到时间任务调度的需求,比如每天凌晨生成前天报表,每小时生成一次汇总数据等等.Quartz是 ...