Unity引擎源码解析(伪) - 4 协程

unity中开启协程的方法如下:

IEnumerator Start()
{
    StartCoroutine(DoWait(2.0f));
    yield return new WaitForSeconds(1.0f);
    StartCoroutine("DoSomething", 2.0f);
}
IEnumerator DoWait(float waitTime)
{
    while (true)
    {
        yield return new WaitForSeconds(waitTime);
    }
}
IEnumerator DoSomething(float someParameter)
{
    while (true)
    {
        yield return null;
    }
}

可以看到协程开启的方法有两种传参方式,传入IEnumerator迭代器,或方法名的字符串。两种方法在引擎中的绑定方法如下:

using System;
using System.Collections;
using UnityEngine.Bindings;
using UnityEngine.Scripting;
using UnityEngineInternal;
using uei = UnityEngine.Internal;

namespace UnityEngine
{
    [RequiredByNativeCode]
    [ExtensionOfNativeClass]
    [NativeHeader("Runtime/Mono/MonoBehaviour.h")]
    [NativeHeader("Runtime/Scripting/DelayedCallUtility.h")]
    public class MonoBehaviour : Behaviour
    {
        [uei.ExcludeFromDocs]
        public Coroutine StartCoroutine(string methodName)
        {
            object value = null;
            return StartCoroutine(methodName, value);
        }

        // Starts a coroutine named /methodName/.
        public Coroutine StartCoroutine(string methodName, [uei.DefaultValue("null")] object value)
        {
            if (string.IsNullOrEmpty(methodName))
                throw new NullReferenceException("methodName is null or empty");

            if (!IsObjectMonoBehaviour(this))
                throw new ArgumentException("Coroutines can only be stopped on a MonoBehaviour");

            return StartCoroutineManaged(methodName, value); //对应C++中的 StartCoroutineManaged() 函数
        }

        // Starts a coroutine.
        public Coroutine StartCoroutine(IEnumerator routine)
        {
            if (routine == null)
                throw new NullReferenceException("routine is null");

            if (!IsObjectMonoBehaviour(this))
                throw new ArgumentException("Coroutines can only be stopped on a MonoBehaviour");

            return StartCoroutineManaged2(routine); //对应C++中的 StartCoroutineManaged2() 函数
        }
    }
}

两种方式最终调用到了C++中类 MonoBehaviour StartCoroutineManaged() 和 StartCoroutineManaged2() 函数,实现如下:

//对应 StartCoroutine(DoWait(2.0f));
ScriptingObjectPtr MonoBehaviour::StartCoroutineManaged2(ScriptingObjectPtr enumerator)
{
    if (!IsActive())
    {
        ErrorStringObject(Format("Coroutine couldn't be started because the the game object '%s' is inactive!", GetName()), this);
        return SCRIPTING_NULL;
    }
    Coroutine* coroutine = CreateCoroutine(enumerator, NULL); //创建并启动协程
    return CreateManagedWrapperForCoroutine(coroutine); //将协程对象 Coroutine 重新包装成 ScriptingObjectPtr 传回C#
}

