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 的原因了。