unity3d:协程实现原理(转),IEnumerator,yield,编辑器下协程

转https://www.cnblogs.com/iwiniwin/p/14878498.html

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
出现在其中的方法就会被编译器自动编译成一个迭代器,对于这样的函数可以称之为迭代器函数。迭代器函数的返回值就是自动生成的迭代器类的一个对象

  1. yield return语句可以返回一个值,表示迭代得到的当前元素
  2. 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已做好了一些默认处理,比如:

  1. 如果Current是null,就相当于什么也不做。在下一次游戏循环中,就会调用MoveNext。所以yield return null就起到了等待一帧的作用

  2. 如果Current是WaitForSeconds类型,Unity会获取它的等待时间,每次游戏循环中都会判断时间是否到了,只有时间到了才会调用MoveNext。所以yield return WaitForSeconds就起到了等待指定时间的作用

  3. 如果Current是UnityWebRequestAsyncOperation类型,它是AsyncOperation的子类,而AsyncOperation有isDone属性,表示操作是否完成,只有isDone为true时,Unity才会调用MoveNext。对于UnityWebRequestAsyncOperation而言,只有请求完成了,才会将isDone属性设置为true。

using(UnityWebRequest webRequest = UnityWebRequest.Get("https://www.cnblogs.com/iwiniwin/p/13705456.html"))
{
    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);
    }

输出
在这里插入图片描述

代码解析:

  1. 第0个进入的是个yield set 为null,接着立马执行MoveNext,current = yield return new EditorWaitForSeconds(10); //第1个current
  2. 在EditorUpdate中检测满足 第1个current的时间条件,然后执行current.MoveNext,执行了代码块a,接着current = yield return new EditorWaitForSeconds(8);//第2个 curent
  3. 在EditorUpdate中检测满足第2个current的条件, 然后执行current.MoveNext,执行代码块b,并把current推到了集合的末尾,现在是推到了集合的的最后的,isHasNext 为false,清除掉EditorUpdate的该委托。但是实际没推成功,current还是指向第2个current

源码

https://github.com/luoyikun/UnityForTest
EditorCorTest.cs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值