您现在的位置是:首页 >技术杂谈 >更有效的协同程序【插件:More Effective Coroutines】网站首页技术杂谈

更有效的协同程序【插件:More Effective Coroutines】

Edision_li 2024-10-17 00:01:02
简介更有效的协同程序【插件:More Effective Coroutines】

插件地址:传送门

1、命名空间

using System.Collections.Generic;

using MEC;

2、与传统的协程相比

传统:StartCoroutine(_CheckForWin());

被RunCoroutine取代。必须选择执行循环进程,默认为“Segment.Update”。
 


using System.Collections.Generic;
using UnityEngine;
using MEC;

public class Test : MonoBehaviour
{
    
    void Start()
    {
        StartCoroutine(_CheckForWin());

        // To run in the Update segment:
        Timing.RunCoroutine(_CheckForWin());
        // To run in the FixedUpdate segment:
        Timing.RunCoroutine(_CheckForWin(), Segment.FixedUpdate);
        // To run in the LateUpdate segment:
        Timing.RunCoroutine(_CheckForWin(), Segment.LateUpdate);
        // To run in the SlowUpdate segment:
        Timing.RunCoroutine(_CheckForWin(), Segment.SlowUpdate);
    }

    IEnumerator<float> _CheckForWin()
    {
        yield return Timing.WaitForSeconds(1);
        Debug.Log("****");
    }
}

3、协程的退出/停止  CancelWith

相当于StopCoroutine,CancelWith确实会增加所有协程生成的不可避免的GC分配的大小大约20个字节。20字节并不大,但是如果您想避免所有可能的GC分配,那么你就可以相对容易地做CancelWith所做的事情,而不用使用这个函数。只是确保在协程中的每个yield return语句之后都做以下检查:if(gameObject != null && gameObject.activeInHierarchy)

public class Cube01 : MonoBehaviour
{

    private void Awake()
    {
       Timing.RunCoroutine(_moveMyButton().CancelWith(gameObject));
    }

    IEnumerator<float> _moveMyButton()
    {
        while (true)
        {
            yield return Timing.WaitForSeconds(1f);
            Debug.Log("********");
        }

    }
}

4、停止和回复协程

MEC协程可以暂停,稍后再恢复。Unity的协程不会这样做,但是概念很简单。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MEC;

public class Cube01 : MonoBehaviour
{
    private CoroutineHandle handleToACoroutine;

    private void Awake()
    {
        handleToACoroutine= Timing.RunCoroutine(_moveMyButton().CancelWith(gameObject));
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.P))
        {
            Timing.PauseCoroutines(handleToACoroutine);
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            Timing.ResumeCoroutines(handleToACoroutine);
        }
    }

    IEnumerator<float> _moveMyButton()
    {
        while (true)
        {
            yield return Timing.WaitForSeconds(1f);
            Debug.Log("********");
        }

    }
}

5、等待直到完成

Unity的默认协同程序有几种情况,你可以产生返回一些变量。例如,你可以“yield return asyncOperation;”。MEC也有这样的功能函数称为WaitUntilDone。

        yield return Timing.WaitUntilDone(wwwObject);
        yield return Timing.WaitUntilDone(asyncOperation);
        yield return Timing.WaitUntilDone(customYieldInstruction);
        // With MEC Pro you can do a little more with WaitUntilDone:
        yield return Timing.WaitUntilDone(newCoroutine);
        // 上面的代码自动启动一个新的协程,并保存当前的协程。
        yield return
        Timing.WaitUntilTrue(functionDelegateThatReturnsBool);  //免费版没有
        yield return
        Timing.WaitUntilFalse(functionDelegateThatReturnsBool); //免费版没有

6、慢更新(slow update)

Unity的协程没有慢更新循环的概念,但MEC的协程有。
慢更新循环(默认情况下)每秒运行7次。它使用绝对时间刻度,所以当你减慢Unity的时间刻度时,它不会减慢SlowUpdate。使用SlowUpdate和总是使用“yield returnTiming.WaitForSeconds(1f/7f);”有两个主要区别。第一个是绝对时间尺度,第二个是所有的SlowUpdate节拍同时发生。

    private void Awake()
    {
        Timing.RunCoroutine(_UpdateTime(), Segment.SlowUpdate);
    }

    private float clock;
    private IEnumerator<float> _UpdateTime()
    {
        while (true)
        {
            //当前段已运行的时间(以秒为单位)。
            clock = Timing.LocalTime;
            yield return 0f;
        }
    }

