unity coroutine
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的更多相关文章
- Unity Coroutine详解(二)
• 介绍• Part 1. 同步等待• Part 2. 异步协程• Part 3. 同步协程• Part 4. 并行协程 1.介绍 ...
- Unity Coroutine详解(一)
Unity 中协程是个非常强大的功能,其作用主要是用于游戏中的延时调用或者执行一连串的有时间间隔的事件流程,例如剧情对话等.简单总结了几点协程相关的知识点,旨在加深记忆,同时为初学者解惑. 1.协程. ...
- Coroutine,你究竟干了什么?
一 引子 使用Unity已经有一段时间了,对于Component.GameObject之类的概念也算是有所了解,而脚本方面从一开始就选定了C#,目前来看还是挺明智的:Boo太小众,而且支持有限:JS( ...
- Unity中巧用协程和游戏对象的生命周期处理游戏重启的问题
主要用到协程(Coroutines)和游戏对象的生命周期(GameObject Lifecycle)基础知识,巧妙解决了游戏重启的问题. 关于协程,这里有篇文章我觉得写的非常好,理解起来也很容易.推荐 ...
- Unity 协程运行时的监控和优化
我是快乐的搬运工: http://gulu-dev.com/post/perf_assist/2016-12-20-unity-coroutine-optimizing#toc_0 --------- ...
- 结合Thread Ninja明确与处理异步协程中的异常
Thread Ninja说明: Thread Ninja - Multithread Coroutine Requires Unity 3.4.0 or higher. A simple script ...
- 【Unity3D基础教程】给初学者看的Unity教程(五):详解Unity3D中的协程(Coroutine)
作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 为什么需要协程 在游戏中有许多过程(Proc ...
- 【转】关于Unity协同程序(Coroutine)的全面解析
http://www.unity.5helpyou.com/2658.html 本篇文章我们学习下unity3d中协程Coroutine的的原理及使用 1.什么是协调程序 unity协程是一个能暂停执 ...
- Unity协程(Coroutine)管理类——TaskManager工具分享
博客分类: Unity3D插件学习,工具分享 源码分析 Unity协程(Coroutine)管理类——TaskManager工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处 ...
随机推荐
- Hadoop-2.2.0中文文档—— MapReduce 下一代--容量调度器
目的 这份文档描写叙述 CapacityScheduler,一个为Hadoop能同意多用户安全地共享一个大集群的插件式调度器,如他们的应用能适时被分配限制的容量. 概述 CapacitySchedul ...
- 【zabbix】Windows服务器获取IIS站点以及程序池状态
在使用zabbix做Windows服务器监控的时候遇到一个比较棘手的问题,检测IIS站点状态. 普通情况下,只要用浏览器访问iis站点测试一下返回码是不是200即可判断状态,但是我这次遇到的是iis使 ...
- ZOJ - 3862 Intersection 【贪心】
题目链接 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3862 思路 因为交换次数达到 n + 10 其实我们可以先将他们 ...
- Linux桥接网络配置
在虚拟机网络配置中,选择桥接的方式.然后进入linux进行设置. 编辑 vim /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 BOOTPR ...
- 用vector代替实现二维数组
vector可以用来模拟数组,当然也可以用来模拟二维数组: 定义如:vector<int>a[100]; 相当于定义了一个100行的数组,当每行的大小是不确定的 模板应用如下: #in ...
- win10系统使用clover时程序崩溃的解决
1. 工具 ---> Internet选项 2. 程序选项卡 ---> 管理加载项 3.选择ExporerWatcher Class ---> 启用 win10对于未验证的程序状态 ...
- Kafka0.7运行时报错 kafka/javaapi/consumer/ConsumerConnector : Unsupported major.minor version 51.0 解决
目前中央库中 org.apache.kafka 是用jdk1.7编译的, 故跑在1.6的jvm中会报错 解决方案: 1. 下载kafka源码, 本地sbt进行install, 编译前 java -ve ...
- laravel基础课程---6、请求(如何获取当前 HTTP 请求的实例)
laravel基础课程---6.请求(如何获取当前 HTTP 请求的实例) 一.总结 一句话总结: 依赖注入:通过依赖注入的方式来获取当前 HTTP 请求的实例:public function sto ...
- 分享知识-快乐自己:Mybatis缓存机制
论缓存机制: 1):mybatis 提供了缓存机制减轻数据库压力,提高数据库性能. 2):mybatis 的缓存分为两级:一级缓存.二级缓存 3):一级缓存是SqlSession级别的缓存,缓存的数据 ...
- Hotel California
On a dark desert highway行驶在昏黑的荒漠公路上cool wind in my hair凉风吹过我的头发warm smell of colutas温馨的大麻香rising up ...