博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Unity时钟定时器插件——Vision Timer源码分析之一
阅读量:4580 次
发布时间:2019-06-09

本文共 12542 字,大约阅读时间需要 41 分钟。

Unity时钟定时器插件——Vision Timer源码分析之一

 

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com

 

       因为项目中,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方法时创建:C#代码  收藏代码// setup main gameobject  if (m_MainObject == null)  {      m_MainObject = new GameObject("Timers");      m_MainObject.AddComponent
(); UnityEngine.Object.DontDestroyOnLoad(m_MainObject); #if (UNITY_EDITOR && !DEBUG) m_MainObject.gameObject.hideFlags = HideFlags.HideInHierarchy; #endif }

 

 

 

 

 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: C#代码  收藏代码    private class Event      {            public int Id;   //标记Event,如果Id = 0 ,表示该Event已经无效,就被Add进m_Pool中,Handle对象和Evnt就是通过Id来关联的            public Callback Function = null;   //函数委托 Callback和ArgCallback是vp.Timer定义的函数委托(原型)          public ArgCallback ArgFunction = null;          public object Arguments = null;            public int Iterations = 1;   //事件的迭代(执行次数)          public float Interval = -1.0f;   //执行时间间隔          public float DueTime = 0.0f;    //下一个事件执行的时间 DueTime = Time.time + Time.deltaTime          public float StartTime = 0.0f;   //事件开始执行事件 StartTime = Time.Time + delayTime          public float LifeTime = 0.0f;   //事件累积的总时间 LifeTime += Time.deltaTime          public bool Paused = false;   #if (DEBUG && UNITY_EDITOR)          private string m_CallingMethod = "";  #endif                 //省略其他代码         }

 

 

 

 

当然还有几个方法:

       Excute():执行Function和ArgFunction

       Recyle():

C#代码  收藏代码private void Recycle()  {        Id = 0;      DueTime = 0.0f;      StartTime = 0.0f;        Function = null;      ArgFunction = null;      Arguments = null;        if (vp_Timer.m_Active.Remove(this))  //从m_Active进入m_Pool          m_Pool.Add(this);   #if (UNITY_EDITOR && DEBUG)      EditorRefresh();  #endif    }

 

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

  1. C#代码  收藏代码public string MethodName  {      get      {          if (Function != null)          {              if (Function.Method != null)              {                  if (Function.Method.Name[0] == '<')                      return "delegate";                  else return Function.Method.Name;              }          }          else if (ArgFunction != null)          {              if (ArgFunction.Method != null)              {                  if (ArgFunction.Method.Name[0] == '<')                      return "delegate";                  else return ArgFunction.Method.Name;              }          }          return null;      }  }

     

     

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

C#代码  收藏代码public static void CancelAll(string methodName)  {      for (int t = vp_Timer.m_Active.Count - 1; t > -1; t--)      {          if (vp_Timer.m_Active[t].MethodName == methodName)              vp_Timer.m_Active[t].Id = 0;      }  }

 

Handle:

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

C#代码  收藏代码public int Id  {      get      {          return m_Id;      }      set      {          m_Id = value;            if (m_Id == 0)          {              m_Event.DueTime = 0.0f;              return;          }            m_Event = null;          for (int t = vp_Timer.m_Active.Count - 1; t > -1; t--)          {              if (vp_Timer.m_Active[t].Id == m_Id)              {                  m_Event = vp_Timer.m_Active[t];                  break;              }          }          if (m_Event == null)              UnityEngine.Debug.LogError("Error: (vp_Timer.Handle) Failed to assign event with Id '" + m_Id + "'.");            // store some initial event info          m_StartIterations = m_Event.Iterations;          m_FirstDueTime = m_Event.DueTime;        }  }

 

 

 还说vp_Timer

        前面介绍了vp_Timer的成员变量以及Event和Handle,就差vp_Timer的使用了,通过调用vp_Timer.In函数将事件加入vp_Timer的mActive队列:

C#代码  收藏代码// time + callback + [timer handle]      public static void In(float delay, Callback callback, Handle timerHandle = null)      { Schedule(delay, callback, null, null, timerHandle, 1, -1.0f); }        // time + callback + iterations + [timer handle]      public static void In(float delay, Callback callback, int iterations, Handle timerHandle = null)      { Schedule(delay, callback, null, null, timerHandle, iterations, -1.0f); }        // time + callback + iterations + interval + [timer handle]      public static void In(float delay, Callback callback, int iterations, float interval, Handle timerHandle = null)      { Schedule(delay, callback, null, null, timerHandle, iterations, interval); }        // time + callback + arguments + [timer handle]      public static void In(float delay, ArgCallback callback, object arguments, Handle timerHandle = null)      { Schedule(delay, null, callback, arguments, timerHandle, 1, -1.0f); }        // time + callback + arguments + iterations + [timer handle]      public static void In(float delay, ArgCallback callback, object arguments, int iterations, Handle timerHandle = null)      { Schedule(delay, null, callback, arguments, timerHandle, iterations, -1.0f); }        // time + callback + arguments + iterations + interval + [timer handle]      public static void In(float delay, ArgCallback callback, object arguments, int iterations, float interval, Handle timerHandle = null)      { Schedule(delay, null, callback, arguments, timerHandle, iterations, interval); }

 

 看到都是对Schedule的封装:

C#代码  收藏代码private static void Schedule(float time, Callback func, ArgCallback argFunc, object args, Handle timerHandle, int iterations, float interval)  {  if (func == null && argFunc == null)  {      UnityEngine.Debug.LogError("Error: (vp_Timer) Aborted event because function is null.");      return;  }  // setup main gameobject  if (m_MainObject == null)  //new 一个 m_MainObject,挂载vp_Timer_  {      m_MainObject = new GameObject("Timers");      m_MainObject.AddComponent
(); UnityEngine.Object.DontDestroyOnLoad(m_MainObject); #if (UNITY_EDITOR && !DEBUG) m_MainObject.gameObject.hideFlags = HideFlags.HideInHierarchy; #endif } // force healthy time values time = Mathf.Max(0.0f, time); iterations = Mathf.Max(0, iterations); interval = (interval == -1.0f) ? time : Mathf.Max(0.0f, interval); // recycle an event - or create a new one if the pool is empty:先从m_Pool中去Event,如果m_Pool为空则直接new一个 m_NewEvent = null; if (m_Pool.Count > 0) { m_NewEvent = m_Pool[0]; m_Pool.Remove(m_NewEvent); } else m_NewEvent = new Event(); // iterate the event counter and store the id for this event vp_Timer.m_EventCount++; m_NewEvent.Id = vp_Timer.m_EventCount; //Event的Id为当前队列的总数 // set up the event with its function, arguments and times if (func != null) m_NewEvent.Function = func; else if (argFunc != null) { m_NewEvent.ArgFunction = argFunc; m_NewEvent.Arguments = args; } m_NewEvent.StartTime = Time.time; //设置Event的成员变量 m_NewEvent.DueTime = Time.time + time; m_NewEvent.Iterations = iterations; m_NewEvent.Interval = interval; m_NewEvent.LifeTime = 0.0f; m_NewEvent.Paused = false; // add event to the Active list vp_Timer.m_Active.Add(m_NewEvent); // if a timer handle was provided, associate it to this event, // but first cancel any already active event using the same // handle: there can be only one ... if (timerHandle != null) { if (timerHandle.Active) timerHandle.Cancel(); // setting the 'Id' property associates this handle with // the currently active event with the corresponding id timerHandle.Id = m_NewEvent.Id; //关联Handle和Event,然后Handle就可以通过关联的Event查询其状态和操作 }

 

 Update:通过比对Time.time和Event.DueTime,如果Time.time >= Event.DueTime 则执行Event的Excute方法。

C#代码  收藏代码private void Update()  {        //  NOTE: this method never processes more than 'MaxEventsPerFrame',      // in order to avoid performance problems with excessive amounts of      // timers. this may lead to events being delayed a few frames.      // if experiencing delayed events 1) try to cut back on the amount      // of timers created simultaneously, and 2) increase 'MaxEventsPerFrame'        // execute any active events that are due, but only check      // up to max events per frame for performance      m_EventBatch = 0;      while ((vp_Timer.m_Active.Count > 0) && m_EventBatch < MaxEventsPerFrame)      {            // if we reached beginning of list, halt until next frame          if (m_EventIterator < 0)          {              // this has two purposes: 1) preventing multiple iterations              // per frame if our event count is below the maximum, and              // 2) preventing reaching index -1              m_EventIterator = vp_Timer.m_Active.Count - 1;              break;          }            // prevent index out of bounds          if (m_EventIterator > vp_Timer.m_Active.Count - 1)              m_EventIterator = vp_Timer.m_Active.Count - 1;            // execute all due events          if (Time.time >= vp_Timer.m_Active[m_EventIterator].DueTime ||   // time is up              vp_Timer.m_Active[m_EventIterator].Id == 0)                 // event has been canceled ('Execute' will kill it)              vp_Timer.m_Active[m_EventIterator].Execute();          else          {              // handle pausing              if (vp_Timer.m_Active[m_EventIterator].Paused)                  vp_Timer.m_Active[m_EventIterator].DueTime += Time.deltaTime;              else                  // log lifetime                  vp_Timer.m_Active[m_EventIterator].LifeTime += Time.deltaTime;          }            // going backwards since 'Execute' will remove items from the list          m_EventIterator--;          m_EventBatch++;      }    }

 

 

        差不多就这样了,很久没写博客了,感觉一点也不顺畅,虽然理解的很透彻,还是很为自己的写作功底捉急。

        

小结:

       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对象会更直观,只有在回调的时候用参数返回关联的对象,要不然采用直接返回会更明白。

 

       虽然上面的分析文章写得比较零乱,但是小结部分我还是很满意的,至少D.S.Qiu之前从来没有在这部分写那么多,算是分享自己的一些经验和体会吧,也发现自己对delegate的不足,又到1:30了时间真是不够用,4个小时就这么过去了。

 

 

       如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

       转载请在文首注明出处:

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)

 

 

 

 

 

 

                                                                             

  •  (486.4 KB)
  • 下载次数: 4

转载于:https://www.cnblogs.com/123ing/p/3704971.html

你可能感兴趣的文章
【转载】法线贴图Nomal mapping 原理
查看>>
prado 初步分析
查看>>
php 做守护进程1
查看>>
JS中实现JSON对象和JSON字符串之间的相互转换
查看>>
简单员工管理实例
查看>>
textwrap 模块
查看>>
SAP 到出XLS
查看>>
HSV
查看>>
JAVA程序中SQL语句无法传递中文参数
查看>>
Android学习_数据库查询使用rawQuery遇到的问题
查看>>
|待研究|委托付款的支付状态触发器
查看>>
redis集群中的主从复制架构(3主3从)
查看>>
初始Linux(其实之前接触过(*^__^*) 嘻嘻……)
查看>>
一些多项式的整理
查看>>
NIO selector
查看>>
MySQL中DATETIME、DATE和TIMESTAMP类型的区别
查看>>
asp代码获取年数,季度数.星期数,天数,小时数,分钟数,秒数等时
查看>>
python之建完model之后操作admin
查看>>
Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
查看>>
shell 脚本后台运行知识
查看>>