1,节选翻译一篇stackoverflow关于Unity协程实现的讨论


The big clues are in the C# version. Firstly, note that the return type for the function is IEnumerator. And secondly, note that one of the statements is yield return. This means that yield must be a keyword, and as Unity’s C# support is vanilla C# 3.5, it must be a vanilla C# 3.5 keyword. Indeed, here it is in MSDN – talking about something called ‘iterator blocks.’ So what’s going on?

…最大的线索是在上面的C#版本代码中。首先,注意方法的返回类型是IEnumerator。第二,注意有一个语句是 yield return。这就意味着yield是一个关键字,并且Unity采用的C#3.5,它一定是一个vanilla C#3.5关键字。的确,它在MSDN这里–这里讲的是一些关于 ‘iterator blocks’ 迭代器块的内容。那么这是怎么回事呢?

Firstly, there’s this IEnumerator type. The IEnumerator type acts like a cursor over a sequence, providing two significant members: Current, which is a property giving you the element the cursor is presently over, and MoveNext(), a function that moves to the next element in the sequence. Because IEnumerator is an interface, it doesn’t specify exactly how these members are implemented; MoveNext() could just add one toCurrent, or it could load the new value from a file, or it could download an image from the Internet and hash it and store the new hash in Current… or it could even do one thing for the first element in the sequence, and something entirely different for the second. You could even use it to generate an infinite sequence if you so desired. MoveNext() calculates the next value in the sequence (returning false if there are no more values), and Current retrieves the value it calculated.

首先,先了解一下IEnumerator type,枚举器类。IEnumerator就好像一个在代码行序列上的鼠标箭头,它有两个重要的成员:Current,鼠标箭头当前所在位置的元素,和MoveNext(),一个可以使箭头移到此序列下一个元素的方法。由于IEnumerator是一个接口,它没有具体写出这些成员是如何运作的。MoveNext()可能是给Current加一,可能是从一个文件中读取一个新的值,或则它可能是从网络上下载一张图片并给它赋予哈希值并把哈希值存到Current处,或者它可以给序列中的第一个元素做一些工作,并给第二个元素做完全不同的事情。你甚至可以用它生产一个无限的序列。MoveNext()计算序列中的下一个值(返回false如果没有下一个值),而Current取回这个值。

Ordinarily, if you wanted to implement an interface, you’d have to write a class, implement the members, and so on. Iterator blocks are a convenient way of implementing IEnumerator without all that hassle – you just follow a few rules, and the IEnumerator implementation is generated automatically by the compiler.

一般情况下,如果你想使用一个接口,你需要写一个类,成员,以及其他。‘Iterator blocks’ 迭代器块是一个可以方便的操作IEnumerator的方法,他不需要上面那些繁琐的操作,只需要服从一些简单的规则,编译器会自动生成IEnumerator。

An iterator block is a regular function that (a) returns IEnumerator, and (b) uses the yield keyword. So what does the yield keyword actually do? It declares what the next value in the sequence is – or that there are no more values. The point at which the code encounters a yield return X or yield break is the point at which IEnumerator.MoveNext() should stop; a yield return X causes MoveNext() to return true andCurrent to be assigned the value X, while a yield break causes MoveNext() to return false.

一个‘iterator block’迭代器块是一个普通方法,它有两个特点,1,返回IEnumerator。2,使用yield关键字。yield关键字是做什么的呢?它声明序列中的下一个值是什么,或者没有任何一下个值。当代码遇到yield return X或者yield break,IEnumerator.MoveNext()将会停止。yield return X将会导致MoveNext()返回true,Current被赋值为X。yield break导致MoveNext()返回false。

2,Unity协程底层实现猜测


上文中提到了C#实现IEnumerator接口的方法会放回一个枚举集合,它有两个特点,一是遇到yield语句会暂停,而是可以利用MoveNext()方法回到上次暂停处。
那利用这两个特点再结合update,我可能会这么实现协程:C#先对实现IEnumerator接口的fun1函数进行解析和转换,Unity负责实现协程的模块获取该函数的Enumerator,在Update中不断进行MoveNext(),逻辑流会在fun1的current处开始,在yield处暂停,Unity负责将yield返回的值解析为条件(例如等待时间),再在update中进行条件判断继续MoveNext或等待下一个update。

3,Lua协程