//对应 StartCoroutine("DoSomething", 2.0f);
ScriptingObjectPtr MonoBehaviour::StartCoroutineManaged(const char* name, ScriptingObjectPtr value)
{
    Coroutine* coroutine = StartCoroutine(name, value); //跳转
    return CreateManagedWrapperForCoroutine(coroutine); //将协程对象 Coroutine 重新包装成 ScriptingObjectPtr 传回C#
}
Coroutine* MonoBehaviour::StartCoroutine(const char* name, ScriptingObjectPtr value)
{
    Assert(ShouldRunBehaviour());
    Assert(GetInstance() != SCRIPTING_NULL);
    if (!IsActive())
    {
        ErrorStringObject(Format("Coroutine '%s' couldn't be started because the the game object '%s' is inactive!", name, GetName()), this);
        return NULL;
    }
    ScriptingMethodPtr method = FindMethod(name);//查找C#方法引用
    if (method.IsNull())
    {
        ErrorStringObject(Format("Coroutine '%s' couldn't be started!", name), this);
        return NULL;
    }
    return InvokeMethodOrCoroutineChecked(method, value); //根据方法名字符串调用,并创建协程
}
Coroutine* MonoBehaviour::InvokeMethodOrCoroutineChecked(ScriptingMethodPtr method, ScriptingObjectPtr value)
{
    Assert(ShouldRunBehaviour());
    ScriptingObjectPtr instance = GetInstance();
    Assert(instance != SCRIPTING_NULL);
    ScriptingExceptionPtr exception = SCRIPTING_NULL;
    ScriptingObjectPtr returnValue = InvokeMethodOrCoroutineChecked(method, value, &exception); //正式调用C#中的方法,跳转
    
    //如果调用后返回值不为空就创建协程,否则直接结束
    if (returnValue != SCRIPTING_NULL && exception == SCRIPTING_NULL)
        return HandleCoroutineReturnValue(method, returnValue); //创建协程,跳转

    if (exception != SCRIPTING_NULL)
        Scripting::LogException(exception, GetInstanceID());

    return NULL;
}
//调用C#脚本的方法,并返回结果(迭代器对象)
ScriptingObjectPtr MonoBehaviour::InvokeMethodOrCoroutineChecked(ScriptingMethodPtr scriptingMethod, ScriptingObjectPtr value, ScriptingExceptionPtr* exception)
{
    ...
    int argCount = scripting_method_get_argument_count(scriptingMethod);
    ScriptingInvocation invocation(GetInstance(), scriptingMethod);
    invocation.logException = false;

    if (argCount == 0)
        return invocation.Invoke(exception); //调用无参
    ...
    ScriptingClassPtr paramClass = scripting_class_from_type(paramType);
    int paramTypeType = scripting_type_get_type(paramType);

    if (scripting_type_is_builtin(paramTypeType))
    {
        ScriptingObjectPtr converted = ConvertBuiltinScriptingValue(value, paramTypeType);
        if (converted)
            invocation.AddStruct(ExtractManagedObjectDataPtr<void>(converted));
    }
    else if (paramTypeType == SCRIPTING_TYPE_VALUETYPE)
    {
        if (inParamClass == paramClass)
            invocation.AddStruct(ExtractManagedObjectDataPtr<void>(value));
    }
    else if (paramTypeType == SCRIPTING_TYPE_CLASS)
    {
        if (scripting_class_is_subclass_of(inParamClass, paramClass))
            invocation.AddObject(value);
    }
    else if (paramTypeType == SCRIPTING_TYPE_STRING && paramTypeType == scripting_type_get_type(scripting_class_get_type(inParamClass)))
    {
        invocation.AddObject(value);
    }
    else if (paramTypeType == SCRIPTING_TYPE_OBJECT)
    {
        invocation.AddObject(value);
    }

    if (invocation.Arguments().GetCount() == 1)
        return invocation.Invoke(exception, false); //调用有参

    //换另一种调用方式
    ScriptingInvocation invokeMember(GetCoreScriptingClasses().invokeMember);
    invokeMember.logException = false;
    invokeMember.AddObject(GetInstance());
    invokeMember.AddString(scripting_method_get_name(scriptingMethod));
    invokeMember.AddObject(value);
    return invokeMember.Invoke(exception, false);
    ...
}
Coroutine* MonoBehaviour::HandleCoroutineReturnValue(ScriptingMethodPtr method, ScriptingObjectPtr returnValue)
{
    if (!IsCoroutine(method, GetMonoManager().GetCommonClasses())) //判断当前是否合法的协程函数,其实就是判断方法的返回值是不是迭代器
        return NULL;

    return CreateCoroutine(returnValue, method);//创建并启动协程
}

这里目的是为了将从C#传入的 IEnumerator(C++对应ScriptingObjectPtr) 迭代器,通过函数 CreateCoroutine() 执行后得到 Coroutine对象,最后返还给C#。

可以看出传入字符串的方式要麻烦的多,还是尽量别用这种方式吧。

