http://gad.qq.com/article/detail/695

使用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#》一书的第六章:“实现迭代器的捷径”

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中,我们可以观察一下实际上生成的代码:

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

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

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

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

C++运行时分析

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

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

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

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

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

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

需要说明的是,图中已经对于细节做了大量简化,只关注了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提供的两个版本:

public Coroutine
StartCoroutine(IEnumerator routine);

public Coroutine
StartCoroutine(string methodName);

虽然功能上一致,但在实现细节上其实有比较大的差异。从运行时效率来看,字符串的版本在运行时要用mono的反射做更多参数检查、函数查询工作(调用图中蓝色线条部分),带来性能损失。

实际上在老版本的Unity中,字符串版本的StartCoroutine最大的作用,是可以调用StopCoroutine来停止对应的Coroutine。而在4.5.0版本中,Unity终于加上了

public void StopCoroutine(IEnumerator routine);

这一接口,于是使用IEnumerator启动的Coroutine,也可以被手动终止了。

结合我们分析源码得出的结论,无疑应当尽量避免使用字符串版本StartCoroutine。

unity coroutine的更多相关文章

  1. Unity Coroutine详解(二)

    •        介绍•        Part 1. 同步等待•        Part 2. 异步协程•        Part 3. 同步协程•        Part 4. 并行协程 1.介绍 ...

  2. Unity Coroutine详解(一)

    Unity 中协程是个非常强大的功能,其作用主要是用于游戏中的延时调用或者执行一连串的有时间间隔的事件流程,例如剧情对话等.简单总结了几点协程相关的知识点,旨在加深记忆,同时为初学者解惑. 1.协程. ...

  3. Coroutine,你究竟干了什么?

    一 引子 使用Unity已经有一段时间了,对于Component.GameObject之类的概念也算是有所了解,而脚本方面从一开始就选定了C#,目前来看还是挺明智的:Boo太小众,而且支持有限:JS( ...

  4. Unity中巧用协程和游戏对象的生命周期处理游戏重启的问题

    主要用到协程(Coroutines)和游戏对象的生命周期(GameObject Lifecycle)基础知识,巧妙解决了游戏重启的问题. 关于协程,这里有篇文章我觉得写的非常好,理解起来也很容易.推荐 ...

  5. Unity 协程运行时的监控和优化

    我是快乐的搬运工: http://gulu-dev.com/post/perf_assist/2016-12-20-unity-coroutine-optimizing#toc_0 --------- ...

  6. 结合Thread Ninja明确与处理异步协程中的异常

    Thread Ninja说明: Thread Ninja - Multithread Coroutine Requires Unity 3.4.0 or higher. A simple script ...

  7. 【Unity3D基础教程】给初学者看的Unity教程(五):详解Unity3D中的协程(Coroutine)

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 为什么需要协程 在游戏中有许多过程(Proc ...

  8. 【转】关于Unity协同程序(Coroutine)的全面解析

    http://www.unity.5helpyou.com/2658.html 本篇文章我们学习下unity3d中协程Coroutine的的原理及使用 1.什么是协调程序 unity协程是一个能暂停执 ...

  9. Unity协程(Coroutine)管理类——TaskManager工具分享

    博客分类: Unity3D插件学习,工具分享 源码分析   Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...

随机推荐

  1. Android进程的生命周期

    Android系统想要永久的保留一个应用进程差点儿是不可能的.所以系统就须要不断的释放老的或者不太重要的进程以便腾出足够的内存空间来执行新的或者更重要的进程,那么系统怎样决定哪个进程应该保留哪个应该杀 ...

  2. Java for LeetCode 125 Valid Palindrome

    Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignori ...

  3. Flash+XML前后按钮超酷焦点图

    在线演示 本地下载

  4. SQLSERVER安装记录

    很多人都喜欢重装编程环境,VS,SQL是最常见的 尤其是SQL,在删除所有的SQL相关的组件之后(360),记得再次打开控制面板,查看是否有漏掉的,本人就有一个SQLXML没有删除掉 在删除之后,清理 ...

  5. office 2010 激活工具 迷你KMS使用说明

    “迷你KMS”——“”体态轻盈仅只1MB.但却一身承担两大职能:KMS服务器和客户激活端.“迷你KMS”可激活Microsoft Office 2010系列三大版本,以及Windows 7企业.专业版 ...

  6. javscript 一些常用的工具方法

    一些工作中经常会用到的js代码,可以封装成一个工具库. 积少成多,从现在开始吧! -------------- 1 . 判断一段文字的长度.要求中文相当于2个字符,非中文的相当于1个字符 String ...

  7. IP地址-计算机网络

    如需转载请联系:fengxw6@mail2.sysu.edu.cn 未经许可,禁止转载. ---Sun Yat-sen University 冯兴伟 1.  MAC地址和IP地址都是全局的(全球分配) ...

  8. PSPnet:Pyramid Scene Parsing Network——作者认为现有模型由于没有引入足够的上下文信息及不同感受野下的全局信息而存在分割出现错误的情景,于是,提出了使用global-scence-level的信息的pspnet

    from:https://blog.csdn.net/bea_tree/article/details/56678560 2017年02月23日 19:28:25 阅读数:6094 首先声明,文末彩蛋 ...

  9. 时间序列数据库——索引用ES、聚合分析时加载数据用什么?docvalues的列存储貌似更优优势一些

    加载 如何利用索引和主存储,是一种两难的选择. 选择不使用索引,只使用主存储:除非查询的字段就是主存储的排序字段,否则就需要顺序扫描整个主存储. 选择使用索引,然后用找到的row id去主存储加载数据 ...

  10. codeforces 609D D. Gadgets for dollars and pounds(二分+贪心)

    题目链接: D. Gadgets for dollars and pounds time limit per test 2 seconds memory limit per test 256 mega ...