相对于Unity协程,lua协程也同样是非对称协程(协程函数只能将控制权交还给调用者),不同点在于lua可以控制在何处恢复某一协程,lua还可通过yield,resume传递参数,Unity只能在启动协程时传入参数,协程可控制在何处挂起但是主线程无法控制协程在何处恢复。

func1=function(para1)               //下面将会调用的协程函数既是协程体body...ret3=coroutine.yield(var2)        //暂停协程,将控制焦点以及var2返回给调用者,var2成为ret2。ret3为协程调用者下次调用resume传进来的参数。...
endco=coroutine.create(func1)          //创建协程
r,ret2=coroutine.resume(co,var1)   //启动协程并传入var1变为para1。//返回值r为协程状态(suspended,running,normal,dead)//ret2为协程yield的返回值coroutine.resume(co,var3)            //恢复协程,逻辑流从协程body上一个yield处开始,var3传为ret3//表达式没有左值,因此不再接收协程状态和协程body yield返回的值

4, xLua和Unity协程的混合使用

Unity的协程使用非常简单

void Update()
{if (Input.GetKeyDown("f")) {StartCoroutine("Fade");}
}
IEnumerator Fade()
{for (float ft = 1f; ft >= 0; ft -= 0.1f) {Color c = renderer.material.color;c.a = ft;renderer.material.color = c;yield return null;}
}

Lua协程虽然已经可以满足逻辑流的挂起与恢复功能以实现异步的效果,但是在有些场景必须需要Unity协程的辅助,例如在Lua协程body中挂起等下一帧或n秒后再运行下一行代码。

任何继承MonoBehaviour的脚本都可以调用StartCoroutine方法开启协程。在xLua中可以以N种方法拿到继承它的CS脚本实例(Unity Component),然后在C#部分调用StartCoroutine方法。

Lua协程与Unity协程混合使用的基本思路:

Lua主线程开启Lua协程==>Lua协程挂起==>Lua主线程开启Unity协程 ==>Unity协程yield(条件)==>Unity协程中调用Lua主线程回调函数==>Lua主线程回调函数中恢复Lua协程==>…

Lua协程不像Unity协程可以自动管理挂起协程的恢复,因此需要在一个回调函数中手动resume挂起的Lua协程,Lua支持函数作为参数,因此可以将回调方法传入C#,C#再以Action方式调用。Lua主线程开启Unity协程后,当Unity协程走到第一个yield处时,Lua主线程继续执行代码,将之后Lua协程的恢复交给了Unity协程。这样不需要任何辅助代码,简单实现了一个Lua协程根据Unity协程YieldInstruction挂起与恢复的功能。

C#部分(未测试)

    public void YieldAndCallback(object unityCondition, Action callback){StartCoroutine(CoBody(unityCondition, callback));}private IEnumerator CoBody(object unityCondition, Action callback){if (unityCondition is IEnumerator)yield return StartCoroutine((IEnumerator)unityCondition);elseyield return unityCondition;callback();}

Lua部分(未测试)

--self.cb要引用到C#实例的YieldAndCallback方法function LuaCoroutine:CallBack(...)self:MoveNext()
endfunction LuaCoroutine:MoveNext()local status, unityCondition = coroutine.resume(self.CoBody)self.cb(unityCondition, handler(self.CallBack, self))
endfunction LuaCoroutine:TestFunction()--do something 1coroutine.yield(CS.UnityEngine.WaitForSeconds(0.5))--do something 2coroutine.yield()--do something 3
endfunction LuaCoroutine:TestStart()self.CoBody = coroutine.create(self.TestFunction)self:MoveNext()
endfunction handler(method, obj)return function(...)if obj==nil thenreturn method(...)elsereturn method(obj, ...)endend
end

xLua官方的协程实现可以在不使用lua协程将协程体函数作为参数传给一个util.cs_generator,在协程体内可直接yield Unity YieldInstruction,util.cs_generator利用了一些底层机制隐藏了相关机制的一些细节。


参考:
http://stackoverflow.com/questions/12932306/how-does-startcoroutine-yield-return-pattern-really-work-in-unity
–James McMahon
C#高级编程第九版 --C.Nagel等


维护日志:
2019-12-25 修改,添加内容

