IEnumerator
IEnumerator是所有非泛型枚举器的基接口。换而言之就是IEnumerator定义了一种适用于任意集合的迭代方式。任意一个集合只要实现自己的IEnumerator,它的使用者就可以通过IEnumerator迭代集合中的元素
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
1.Current属性可以获取集合中当前迭代位置的元素
2.MoveNext方法将当前迭代位置推进到下一个位置,如果成功推进到下一个位置则返回true,否则已经推进到集合的末尾返回false
3.Reset方法可以将当前迭代位置设置为初始位置(该位置位于集合中第一个元素之前,所以当调用Reset方法后,再调用MoveNext方法,Curren值则为集合的第一个元素)
比如我们经常会使用的foreach关键字遍历集合,其实
只是C#提供的语法糖而已
foreach (var item in collection)
{
Console.WriteLine(item.ToString());
}
在编译时编译器会将上面的循环转换为类似于下面的代码
{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext()) // 判断是否成功推进到下一个元素(可理解为集合中是否还有可供迭代的元素)
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
} finally
{
// dispose of enumerator.
}
}
yield
yield是C#的关键字,其实就是快速定义迭代器的语法糖。只要是yield
出现在其中的方法就会被编译器自动编译成一个迭代器,对于这样的函数可以称之为迭代器函数。迭代器函数的返回值就是自动生成的迭代器类的一个对象
- yield return语句可以返回一个值,表示迭代得到的当前元素
- yield break语句可以用来终止迭代,表示当前没有可被迭代的元素了
Unity协程机制的实现原理
协程是一种比线程更轻量级的存在,协程可完全由用户程序控制调度。协程可以通过yield方式进行调度转移执行权,调度时要能够保存上下文,在调度回来的时候要能够恢复。这是不是和上面“停住”代码然后又原位恢复的执行效果很像?没错,Unity实现协程的原理,就是通过yield return生成的IEnumerator再配合控制何时触发MoveNext来实现了执行权的调度
具体而言,Unity每通过MonoBehaviour.StartCoroutine启动一个协程,就会获得一个IEnumerator(StartCoroutine的参数就是IEnumerator,参数是方法名的重载版本也会通过反射拿到该方法对应的IEnumerator)。并在它的游戏循环中,根据条件判断是否要执行MoveNext方法。而这个条件就是根据IEnumerator的Current属性获得的,即yield return返回的值。
在启动一个协程时,Unity会先调用得到的IEnumerator的MoveNext一次,以拿到IEnumerator的Current值。所以每启动一个协程,协程函数会立即执行到第一个yield return处然后“停住”。
对于不同的Current类型(一般是YieldInstruction的子类),Unity已做好了一些默认处理,比如:
- 如果Current是null,就相当于什么也不做。在下一次游戏循环中,就会调用MoveNext。所以yield return null就起到了等待一帧的作用
- 如果Current是WaitForSeconds类型,Unity会获取它的等待时间,每次游戏循环中都会判断时间是否到了,只有时间到了才会调用MoveNext。所以yield return WaitForSeconds就起到了等待指定时间的作用
- 如果Current是UnityWebRequestAsyncOperation类型,它是AsyncOperation的子类,而AsyncOperation有isDone属性,表示操作是否完成,只有isDone为true时,Unity才会调用MoveNext。对于UnityWebRequestAsyncOperation而言,只有请求完成了,才会将isDone属性设置为true。
using(UnityWebRequest webRequest = UnityWebRequest.Get()
{
yield return webRequest.SendWebRequest();
if(webRequest.isNetworkError)
{
Debug.Log("Error " + webRequest.error);
}
else
{
Debug.Log("Received " + webRequest.downloadHandler.text);
}
}
namespace UnityEngine.Networking
{
//
// 摘要:
// Asynchronous operation object returned from UnityWebRequest.SendWebRequest().
// You can yield until it continues, register an event handler with AsyncOperation.completed,
// or manually check whether it's done (AsyncOperation.isDone) or progress (AsyncOperation.progress).
[NativeHeader("Modules/UnityWebRequest/Public/UnityWebRequestAsyncOperation.h")]
[NativeHeader("UnityWebRequestScriptingClasses.h")]
[UsedByNativeCode]
public class UnityWebRequestAsyncOperation : AsyncOperation
{
public UnityWebRequestAsyncOperation();
//
// 摘要:
// Returns the associated UnityWebRequest that created the operation.
public UnityWebRequest webRequest { get; }
}
}
编辑器下协程
文件浏览器
https://github.com/iwiniwin/unity-remote-file-explorer
Editor Coroutines version 0.0.1-preview.2
https://docs.unity3d.com/Packages/com.unity.editorcoroutines@1.0/manual/index.html 对于不同的Current类型,生成不同的data,满足data的条件,执行MoveNext
public void Set(object yield)
{
if (yield == data.current)
return;
var type = yield.GetType();
var dataType = DataType.None;
double targetTime = -1;
if(type == typeof(EditorWaitForSeconds))
{
targetTime = EditorApplication.timeSinceStartup + (yield as EditorWaitForSeconds).WaitTime;
dataType = DataType.WaitForSeconds;
}
else if(type == typeof(EditorCoroutine))
{
dataType = DataType.EditorCoroutine;
}
else if(type == typeof(AsyncOperation) || type.IsSubclassOf(typeof(AsyncOperation)))
{
dataType = DataType.AsyncOP;
}
data = new ProcessorData { current = yield, targetTime = targetTime, type = dataType };
}
public bool MoveNext(IEnumerator enumerator)
{
bool advance = false;
switch (data.type)
{
case DataType.WaitForSeconds:
advance = data.targetTime <= EditorApplication.timeSinceStartup;
break;
case DataType.EditorCoroutine:
advance = (data.current as EditorCoroutine).m_IsDone;
break;
case DataType.AsyncOP:
advance = (data.current as AsyncOperation).isDone;
break;
default:
advance = data.current == enumerator.Current; //a IEnumerator or a plain object was passed to the implementation
break;
}
if(advance)
{
data = default(ProcessorData);
Debug.Log("enumerator.CurrentBefore:" + enumerator.Current);
bool isHasNext = enumerator.MoveNext();
Debug.Log("enumerator.Current:" + enumerator.Current + "--isHasNext:" + isHasNext );
return isHasNext;
}
return true;
}
测试
IEnumerator CountEditorUpdates()
{
yield return new EditorWaitForSeconds(10); //第1个current
++m_Updates; //代码块a 满足第1个cureent条件,cureent执行MoveNext后执行
Debug.Log(m_Updates);
yield return new EditorWaitForSeconds(8);//第2个 curent
++m_Updates;//代码块b 满足第2个cureent条件,cureent执行MoveNext后执行
Debug.Log(m_Updates);
}
输出
代码解析:
- 第0个进入的是个yield set 为null,接着立马执行MoveNext,current = yield return new EditorWaitForSeconds(10); //第1个current
- 在EditorUpdate中检测满足 第1个current的时间条件,然后执行current.MoveNext,执行了代码块a,接着current = yield return new EditorWaitForSeconds(8);//第2个 curent
- 在EditorUpdate中检测满足第2个current的条件, 然后执行current.MoveNext,执行代码块b,并把current推到了集合的末尾,现在是推到了集合的的最后的,isHasNext 为false,清除掉EditorUpdate的该委托。但是实际没推成功,current还是指向第2个current
源码
https://github.com/luoyikun/UnityForTest EditorCorTest.cs