SlowUpdate还可以很好地检查临时调试变量。例如,如果重建项目需要很长时间,您可以在脚本中设置一个公共bool值,以重置该脚本上的值。您需要定期检查该bool值是否已设置为true,执行该检查的最佳时间是在SlowUpdate上。当用户选中复选框时,它会感觉它立即响应,但它将在你的应用程序中使用更少的处理每1/7秒检查一次,而不是每秒30 - 100次(取决于你的帧率)。

注意:Unity的Time. deltatime变量在SlowUpdate中不会返回正确的值,因为Unity的Time类对这个片段一无所知。可以使用Timing.DeltaTime代替。

您还可以更改SlowUpdate运行的速率。例如:

Timing.Instance.TimeBetweenSlowUpdateCalls = 3f;

上面这行代码将使SlowUpdate每3秒只运行一次。

7、标签【Tag】

在启动协程时,可以选择是否提供标记。标记是标识该协程的字符串。当您标记一个协程或一组协程时,您可以稍后使用KillCoroutine(标签)或KillAllCoroutines(标签)杀死该协程或该组。【API可能会变,可以查看相关API调用】


using System.Collections.Generic;
using UnityEngine;
using MEC;

public class Test : MonoBehaviour
{
    void Start()
    {
        Timing.RunCoroutine(_shout(1, "Hello"), "shout");
        Timing.RunCoroutine(_shout(2, "World!"), "shout");
        Timing.RunCoroutine(_shout(3, "I"), "shout2");
        Timing.RunCoroutine(_shout(4, "Like"), "shout2");
        Timing.RunCoroutine(_shout(5, "Cake!"), "shout2");
        Timing.RunCoroutine(_shout(6, "Bake"), "shout3");
        Timing.RunCoroutine(_shout(7, "Me"), "shout3");
        Timing.RunCoroutine(_shout(8, "Cake!"), "shout3");
        Debug.Log("Killed " + Timing.KillCoroutines("shout2"));
 
    }
    IEnumerator<float> _shout(float time, string text)
    {
        yield return Timing.WaitForSeconds(time);
        Debug.Log(text);
    }
}

 8、LocalTime and DeltaTime

Unity的Time. deltatime变量在SlowUpdate中不会返回正确的值,因为Unity的Time类对这个片段一无所知。可以使用Timing.DeltaTime代替。Unity中的默认Time类在大多数情况下都可以正常工作,但在SlowUpdate中无法正常工作。

9、其它的功能【Additional Functionality】

Timing对象中还包含三个辅助函数:CallDelayed、callcontinuous和callperiodic。

(1)CallDelayed: 在一定秒数后调用指定的动作。

(2)CallContinously:每帧连续调用动作,持续数秒。

(3)CallPeriodically:周期性地每隔“x”秒调用动作,持续数秒。

这三种功能都可以很容易地使用协程创建,但是这个基本功能最终被忽略了由于使用频繁,我们将其包含在基本模块中。


using System.Collections.Generic;
using UnityEngine;
using MEC;

public class Test : MonoBehaviour
{

    private void Start()
    {
        // 2秒后启动_RunFor5Seconds。
        Timing.CallDelayed(2f, delegate
        {
            Timing.RunCoroutine(_RunFor5Seconds(Timing.RunCoroutine(Test01())));
        });

        // 使物体以每秒一个世界单位的速度向前推进,持续4秒。
        Timing.CallContinuously(4f, delegate {PushOnGameObject(Vector3.forward);}, 
            Segment.FixedUpdate);

        // 尽量不要对callcontinuous进行闭包是非常重要的,因为这将导致每帧都进行GC分配。
        Timing.CallContinuously<Vector3>(Vector3.forward, 4f,
         vector => PushOnGameObject(vector), Segment.FixedUpdate);

    }

    private void PushOnGameObject(Vector3 amount)
    {
        transform.position += amount * Time.deltaTime;
    }


    private IEnumerator<float> _RunFor5Seconds(CoroutineHandle waitHandle)
    {
        Debug.Log("Yielding 5s..");
        yield return Timing.WaitUntilDone(waitHandle);
        Debug.Log("Starting 5 second run.");
        yield return Timing.WaitForSeconds(5f);
        Debug.Log("Finished 5 second run.");
    }