Coroutine* MonoBehaviour::CreateCoroutine(ScriptingObjectPtr userCoroutine, ScriptingMethodPtr method)
{
    Coroutine* coroutine = NULL;
    TryCreateAndRunCoroutine(userCoroutine, method, &coroutine);
    return coroutine;
}
bool MonoBehaviour::TryCreateAndRunCoroutine(ScriptingObjectPtr userCoroutine, ScriptingMethodPtr method, Coroutine **coroutine)
{
    ScriptingMethodPtr moveNext = scripting_object_get_virtual_method(userCoroutine, GetCommonScriptingClasses().IEnumerator_MoveNext);
    ScriptingMethodPtr current = scripting_object_get_virtual_method(userCoroutine, GetCommonScriptingClasses().IEnumerator_Current);
    ...
    //创建协程对象
    *coroutine = new Coroutine();
    (*coroutine)->m_CoroutineEnumeratorGCHandle.AcquireStrong(userCoroutine);
    (*coroutine)->m_CoroutineMethod = method;
    (*coroutine)->SetMoveNextMethod(moveNext); //绑定C#中迭代器字段 moveNext
    (*coroutine)->SetCurrentMethod(current);  //绑定C#中迭代器字段 current
    (*coroutine)->m_Behaviour = this;
    (*coroutine)->m_ContinueWhenFinished = NULL;
    (*coroutine)->m_WaitingFor = NULL;
    (*coroutine)->m_AsyncOperation = NULL;
    (*coroutine)->m_RefCount = 1;
    ...
    m_ActiveCoroutines.push_back(**coroutine); //加入当前MonoBehaviour的列表
    Assert(&m_ActiveCoroutines.back() == *coroutine);
    bool exceptionHasBeenThrown;
    m_ActiveCoroutines.back().Run(&exceptionHasBeenThrown); //执行协程
    //m_RefCount表示内部的引用计数,大于1:后续还有得执行
    Assert((*coroutine)->m_RefCount != 0);
    if ((*coroutine)->m_RefCount <= 1)
    {
        Coroutine::CleanupCoroutine(*coroutine);
        *coroutine = NULL;
        return !exceptionHasBeenThrown;
    }
    Coroutine::CleanupCoroutine(*coroutine);
    return true;
}

协程的创建包装了迭代器,而迭代器是在C#运行的,C#中运行的结果又返回给C++中来执行逻辑。现在来看看C++中协程类 Coroutine 的核心实现。

CallObjectState Coroutine::Run(bool *exceptionThrown)
{
    ...
    m_RefCount++;
    ScriptingExceptionPtr exception = SCRIPTING_NULL;
    bool keepLooping = InvokeMoveNext(&exception); //调用C#中的迭代器,运行到下一项
    Assert(m_RefCount > 0 && m_RefCount <= 10000000);
    ...
    //如果迭代完成了,执行完成逻辑
    if (!keepLooping)
    {
        m_RefCount++;
        if (m_ContinueWhenFinished) //如果当前的协程,是另一个协程开启的
        {
            Assert(this == m_ContinueWhenFinished->m_WaitingFor);
            Coroutine* continueWhenFinished = m_ContinueWhenFinished;
            m_ContinueWhenFinished->m_WaitingFor = NULL;
            m_ContinueWhenFinished = NULL;
            if (continueWhenFinished->m_Behaviour)
            {
                m_DoneRunning = true;
                continueWhenFinished->Run(); //继续执行父协程
            }
            CleanupCoroutine(continueWhenFinished);
        }
        //后续的清理
        const bool wasDestroyed = m_RefCount == 1;
        CleanupCoroutine(this);
        if (wasDestroyed)
            return kCallObjectDestroyed;

        return kCallObjectAlive;
    }

    if (m_Behaviour == NULL)
        return kCallObjectAlive;

    ProcessCoroutineCurrent(); //处理当前迭代器的当前项
    return kCallObjectAlive;
}