Unity协程实现分析以及Lua协程与Unity协程的混合使用相关推荐

  1. Lua基础之coroutine(协程)

    为什么80%的码农都做不了架构师?>>>    概括:1.创建协程2.coroutine的函数3.coroutine的基本流程4.yield对coroutine流程的干预5.resu ...

  2. Kotlin学习笔记23 协程part3 lambda表达式深入 挂起函数 全局协程

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 lambda表达式深入 /*** lambda 表达式深入* 当函数参数是函数时 并且该函数只有一个参数 可以不传入任 ...

  3. swoole mysql 协程_swoole-orm: 基于swoole的mysql协程连接池,简单封装。实现多个协程间共用同一个协程客户端。参考thinkphp-orm...

    swoole-orm 基于swoole的mysql协程连接池,简单封装. 实现多个协程间共用同一个协程客户端 感谢完善 [1]:nowbe -> 新增数据返回insert_id 版本 v0.0. ...

  4. python协程和线程_python之并发编程(线程\进程\协程)

    一.进程和线程 1.进程 假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源.是 ...

  5. python协程是什么_在python中线程和协程的区别是什么

    在python中线程和协程的区别:1.一个线程可以拥有多个协程,这样在python中就能使用多核CPU:2.线程是同步机制,而协程是异步:3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进 ...

  6. python线程协程进程的区别_进程和线程、协程的区别

    现在多进程多线程已经是老生常谈了,协程也在最近几年流行起来.python中有协程库gevent,py web框架tornado中也用了gevent封装好的协程.本文主要介绍进程.线程和协程三者之间的区 ...

  7. Unity性能优化要点分析(二) 渲染优化技术

    本章摘录自UnityShader入门精要的第16章内容. 移动平台的特点 移动平台的GPU架构有很大不同,由于芯片架构的不同,一些游戏往往需要针对不同的芯片发布不同的版本,以便对每种芯片进行更有针对性 ...

  8. unity ulua基础(ulua介绍,lua与C#互相调用)

    slua ulua nlua 某篇文章的说法 slua 是目前所有unity+lua方案里最快的,没有反射,很少gc alloc,功能最强大的 ulua/nlua 都是基于反射的解决方案,劣势是速度慢 ...

  9. Unity之翻牌游戏分析

    Unity之翻牌游戏分析 一.游戏介绍 二.游戏开发 1.搭建场景 2.逻辑分析 3.难点突破 三.总结 一.游戏介绍 4*4的16张卡片里面有8种卡片,每种2张一样的,默认卡片全都是反面的,点击会翻 ...

最新文章

  1. LB 负载均衡的层次结构
  2. 共享内存 传一个类指针_C++指针
  3. 16个烧光你脑细胞的悖论
  4. Linux 实操———— Shell 远程执行命令
  5. sql server 监视_监视SQL Server报告服务
  6. android监听键盘的隐藏,Android监听软键盘的显示和隐藏
  7. Visual Studio 2010 中编写C代码的一些常见问题
  8. python 多线程读写文件_Python多线程同步---文件读写控制方法
  9. 圈圈教你玩usb第一版硬件实物图
  10. linux学习—— CentOS命令行版下创建KVM虚拟机(VM)并添加SR-IOV的虚拟网卡(VF)
  11. matlab求导赋值,MATLAB 函数先求导再赋值
  12. 如何读取 PEM 文件以获取公钥和私钥
  13. 泽众AutoRunner软件的使用方法,以测试win10环境下系统自带的计算器为例
  14. Total Control的深入用法,如何使用脚本实现启动或重启指定App
  15. Vite图片压缩(vite-plugin-imagemin) imagemin error: XXXX解决办法
  16. 常见几种操作系统简介
  17. js获取最新的省市区地址
  18. 前端保留两位有效数字_用js取小数点后两位的一些方法
  19. 【数字图像处理】秒懂傅里叶变换,仅需此文
  20. HTTP协议是什么?

热门文章

  1. react更改路由入参_react路由传参方式
  2. java获取本机ip地址_代码片段:获取系统所有IP
  3. 中科大410分计算机排名第几,2021考研成绩发布:中科大400分无缘复试,中山大学321分登顶第二...
  4. php 整型,php整型就是整数
  5. linux 查找文件_LINUX常用命令全集
  6. python创建字典和包的区别_python之路—模块和包
  7. cmd指令大全指令_数控加工中心编程技巧及指令大全,请转给需要的数控人!
  8. layer php弹出层,layer官方演示与讲解(jQuery弹出层插件)
  9. 高速信号传输约翰逊 pdf_高速串口技术如何突破板级连接限制
  10. android电视怎么升级失败,智能电视升级失败,原因都在这里!