    private IEnumerator<float> Test01()
    {
        yield return Timing.WaitForOneFrame;
        Debug.Log("********");
    }
}

10、链式结构 【Fluid Architecture】

// 常规

Timing.RunCoroutine(_Foo().CancelWith(gameObject));

// MEC的链式

_Foo().CancelWith(gameObject).RunCoroutine();

请记住:与运行协程的其他方式一样,如果您忘记使用RunCoroutine,编译器不会报错,但它不会执行协程。当对Run的调用位于行尾时,这个事实可能有点难以记住。还要记住,这种流畅的语法可能会让其他习惯使用Unity默认协同程序的开发人员感到困惑,因为Unity的默认协同程序API不支持这种语法。

QA:

问:MEC有WaitForEndOfFrame函数吗?
它没有在MEC Free中实现,但MEC Pro有一个部分。
注意:关于WaitForEndOfFrame的实际作用有一些混淆。当你只是想屈服到下一帧时,WaitForEndOfFrame不是一个理想的命令,最好使用“yield return null;”。许多人在使用Unity的协程时使用WaitForEndOfFrame,因为这是他们在Unity的默认协程中最接近WaitForOneFrame的东西,他们没有意识到这可能会导致微妙的问题。MEC定义了常数Timing。WaitForOneFrame,所以在MEC中,如果你愿意,你可以使用显式变量名,而不会产生使用EndOfFrame可能导致的视觉故障和性能下降的可能性。

问:MEC有StopCoroutine的功能吗?
是的。它叫做Timing.KillCoroutines()。它可以接受前一个Timing返回的协程的句柄。RunCoroutine命令,或者它可以接受一个标记。
注意:KillCoroutine用于从不同的函数中停止协程函数。如果你想从协程的函数内部结束协程,那么最好的命令是“yieldbreak;”,这相当于在任何其他函数中调用“return;”。yield break更好的原因是因为KillCoroutine命令不能结束当前正在运行的协程函数,因此该函数将继续执行,直到下一个yield命令,但yield break没有这个问题。

问:MEC有StopAllCoroutines函数吗?
是的。Timing.KillCorutines()。如果你想暂时停止一切,你也可以使用Timing.PauseCorutines()和Timing.ResumeAllCorutines()。

问:MEC是否有一个函数使一个协程在另一个协程完成之前生成?
是的。在你想要持有的协程内部,你调用"yield return Timing.WaitUntilDone(coroutineHandle);"无论何时调用Timing.RunCoroutine,都会返回句柄。

问:MEC完全删除GC分配吗?
答:不是。MEC删除所有逐帧GC分配。(除非你在协程内部的堆上分配内存,但MEC无法控制这一点。)当协程第一次创建时,函数指针和传递给它的任何变量都被放在堆上,最终必须由垃圾收集器清理。这种不可避免的分配发生在Unity的协程和MEC协程中。MEC协程平均分配的垃圾确实比Unity协程少。

问:MEC协程总是比Unity协程内存效率更高吗,还是只有在特定情况下才如此?
答:MEC协程在所有情况下都比Unity协程产生更少的GC分配,除非你分配大字符串并将其作为协程的标记

案例:

void Start ()
 {
 CoroutineHandle handle = 
Timing.RunCoroutine(_RunFor10Seconds());
 handle = Timing.RunCoroutine(_RunFor1Second(handle));
 Timing.RunCoroutine(_RunFor5Seconds(handle));
 }
 private IEnumerator<float> _RunFor10Seconds()
 {
 Debug.Log("Starting 10 second run.");
 
 yield return Timing.WaitForSeconds(10f);
 Debug.Log("Finished 10 second run.");
 }
 private IEnumerator<float> _RunFor1Second(CoroutineHandle
waitHandle)
 {
 Debug.Log("Yielding 1s..");
 yield return Timing.WaitUntilDone(waitHandle);
 Debug.Log("Starting 1 second run.");
 yield return Timing.WaitForSeconds(1f);
 Debug.Log("Finished 1 second run.");
 }
 private IEnumerator<float> _RunFor5Seconds(CoroutineHandle
waitHandle)
 {
 Debug.Log("Yielding 5s..");
 yield return Timing.WaitUntilDone(waitHandle);
 Debug.Log("Starting 5 second run.");
 yield return Timing.WaitForSeconds(5f);
 Debug.Log("Finished 5 second run.");
 }

 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。