您当前的位置:首页 VR开发 Unity 正文

Unity Coroutine技术介绍

查看: 1998| 评论: 0 2016-12-25 10:32 AM| 发布者: administrator |原作者: frankjfwang

Coroutine是unity非常重要的多线程机制,使用unity的朋友一定需要非常熟练使用,52VR看到一篇来自gad的文章,推荐给大家!

使用Unity 3D引擎的同学,对于Coroutine(协程)的使用肯定也是非常熟悉的了。然而Coroutine背后的技术以及具体的实现方式、运行流程如何,恐怕并不是那么容易说得清楚。

本文尝试通过分析Unity 3.5.7版本的源代码,来厘清这一关键技术细节。(由于Unity 3.5.7的源代码,能拿到的版本无法编译、运行,只能直接通过源代码阅读的方式来进行静态的梳理)。

C#层面的Coroutine:背后的技术

IEnumerator接口与yield语句

IEnumerator本身是一个forward iterator,定义如下:

public interface IEnumeratorn
{
        object Current { get }
        bool MoveNext();
        void Reset();
}

 最常见的使用方式,是在foreach中使用,foreach又可展开为(简化版本):

    while (i.MoveNext())    // i implements IEnumerator

    {

       object o = i.Current;

       // do stuff with o...

    }

.Net为了简化IEnumerator的实现,引入了yield语句:

http://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx

简单来说,yield return用于在每次迭代时返回一个元素,这个元素可以通过IEnumerator.Current属性访问;同时,yield语句会保留迭代器的当前状态,当下次迭代时,保证状态连续性。

关于IEnumerator/yield的细节,推荐参考《深入理解C#》一书的第六章:“实现迭代器的捷径”

102935hc99mk842zaxwwwx

http://book.douban.com/subject/7055340/

 Unity中的Coroutine

以上都是理论基础。由于yield语句实际上用到了比较高级的编译器技术,只是了解理论总是有些隔靴搔痒的感觉。那么,让我们来看一下Unity中是如何“实践”的。

这里以一个简单的Coroutine函数为例:

       IEnumerator Fade()

        {

            for (float f = 1f; f >= 0; f -= 0.1f)

            {

                Color c = renderer.material.color;

                c.a = f;

                renderer.material.color = c;

                yield return new WaitForSeconds(0.1f);

            }

        }

 在Reflector中,我们可以观察一下实际上生成的代码:

102936p3u2dujpjj5ju33z

注意这个c__Iterator15 ,就是编译器帮我们生成的辅助类,实现了IEnumerator接口:

102936jytyjwlt9uyyy5xh

我们还可以进一步展开MoveNext()方法来查看:

102936w80w1cq0x128lphh

于是可以发现,我们写在Fade()中的代码,都出现在了MoveNext()中,而编译器将其展开放在了一个有限状态机中;而yield return语句,实际上直接赋值给了current:

102936kji8eiiei5oir6cl

至此,Coroutine在C#层面所涉及的技术,我们已经有了一个大体的了解。

C++运行时分析

我们知道,Unity底层引擎是采用C++编写,然后通过一个中间层将所有功能封装在UnityEngine.dll中(UnityEditor.dll对应编辑器功能),供上层C#(以及Unitynoxss、Boo)调用。

 我们可以在Reflector中查看这个dll,如下图:

102936iv5zrhrhz37dfybb

在Unity的源码中,引擎的运行时代码全部位于根目录的“Runtime”目录下。

这部分封装层,在Unity源代码中对应的是Runtime¥Export目录下的内容。Unity所使用的应该是自家的Wrapper技术,具体未深究。好在查看其中的原始文件依然能让我们探究竟。

比如Coroutine相关入口,所对应的Wrapper文件就是Runtime¥Export¥UnityEngineMonobBehaviour.txt文件。以此为入口,我们可以开始步步分析Coroutine的相关实现了。

在浏览了相关代码时,我整理出了一个大致的调用关系图,如下(svg版本见附件):

102937et4utuz7dzndttq4

需要说明的是,图中已经对于细节做了大量简化,只关注了StartCoroutine的主要流程,而忽略了错误处理、Coroutine销毁、参数对象生命周期管理等细节。

以下为详细的流程分析:

1, C#层调用StartCoroutine方法,将IEnumerator对象(或者是用于创建IEnumerator对象的方法名字符串)传入C++层,创建出一个对应的Coroutine对象,之后调用这个Coroutine对象的Run()方法;

2, Coroutine.Run()中,首先通过mono的反射功能,找到IEnumerator上的MoveNext()、get_Current()两个方法,然后调用一次MoveNext();如果MoveNext()返回false,表示Coroutine执行结束,进入清理流程(上图中忽略);如果返回true,表示Coroutine执行到了一句yield return处;这时就需要调用get_Current()取出yield return返回的对象(monoWait),再根据monoWait的具体类型(null、WaitForSeconds、WaitForFixedUpdate等),将Coroutine对象保存到DelayedCallManager的callback列表中;至此,Coroutine在当前帧的执行即结束;

3, 之后游戏运行过程中,游戏主循环的PlayerLoop()方法会在每帧的不同时间点以不同的modeMask调用DelayedCallManager.Update方法(注:PlayerLoop中的具体过程以及调用Coroutine时间点,可以参考:http://docs.unity3d.com/uploads/Main/monobehaviour_flowchart.svg);Update方法中会遍历callback列表中的Coroutine对象;如果某个Coroutine对象的monoWait的执行条件满足,则将其从callback列表中取出,执行这个Coroutine对象的Run()方法,回到2的执行流程中。

至此,Coroutine的整体流程已经分析完毕,没有什么疑惑之处了。

 

后记

在掌握了Coroutine机制的实现细节之后,对于一些更深层次的问题也自然会有更进一步的认识。

比如StartCoroutine提供的两个版本:

Coroutine,unity

52VR.COM微信扫一扫
52vr公众号
专注于VR的学习、开发和人才交流

52VR开发交流

相关推荐

已有 0 人参与

发表评论

您需要登录才可以回帖 登录 | 立即注册

手机版|VR开发网 ( 津ICP备18009691号 ) 统计 网安备12019202000257

GMT+8, 2020-9-20 09:55 AM