Unity实用框架(二)协程管理框架

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将要求协程的发起者提供更多信息。

  1. public Coroutine StartCoroutine(Coroutine coroutine, bool startImmediately = true)
    

    最简单的接口,这种调用方式会使得将要被执行的协程被加入到WaitQueue中,在下一帧得到执行。如果需要立即开始执行,第二个参数应当被置为真。默认情况下,协程应当立即执行。

  2. public Coroutine StartCoroutine(UnityEngine.Object parentObject, Coroutine coroutine, bool startImmediately = true)
    

    此接口需要调用者指定协程的父对象。对于它的使用场景,比如,一个忍者对象释放了影分身,它的所有分身被一个Coroutine控制。当其本体死亡时,要求所有的Coroutine一同终结,这时,只需要在创建协程时将协程的父对象绑定到该本体上即可。

  3. public Coroutine StartCoroutine(UnityEngine.Object parentObject, IEnumerator context, bool startImmediately = true, int priority = 0)
    

    基本同2,但调用者不需要创建一个Coroutine对象,而只需要使用自己的IEumerator上下文即可,这使得调用者的写法更加灵活。但在这种情况下,调用者必须指定协程父对象。此priority同上面的isChildOf,需要自定义或扩展。

  4. public void StopCoroutine(Coroutine coroutine)
    

    终止一个协程。不管此协程处于running状态还是queue状态,直接将其从coroutine数组或waitQueue中移除。

  5. 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,未完待续

猜你喜欢

转载自blog.csdn.net/natrick/article/details/125822858
今日推荐