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/[email protected]/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