unity coroutine

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


使用Unity 3D引擎的同窗,對於Coroutine(協程)的使用確定也是很是熟悉的了。然而Coroutine背後的技術以及具體的實現方式、運行流程如何,恐怕並非那麼容易說得清楚。xss

本文嘗試經過分析Unity 3.5.7版本的源代碼,來釐清這一關鍵技術細節。(因爲Unity 3.5.7的源代碼,能拿到的版本沒法編譯、運行,只能直接經過源代碼閱讀的方式來進行靜態的梳理)。編輯器

C#層面的Coroutine:背後的技術svg

IEnumerator接口與yield語句函數

IEnumerator自己是一個forward iterator,定義以下:oop

public interface IEnumeratorn
{
        object Current { get }
        bool MoveNext();
        void Reset();
}
源碼分析

 最多見的使用方式,是在foreach中使用,foreach又可展開爲(簡化版本):性能

    while (i.MoveNext())    // i implements IEnumeratorspa

    {3d

       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#》一書的第六章:「實現迭代器的捷徑」

【Unity 3D引擎源碼分析】全面解析Coroutine技術

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中,咱們能夠觀察一下實際上生成的代碼:

【Unity 3D引擎源碼分析】全面解析Coroutine技術

注意這個<Fade>c__Iterator15 ,就是編譯器幫咱們生成的輔助類,實現了IEnumerator接口:

【Unity 3D引擎源碼分析】全面解析Coroutine技術

咱們還能夠進一步展開MoveNext()方法來查看:

【Unity 3D引擎源碼分析】全面解析Coroutine技術

因而能夠發現,咱們寫在Fade()中的代碼,都出如今了MoveNext()中,而編譯器將其展開放在了一個有限狀態機中;而yield return語句,實際上直接賦值給了current:

【Unity 3D引擎源碼分析】全面解析Coroutine技術

至此,Coroutine在C#層面所涉及的技術,咱們已經有了一個大致的瞭解。

C++運行時分析

咱們知道,Unity底層引擎是採用C++編寫,而後經過一箇中間層將全部功能封裝在UnityEngine.dll中(UnityEditor.dll對應編輯器功能),供上層C#(以及Unitynoxss、Boo)調用。

 咱們能夠在Reflector中查看這個dll,以下圖:

【Unity 3D引擎源碼分析】全面解析Coroutine技術

在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。

相關文章
相關標籤/搜索