Unity 协程原理
unity 协程 Unity 开发常用到协程Coroutine,但是unity的协程和monobehaviour绑定,有时候并不想继承mono,但是又想使用协程,这时候就有点麻烦,不如来学习协程原理来自己写一个吧(
协程 下面是一个简单的协程,我们可以看到下面除了常用语法,还有两个相对比较陌生的东西IEnumerator和yield
1 2 3 4 5 6 7 8 9 10 11 12 public class UnityCoroutineTest { public IEnumerator UnityCoroutineTestFunction() { int UnityCoroutineTest_i = 0; while (UnityCoroutineTest_i < 10) { UnityCoroutineTest_i++; yield return null; } } }
IEnumerator 迭代器 IEnumerator定义,一个接口,实现了下面的函数
1 2 3 4 5 6 7 8 9 10 11 12 namespace System.Collections { public interface IEnumerator { //指向当前物体 object Current { get; } //移向下一个 bool MoveNext(); //重置 void Reset(); } }
谈到了IEnumerator,顺便谈谈IEnumerable,可能看上去比较陌生,但其实我们常用的foreach遍历集合数组,背后就是IEnumerable在帮助我们,GetEnumerator获得迭代器,然后用迭代器进行遍历
1 2 3 4 5 6 7 namespace System.Collections { public interface IEnumerable { IEnumerator GetEnumerator(); } }
直接看官方文档使用案例,官方文档IEnumerator 接口 (System.Collections) | Microsoft Learn
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void Start() { coroutineTest = new UnityCoroutineTest(); IEnumerator enumerator = coroutineTest.UnityCoroutineTestFunction(); while (enumerator != null) { if(enumerator.MoveNext()) { Debug.Log(enumerator.Current); } else { break; } } }
yield 根据官方文档来看,实现IEnemerator/IEnumerable非常麻烦,需要实现接口类
但是我们的协程函数,并没有继承IEnumerator,但为什么还是可以使用对应的方法?
关键就是yield,直接看官方文档解释
yield 语句 - 在迭代器中提供下一个元素 - C# | Microsoft Learn
总结就是yield是C#的关键字,其实就是快速定义迭代器的语法糖。
协程原理 上面我们知道了yield IEnumerator
其实我们的协程函数就是一个迭代器函数,那我们为什么可以使用yield return null停住函数到下一帧执行,或者使用yield return new WaitForSeconds等待几秒继续执行呢
利用Unity的IL2CPP,将c#编译为c++ 截取部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 // UnityCoroutineTest struct UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562; // UnityCoroutineTest/<UnityCoroutineTestFunction>d__0 struct U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA; // UnityCoroutineTest/<UnityCoroutineTestFunction>d__0 struct U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA : public RuntimeObject { // System.Int32 UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::<>1__state int32_t ___U3CU3E1__state_0; // System.Object UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::<>2__current RuntimeObject* ___U3CU3E2__current_1; // System.Int32 UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::<UnityCoroutineTest_i>5__2 int32_t ___U3CUnityCoroutineTest_iU3E5__2_2; }; // System.Void UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::.ctor(System.Int32) IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void U3CUnityCoroutineTestFunctionU3Ed__0__ctor_m4DEDAEE22CC7C85C7E65D5C00DFE19D3FE923F09 (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, int32_t ___0_U3CU3E1__state, const RuntimeMethod* method) ; // System.Collections.IEnumerator UnityCoroutineTest::UnityCoroutineTestFunction() IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR RuntimeObject* UnityCoroutineTest_UnityCoroutineTestFunction_m5CE5BCA8CD865CB46FB4694BF146B222BBD4CC30 (UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562* __this, const RuntimeMethod* method) { static bool s_Il2CppMethodInitialized; if (!s_Il2CppMethodInitialized) { il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA_il2cpp_TypeInfo_var); s_Il2CppMethodInitialized = true; } { U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* L_0 = (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA*)il2cpp_codegen_object_new(U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA_il2cpp_TypeInfo_var); NullCheck(L_0); U3CUnityCoroutineTestFunctionU3Ed__0__ctor_m4DEDAEE22CC7C85C7E65D5C00DFE19D3FE923F09(L_0, 0, NULL); return L_0; } } // System.Void UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::.ctor(System.Int32) IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void U3CUnityCoroutineTestFunctionU3Ed__0__ctor_m4DEDAEE22CC7C85C7E65D5C00DFE19D3FE923F09 (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, int32_t ___0_U3CU3E1__state, const RuntimeMethod* method) { { Object__ctor_mE837C6B9FA8C6D5D109F4B2EC885D79919AC0EA2(__this, NULL); int32_t L_0 = ___0_U3CU3E1__state; __this->___U3CU3E1__state_0 = L_0; return; } } // System.Void UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::System.IDisposable.Dispose() IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void U3CUnityCoroutineTestFunctionU3Ed__0_System_IDisposable_Dispose_m8FD2D852D14451272231DB230B32DBA6359386AA (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, const RuntimeMethod* method) { { return; } } // System.Boolean UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::MoveNext() IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR bool U3CUnityCoroutineTestFunctionU3Ed__0_MoveNext_m3A155395AC6F342CA8666F1337118795E87D2F00 (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, const RuntimeMethod* method) { int32_t V_0 = 0; int32_t V_1 = 0; { int32_t L_0 = __this->___U3CU3E1__state_0; V_0 = L_0; int32_t L_1 = V_0; if (!L_1) { goto IL_0010; } } { int32_t L_2 = V_0; if ((((int32_t)L_2) == ((int32_t)1))) { goto IL_0040; } } { return (bool)0; } IL_0010: { __this->___U3CU3E1__state_0 = (-1); // int UnityCoroutineTest_i = 0; __this->___U3CUnityCoroutineTest_iU3E5__2_2 = 0; goto IL_0047; } IL_0020: { // UnityCoroutineTest_i++; int32_t L_3 = __this->___U3CUnityCoroutineTest_iU3E5__2_2; V_1 = L_3; int32_t L_4 = V_1; __this->___U3CUnityCoroutineTest_iU3E5__2_2 = ((int32_t)il2cpp_codegen_add(L_4, 1)); // yield return null; __this->___U3CU3E2__current_1 = NULL; Il2CppCodeGenWriteBarrier((void**)(&__this->___U3CU3E2__current_1), (void*)NULL); __this->___U3CU3E1__state_0 = 1; return (bool)1; } IL_0040: { __this->___U3CU3E1__state_0 = (-1); } IL_0047: { // while (UnityCoroutineTest_i < 10) int32_t L_5 = __this->___U3CUnityCoroutineTest_iU3E5__2_2; if ((((int32_t)L_5) < ((int32_t)((int32_t)10)))) { goto IL_0020; } } { // } return (bool)0; } } // System.Object UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::System.Collections.Generic.IEnumerator<System.Object>.get_Current() IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR RuntimeObject* U3CUnityCoroutineTestFunctionU3Ed__0_System_Collections_Generic_IEnumeratorU3CSystem_ObjectU3E_get_Current_m9ED6C30EC34DBF0ECACE9A93DE5FCDBAFF10D0C9 (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, const RuntimeMethod* method) { { RuntimeObject* L_0 = __this->___U3CU3E2__current_1; return L_0; } } // System.Void UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::System.Collections.IEnumerator.Reset() IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void U3CUnityCoroutineTestFunctionU3Ed__0_System_Collections_IEnumerator_Reset_mC0DAB409F8D496258495CE3857425D58F9848D4A (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, const RuntimeMethod* method) { { NotSupportedException_t1429765983D409BD2986508963C98D214E4EBF4A* L_0 = (NotSupportedException_t1429765983D409BD2986508963C98D214E4EBF4A*)il2cpp_codegen_object_new(((RuntimeClass*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&NotSupportedException_t1429765983D409BD2986508963C98D214E4EBF4A_il2cpp_TypeInfo_var))); NullCheck(L_0); NotSupportedException__ctor_m1398D0CDE19B36AA3DE9392879738C1EA2439CDF(L_0, NULL); IL2CPP_RAISE_MANAGED_EXCEPTION(L_0, ((RuntimeMethod*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&U3CUnityCoroutineTestFunctionU3Ed__0_System_Collections_IEnumerator_Reset_mC0DAB409F8D496258495CE3857425D58F9848D4A_RuntimeMethod_var))); } } // System.Object UnityCoroutineTest/<UnityCoroutineTestFunction>d__0::System.Collections.IEnumerator.get_Current() IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR RuntimeObject* U3CUnityCoroutineTestFunctionU3Ed__0_System_Collections_IEnumerator_get_Current_m815A583450518900F6F1FC1AC48DB29369377C91 (U3CUnityCoroutineTestFunctionU3Ed__0_t9745C4E179E3DDFF3C5734D8AC75AF3E86F14ABA* __this, const RuntimeMethod* method) { { RuntimeObject* L_0 = __this->___U3CU3E2__current_1; return L_0; } } #ifdef __clang__ #pragma clang diagnostic pop #endif
对于迭代器函数。将其编译成了类,并且实现了接口函数,那停等又是怎么实现的呢
查看movenext函数,可以发现其中通过不同的跳转实现了我们实现的逻辑,并对应返回true/false
那协程的实现就可以大致的猜到了,流程如下
unity startcoroutine 原理 上面的仅为猜想,实际上是不是这样的呢,看看源码就知道了) 没有源码可看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void CoroutineTest_Start_m7030C8C182CF1F69656573098D28E0DCEFBF1F79 (CoroutineTest_t7632EEB899E029CCDFF86C210611720ECAB096FA* __this, const RuntimeMethod* method) { static bool s_Il2CppMethodInitialized; if (!s_Il2CppMethodInitialized) { il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562_il2cpp_TypeInfo_var); s_Il2CppMethodInitialized = true; } { // coroutineTest = new UnityCoroutineTest(); UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562* L_0 = (UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562*)il2cpp_codegen_object_new(UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562_il2cpp_TypeInfo_var); NullCheck(L_0); UnityCoroutineTest__ctor_mAF28F94E6D560827424C4258926A686389211267(L_0, NULL); __this->___coroutineTest_4 = L_0; Il2CppCodeGenWriteBarrier((void**)(&__this->___coroutineTest_4), (void*)L_0); // StartCoroutine(coroutineTest.UnityCoroutineTestFunction()); UnityCoroutineTest_t1C000B5AF0E49BD3FF9C3A33861B2E804321D562* L_1 = __this->___coroutineTest_4; NullCheck(L_1); RuntimeObject* L_2; L_2 = UnityCoroutineTest_UnityCoroutineTestFunction_m5CE5BCA8CD865CB46FB4694BF146B222BBD4CC30(L_1, NULL); Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* L_3; L_3 = MonoBehaviour_StartCoroutine_m4CAFF732AA28CD3BDC5363B44A863575530EC812(__this, L_2, NULL); // } return; } } // UnityEngine.Coroutine UnityEngine.MonoBehaviour::StartCoroutine(System.Collections.IEnumerator) IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* MonoBehaviour_StartCoroutine_m4CAFF732AA28CD3BDC5363B44A863575530EC812 (MonoBehaviour_t532A11E69716D348D8AA7F854AFCBFCB8AD17F71* __this, RuntimeObject* ___0_routine, const RuntimeMethod* method) { bool V_0 = false; bool V_1 = false; Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* V_2 = NULL; { RuntimeObject* L_0 = ___0_routine; V_0 = (bool)((((RuntimeObject*)(RuntimeObject*)L_0) == ((RuntimeObject*)(RuntimeObject*)NULL))? 1 : 0); bool L_1 = V_0; if (!L_1) { goto IL_0014; } } { NullReferenceException_tBDE63A6D24569B964908408389070C6A9F5005BB* L_2 = (NullReferenceException_tBDE63A6D24569B964908408389070C6A9F5005BB*)il2cpp_codegen_object_new(((RuntimeClass*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&NullReferenceException_tBDE63A6D24569B964908408389070C6A9F5005BB_il2cpp_TypeInfo_var))); NullCheck(L_2); NullReferenceException__ctor_mA41317A57F5C1C0E3F59C7EB25ABD484564B23D4(L_2, ((String_t*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&_stringLiteral26A69F385CB916B500238120B972B54B804F7DDE)), NULL); IL2CPP_RAISE_MANAGED_EXCEPTION(L_2, ((RuntimeMethod*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&MonoBehaviour_StartCoroutine_m4CAFF732AA28CD3BDC5363B44A863575530EC812_RuntimeMethod_var))); } IL_0014: { bool L_3; //检查是否是monobehaviour L_3 = MonoBehaviour_IsObjectMonoBehaviour_mC2F75720102B56F81F3D1329BE96C2C7B336B615(__this, NULL); V_1 = (bool)((((int32_t)L_3) == ((int32_t)0))? 1 : 0); bool L_4 = V_1; if (!L_4) { goto IL_002c; } } { ArgumentException_tAD90411542A20A9C72D5CDA3A84181D8B947A263* L_5 = (ArgumentException_tAD90411542A20A9C72D5CDA3A84181D8B947A263*)il2cpp_codegen_object_new(((RuntimeClass*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&ArgumentException_tAD90411542A20A9C72D5CDA3A84181D8B947A263_il2cpp_TypeInfo_var))); NullCheck(L_5); ArgumentException__ctor_m026938A67AF9D36BB7ED27F80425D7194B514465(L_5, ((String_t*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&_stringLiteralB354FCF3A750169C4EEFC050334DD9F51BC10E0C)), NULL); IL2CPP_RAISE_MANAGED_EXCEPTION(L_5, ((RuntimeMethod*)il2cpp_codegen_initialize_runtime_metadata_inline((uintptr_t*)&MonoBehaviour_StartCoroutine_m4CAFF732AA28CD3BDC5363B44A863575530EC812_RuntimeMethod_var))); } IL_002c: { RuntimeObject* L_6 = ___0_routine; Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* L_7; L_7 = MonoBehaviour_StartCoroutineManaged2_m55C19C5C5C65E9883E12101A46F37AB1172C73E8(__this, L_6, NULL); V_2 = L_7; goto IL_0036; } IL_0036: { Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* L_8 = V_2; return L_8; } } // UnityEngine.Coroutine UnityEngine.MonoBehaviour::StartCoroutineManaged2(System.Collections.IEnumerator) IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* MonoBehaviour_StartCoroutineManaged2_m55C19C5C5C65E9883E12101A46F37AB1172C73E8 (MonoBehaviour_t532A11E69716D348D8AA7F854AFCBFCB8AD17F71* __this, RuntimeObject* ___0_enumerator, const RuntimeMethod* method) { typedef Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* (*MonoBehaviour_StartCoroutineManaged2_m55C19C5C5C65E9883E12101A46F37AB1172C73E8_ftn) (MonoBehaviour_t532A11E69716D348D8AA7F854AFCBFCB8AD17F71*, RuntimeObject*); static MonoBehaviour_StartCoroutineManaged2_m55C19C5C5C65E9883E12101A46F37AB1172C73E8_ftn _il2cpp_icall_func; if (!_il2cpp_icall_func) _il2cpp_icall_func = (MonoBehaviour_StartCoroutineManaged2_m55C19C5C5C65E9883E12101A46F37AB1172C73E8_ftn)il2cpp_codegen_resolve_icall ("UnityEngine.MonoBehaviour::StartCoroutineManaged2(System.Collections.IEnumerator)"); Coroutine_t85EA685566A254C23F3FD77AB5BDFFFF8799596B* icallRetVal = _il2cpp_icall_func(__this, ___0_enumerator); return icallRetVal; }
等价为以下部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); } [MethodImpl(MethodImplOptions.InternalCall) ] private extern Coroutine StartCoroutineManaged2 (IEnumerator enumerator ) ;
实现自己的协程 unity中常用的就是返回null 和 waitforseconds ,对于返回我们约定一个接口,返回的值都要实现这个接口,是否完成,对于null ,直接返回true,对于waitforseconds,需要运行一定时间后才会返回true
在这里我也只实现了这两个返回值,这个可以扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public interface ICoroutineYield { public bool isDone(float deltaTime); } public class YieldReturnNULL : ICoroutineYield { public YieldReturnNULL() { } public bool isDone(float deltaTime) { return true; } } public class YieldWaitForSeconds : ICoroutineYield { private float m_time; public YieldWaitForSeconds(float delayTime) { m_time = delayTime; } public bool isDone(float deltaTime) { m_time -= deltaTime; return m_time <= 0; } }
协程的更新
需要注意的点:不要在foreach循环中修改迭代器容器的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void onUpdate(float deltaTime) { if(coroutineDict.Count==0) return; var keys = new List<string>(coroutineDict.Keys); foreach(var key in keys) { var list = coroutineDict[key]; for(int i = 0; i < list.Count; i++) { var coroutine = list[i]; if (!coroutine.Current.isDone(deltaTime)) { continue; } if(!coroutine.MoveNext()) { list.RemoveAt(i); } } if (coroutineDict[key].Count == 0) coroutineDict.Remove(key); } }
code:GameProject/SomeTools/MyCoroutines at master · forestlyn/GameProject (github.com)
参考 聊一聊Unity协程背后的实现原理 - iwiniwin - 博客园 (cnblogs.com)