void Coroutine::ProcessCoroutineCurrent()
{
    ScriptingExceptionPtr exception = SCRIPTING_NULL;
    ScriptingInvocation invocation(m_Current);
    invocation.objectInstanceIDContextForException = m_Behaviour->GetInstanceID();
    invocation.classContextForProfiler = m_Behaviour->GetClass();
    ScriptingClassPtr methodKlass = scripting_method_get_class(m_Current);
    if (scripting_class_is_valuetype(methodKlass))
        invocation.SetTarget(scripting_object_unbox(m_CoroutineEnumeratorGCHandle.Resolve()));
    else
    {
        invocation.SetTarget(m_CoroutineEnumeratorGCHandle.Resolve());
    }
    ScriptingObjectPtr monoWait = invocation.Invoke(&exception); //获取迭代器当前项的对象数据
    Assert(m_RefCount > 0 && m_RefCount <= 10000000);
    if (exception != SCRIPTING_NULL)
        return;
    //0 如果是空,这里其实是C#中的代码:yield return null;
    if (monoWait == SCRIPTING_NULL)
    {
        m_RefCount++;
        //下一帧执行完成任务
        CallDelayed(ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
        return;
    }
    HandleIEnumerableCurrentReturnValue(monoWait); //继续处理当前项
}

void Coroutine::HandleIEnumerableCurrentReturnValue(ScriptingObjectPtr monoWait)
{
    AsyncOperation* async = NULL;
    ScriptingClassPtr waitClass = scripting_object_get_class(monoWait);
    const CommonScriptingClasses& classes = GetMonoManager().GetCommonClasses();
    //1 如果当前项是 延时操作
    if (scripting_class_is_subclass_of(waitClass, GetCoreScriptingClasses().waitForSeconds))
    {
        m_RefCount++;
        ScriptingWaitForSeconds wait;
        MarshallManagedStructIntoNative(monoWait, &wait);
        if (IsNAN(wait.m_Seconds))
            ErrorString("float.NaN has been passed into WaitForSeconds which will result in an infinite wait time.");
        //开启一个延时回调
        CallDelayed(ContinueCoroutine, m_Behaviour, wait.m_Seconds, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
        return;
    }
    //2 如果当前项是 等下一个固定帧
    if (scripting_class_is_subclass_of(waitClass, GetCoreScriptingClasses().waitForFixedUpdate))
    {
        m_RefCount++;
        //开启固定帧的延时
        CallDelayed(ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunFixedFrameRate);
        return;
    }
    //3 如果当前项是 等当前帧结束
    if (scripting_class_is_subclass_of(waitClass, GetCoreScriptingClasses().waitForEndOfFrame))
    {
        m_RefCount++;
        //开启等待当前帧结束的回调
        CallDelayed(ContinueCoroutine, m_Behaviour, -1.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kEndOfFrame);
        return;
    }
    //4 如果当前项是 迭代器
    if (scripting_class_is_subclass_of(waitClass, classes.iEnumerator))
    {
#if UNITY_EDITOR
        if (m_CoroutineEnumeratorGCHandle.Resolve() == monoWait)
        {
            const char* className = scripting_class_get_name(waitClass);
            WarningStringMsg("IEnumerator class %s is returning this (itself) in %s.Current, which can lead to infinite recursion.", className, className);
        }
#endif
        m_RefCount++;
        Coroutine* waitForCoroutine;
        //重新调用 MonoBehaviour 的开启协程的函数
        if (!m_Behaviour->TryCreateAndRunCoroutine(monoWait, NULL, &waitForCoroutine))
            return;
        if (waitForCoroutine == NULL)
        {
            CallDelayed(ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
            return;
        }
        AssertMsg(!waitForCoroutine->m_DoneRunning, "Coroutine initialized incorrectly");
        AssertMsg(waitForCoroutine->m_ContinueWhenFinished == NULL, "Coroutine initialized incorrectly");
        waitForCoroutine->m_IsIEnumeratorCoroutine = true;
        waitForCoroutine->m_ContinueWhenFinished = this;
        m_IsIEnumeratorCoroutine = true;
        m_WaitingFor = waitForCoroutine; //给当前协程设置子协程
        return;
    }
    //5 如果当前项是 另一个协程
    if (scripting_class_is_subclass_of(waitClass, GetCoreScriptingClasses().coroutine))
    {
        Coroutine* waitForCoroutine;
        MarshallManagedStructIntoNative(monoWait, &waitForCoroutine); //类型转换为C++中对应的协程
        if (waitForCoroutine->m_DoneRunning)
        {
            //直接走下一步
            ContinueCoroutine(m_Behaviour, this);
            return;
        }
        if (waitForCoroutine->m_ContinueWhenFinished != NULL)
        {
            LogStringObject("Another coroutine is already waiting for this coroutine!\nCurrently only one coroutine can wait for another coroutine!", m_Behaviour);
            return;
        }
        m_RefCount++;
        waitForCoroutine->m_ContinueWhenFinished = this;
        m_WaitingFor = waitForCoroutine; //给当前协程设置子协程
        return;
    }
    //6 如果当前项是 一个异步操作,如unity中的网络请求 yield return UnityWebRequest.Get(uri).SendWebRequest();
    if ((scripting_class_is_subclass_of(waitClass, GetCoreScriptingClasses().asyncOperation)) && (async = ScriptingObjectWithIntPtrField<AsyncOperation>(monoWait).GetPtr()) != NULL)
    {
        m_RefCount++;
        //异步任务完成,延迟一帧回调
        if (async->IsDone())
        {
            CallDelayed(ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
            return;
        }
        //设置过回调,下一帧继续
        if (async->HasCoroutineCallback())
        {
            ErrorString("This asynchronous operation is already being yielded from another coroutine. An asynchronous operation can only be yielded once.");
            CallDelayed(ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
            return;
        }
        //设置异步操作的回调,完成了就会继续迭代下一项
        async->SetCoroutineCallback(ContinueCoroutine, m_Behaviour, this, CleanupCoroutine);
        if (m_AsyncOperation != NULL)
        {
            m_AsyncOperation->Release();
        }
        m_AsyncOperation = async;
        m_AsyncOperation->AddRef();
        return;
    }
    //7 如果当前项是 未知类型。比如 yield return 123456
    m_RefCount++;
    CallDelayed(ContinueCoroutine, m_Behaviour, 0.0F, this, 0.0F, CleanupCoroutine, DelayedCallManager::kRunDynamicFrameRate | DelayedCallManager::kWaitForNextFrame);
}

//每一种迭代器的项处理之后的延迟函数
CallObjectState Coroutine::ContinueCoroutine(Object* o, void* userData)
{
    Coroutine* coroutine = (Coroutine*)userData;
    Assert(coroutine->m_RefCount > 0 && coroutine->m_RefCount < 1000000);
    if ((Object*)coroutine->m_Behaviour != o) //异常处理
    {
        ErrorString("Coroutine continue failure");
        #if DEBUG_COROUTINE
        if ((Object*)coroutine->m_Behaviour != o)
        {
            printf_console("continue Coroutine corruption %d refcount: %d behaviour: %d \n", coroutine, coroutine->m_RefCount, coroutine->m_Behaviour);
            printf_console("continue Coroutine corruption name: %s methodname\n", ((MonoBehaviour*)(o))->GetScript()->GetName());
            if (!coroutine->m_CoroutineMethod.IsNull())
                printf_console("continue Coroutine methodname: %s\n", scripting_method_get_name(coroutine->m_CoroutineMethod));
        }
        #endif
        return kCallObjectAlive;
    }
    //协程递归执行,推进迭代器到下一项
    return coroutine->Run();
}

这里就是unity协程最最核心的逻辑了,它一共细分为 0 - 7 共八种类型判断(0 和 7 其实逻辑是一样的,4 和 5 的逻辑也算同一种)。这里还剩一个待讨论点,就是 CallDelayed() 函数了,它里面其实是unity内部实现了一个延迟调用管理器,在大循环初始化的时候也有注册。

void InitPlayerLoopCallbacks()
{
    ...
    //Update
    REGISTER_PLAYERLOOP_CALL(Update, ScriptRunBehaviourUpdate,
    {
        GetBehaviourManager().Update();
    });
    //DelayedCallManager
    REGISTER_PLAYERLOOP_CALL(Update, ScriptRunDelayedDynamicFrameRate,
    {
        GetDelayedCallManager().Update(DelayedCallManager::kRunDynamicFrameRate);
    });
    ...
}

//宏定义
#define PLAYER_LOOP_UPDATE \
    PLAYER_LOOP_INJECT(ScriptRunBehaviourUpdate) \ //Update
    PLAYER_LOOP_INJECT(ScriptRunDelayedDynamicFrameRate) \ //可见 DelayedCallManager 与 Update 在同一个循环分组,但顺序在它之后
    PLAYER_LOOP_INJECT(ScriptRunDelayedTasks) /* Process tasks added to the Synchronizaton Context */ \
    PLAYER_LOOP_INJECT(DirectorUpdate)

延迟调用管理器 DelayedCallManager 的内部实现就不细说了,简单描述下就是:调用 CallDelayed(...) 后构造一个带调用对象存放进一个 std::multiset(因为当前需求要按执行时间自动排序,且时间不唯一) 容器中,内部的处理函数 GetDelayedCallManager().Update(...) 会在若干个循环阶段执行。

另外再提一点,MonoBehaviour 中的 Start() 函数

inline void MonoBehaviour::Start()
{
    ...
    ScriptingMethodPtr method = GetMethod(MonoScriptCache::kCoroutineStart);
    if (!method.IsNull())
        InvokeMethodOrCoroutineChecked(method, SCRIPTING_NULL); //执行Start(),如果返回值是迭代器,就开启协程
}

所以C#脚本中的 Start() 返回值可以是 void 也可以是 IEnumerator 的原因了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值