unity 协程原理

unity 协程原理

Unity 开发常用到协程Coroutine,但是unity的协程和monobehaviour绑定,有时候并不想继承mono,但是又想使用协程,这时候就有点麻烦,不如来学习协程原理来自己写一个吧(

协程

下面是一个简单的协程,我们可以看到下面除了常用语法,还有两个相对比较陌生的东西IEnumerator和yield

public class UnityCoroutineTest
{
    public IEnumerator UnityCoroutineTestFunction()
    {
        int UnityCoroutineTest_i = 0;
        while (UnityCoroutineTest_i < 10)
        {
            UnityCoroutineTest_i++;
            yield return null;
        }
    }
}

IEnumerator 迭代器

IEnumerator定义,一个接口,实现了下面的函数

namespace System.Collections
{
    public interface IEnumerator
    {
        //指向当前物体
        object Current { get; }
		//移向下一个
        bool MoveNext();
        //重置
        void Reset();
    }
}

谈到了IEnumerator,顺便谈谈IEnumerable,可能看上去比较陌生,但其实我们常用的foreach遍历集合数组,背后就是IEnumerable在帮助我们,GetEnumerator获得迭代器,然后用迭代器进行遍历

namespace System.Collections
{
    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }
}

直接看官方文档使用案例,官方文档IEnumerator 接口 (System.Collections) | Microsoft Learn

void Start()
{
    coroutineTest = new UnityCoroutineTest();
    IEnumerator enumerator = coroutineTest.UnityCoroutineTestFunction();
    while (enumerator != null)
    {
        if(enumerator.MoveNext())
        {
            Debug.Log(enumerator.Current);
        }
        else
        {
            break;
        }
    }
}

image-20231208104425008

yield

根据官方文档来看,实现IEnemerator/IEnumerable非常麻烦,需要实现接口类

但是我们的协程函数,并没有继承IEnumerator,但为什么还是可以使用对应的方法?

关键就是yield,直接看官方文档解释

yield 语句 - 在迭代器中提供下一个元素 - C# | Microsoft Learn

总结就是yield是C#的关键字,其实就是快速定义迭代器的语法糖。

协程原理

上面我们知道了yield IEnumerator

其实我们的协程函数就是一个迭代器函数,那我们为什么可以使用yield return null停住函数到下一帧执行,或者使用yield return new WaitForSeconds等待几秒继续执行呢

利用Unity的IL2CPP,将c#编译为c++ 截取部分

// 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

那协程的实现就可以大致的猜到了,流程如下

image-20231208115153590

unity startcoroutine 原理

上面的仅为猜想,实际上是不是这样的呢,看看源码就知道了) 没有源码可看

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;
}

等价为以下部分

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

在这里我也只实现了这两个返回值,这个可以扩展

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循环中修改迭代器容器的值

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);
    }
}

image-20231209185855922

个人博客文章

参考

聊一聊Unity协程背后的实现原理 - iwiniwin - 博客园 (cnblogs.com)

猜你喜欢

转载自blog.csdn.net/m0_54165465/article/details/134960753