Unity实用框架(二)协程管理框架
线程是一项由操作系统控制的资源。使用线程,在多核处理器中,可以显著地提高处理器的利用率。但事实上,在大多数情况下,我们需要的仅仅是代码并发执行的能力,在这种情况下,无需任何系统操作,全程在用户态上执行的协程,自然能够大展身手。
同时,由于unity monobehaviour的调用使用了c#的反射机制,而反射操作本身是具有可观开销的,因此,使用Coroutine全面取代Mono Update也可以有效的减轻mono的反射开销,从而达到优化代码的目的。
C# unity自带的Coroutine提供了最基础的使用协程的接口,在此基础上,笔者将提供一种能够适用于更多场景、更容易由程序员全权控制的协程管理框架.
CoroutineManager
调用过程
首先,CoroutineManager将维护以下这些协程数据结构:
//协程池中的协程
private Coroutine[] coroutines = new Coroutine[InitialSize];
//当前协程数
private int tail = 0;
//等待被执行的协程
private List<Coroutine> waitQueue = new List<Coroutine>(InitialSize);
//等待被停止的协程
private List<Coroutine> stopWaitQueue = new List<Coroutine>();
//由于父对象而需要被停止的协程,这里容器中存放的是Object而非Coroutine,代表该父对象并不局限于协程间的父子关系。
private List<UnityEngine.Object> stopByParentWaitQueue = new List<UnityEngine.Object>();
在CoroutineManager的update中,首先,结束stopWaitQueue中的协程及其子协程:
lock (stopWaitQueue)
{
for (int i = 0; i < tail; i++)
{
for (int j = 0; j < stopWaitQueue.Count; j++)
{
//IsChildOf为Coroutine类的函数,用来判断某协程是否为另一个协程的子协程
//这里使用的Coroutine是自定义的而非unity自带的,为了方便,也可以通过this关键字去扩展unity的Coroutine
if (coroutines[i].IsChildOf(stopWaitQueue[j]))
{
coroutines[i].Stop();
coroutines[i] = null;
break;
}
}
}
stopWaitQueue.Clear();
}
结束stopByParentWaitQueue规定应当结束的协程。
lock (stopByParentWaitQueue)
{
for (int i = 0; i < tail; i++)
{
for (int j = 0; j < stopByParentWaitQueue.Count; j++)
{
if (coroutines[i] != null &&
coroutines[i].ParentObject == stopByParentWaitQueue[j])
{
coroutines[i].Stop();
coroutines[i] = null;
break;
}
}
}
stopByParentWaitQueue.Clear();
}
正常进行协程。
for (int i = 0; i < tail; i++)
{
if (coroutines[i] == null)
{
continue;
}
coroutines[i] = coroutines[i].Restart();
}
最后,从协程数组中移除已经被置为空,即已失效的协程。
int i, j = 1;
for (i = 0; i < tail; i++, j++)
{
if (coroutines[i] == null)
{
for (; j < tail; j++)
{
if (coroutines[j] != null)
{
coroutines[i] = coroutines[j];
coroutines[j] = null;
break;
}
}
if (j == tail)
{
break;
}
}
}
tail = i;
在lateUpdate中,waitQueue中的协程将被加载到coroutine数组中,以便于下一帧执行。根据在生成协程中赋予的priority,调整加入coroutine后的顺序使得高优先级协程被首先执行。
lock (waitQueue)
{
if (waitQueue.Count > 0)
{
for (int i = 0; i < waitQueue.Count; i++)
{
if (tail == coroutines.Length)
{
Array.Resize(ref coroutines, checked(tail * 2));
}
coroutines[tail++] = waitQueue[i];
for (int j = tail - 1; j >= 1; j--)
{
if (coroutines[j - 1] == null || coroutines[j - 1].Priority < coroutines[j].Priority)
{
var temp = coroutines[j];
coroutines[j] = coroutines[j - 1];
coroutines[j - 1] = temp;
}
}
}
waitQueue.Clear();
}
}
使用接口
为了与系统的Coroutine保持一致,这里给外部调用的接口也命名为StartCoroutine。但这里的StartCoroutine将要求协程的发起者提供更多信息。
-
public Coroutine StartCoroutine(Coroutine coroutine, bool startImmediately = true)
最简单的接口,这种调用方式会使得将要被执行的协程被加入到WaitQueue中,在下一帧得到执行。如果需要立即开始执行,第二个参数应当被置为真。默认情况下,协程应当立即执行。 -
public Coroutine StartCoroutine(UnityEngine.Object parentObject, Coroutine coroutine, bool startImmediately = true)
此接口需要调用者指定协程的父对象。对于它的使用场景,比如,一个忍者对象释放了影分身,它的所有分身被一个Coroutine控制。当其本体死亡时,要求所有的Coroutine一同终结,这时,只需要在创建协程时将协程的父对象绑定到该本体上即可。 -
public Coroutine StartCoroutine(UnityEngine.Object parentObject, IEnumerator context, bool startImmediately = true, int priority = 0)
基本同2,但调用者不需要创建一个Coroutine对象,而只需要使用自己的IEumerator上下文即可,这使得调用者的写法更加灵活。但在这种情况下,调用者必须指定协程父对象。此priority同上面的isChildOf,需要自定义或扩展。 -
public void StopCoroutine(Coroutine coroutine)
终止一个协程。不管此协程处于running状态还是queue状态,直接将其从coroutine数组或waitQueue中移除。 -
public void StopCoroutinesOf(UnityEngine.Object parentObject)
终止某个对象下的子协程。同4,但此时要根据stopByParentWaitQueue来判断协程是否为该对象的子对象。在第2条给出的例子中,就可以调用此接口完成任务。
更多地,为了更方便调试,在coroutineManager中封装日志管理的功能也是有必要的,可以通过注册Unity自带的UnityEngine.Application.logMessageReceive事件,加上自己的逻辑,来完成这一功能。
Coroutine(自定义)
协程继承自IEnumerator,显然是理所应当的。
首先是协程应当拥有的一些属性:
internal int Priority { get; set; }
protected Exception error;
在最简单的协程中,只需要实现MoveNext和Restart两个函数,就可以实现协程拥有的功能。
更新于2022.7.16,未完待续
|