📜 ⬆️ ⬇️

Work with Korutins in Unity


Korutin (Coroutines, coroutines) in Unity is a simple and convenient way to run functions that should work in parallel for some time. There is nothing fundamentally difficult in working with Korutins, and the Internet is full of articles with a superficial description of their work. However, I never managed to find a single article that would describe the possibility of launching a group of Coroutines with the continuation of work after their completion.
I want to offer you a small pattern that realizes this possibility, as well as a selection of information about the korutinas.



Cortinos are simple C # iterators that return IEnumerator and use the yield keyword. In Unity, cortices are registered and executed before the first yield using the StartCoroutine method. Next, Unity polls the registered cortices after each call to Update and before calling LateUpdate, determining by the return value in the yield, when it is necessary to proceed to the next block of code.
')
There are several options for return values:

Continue after the next FixedUpdate:
yield return new WaitForFixedUpdate(); 


Continue after the next LateUpdate and scene rendering:
 yield return new WaitForEndOfFrame(); 


Continue after a while:
 yield return new WaitForSeconds(0.1f); //    100ms 


Continue on completing another quorutine:
 yield return StartCoroutine(AnotherCoroutine()); 


Continue after downloading a remote resource:
 yield return new WWW(someLink); 


All other return values ​​indicate that you must continue after passing the current iteration of the Update loop:
 yield return null; 


You can quit korutin like this:
 yield return break; 


Using WaitForSeconds creates a long-lived object in memory (a managed heap), so using it in fast loops can be a bad idea.

I already wrote that the corutines are working in parallel, it should be clarified that they do not work asynchronously, that is, they are executed in the same thread.

A simple example of korutin:

 void Start() { StartCoroutine(TestCoroutine()); } IEnumerator TestCoroutine() { while(true) { yield return null; Debug.Log(Time.deltaTime); } } 


This code runs a corute with a loop that will write to the console the time elapsed since the last frame.
You should pay attention to the fact that first return return null is called in Coroutine, and only then the log is written. In our case, it matters, because the execution of korutin starts at the moment when StartCoroutine (TestCoroutine ()) is called , and the transition to the next code block after yield return null will be done after the Update method, so that before and after the first yield return null Time. deltaTime will point to the same value.

You should also note that an endless loop corutin can still be interrupted by calling StopAllCoroutines () , StopCoroutine (“TestCoroutine”) , or destroying the parent GameObject.

Good. So with the help of corutines, we can create triggers that check certain values ​​for each frame, we can create a sequence of corortines launched one after another, for example, playing a series of animations, with different calculations at different stages. Or just run other corutins inside a quorutine without a yield return and continue execution. But how to start a group of korutinov, working in parallel, and continue only after their completion?

Of course, you can add to the class in which corutin is defined, a variable indicating the current state:

Class to move:

 public bool IsMoving = false; IEnumerator MoveCoroutine(Vector3 moveTo) { IsMoving = true; //        var iniPosition = transform.position; while (transform.position != moveTo) { //              //     yield return null; } IsMoving = false; } 


A class that works with a group of classes that need to be moved:

 IEnumetaror PerformMovingCoroutine() { //   foreach(MovableObjectScript s in objectsToMove) { //   StartCoroutine(s.MoveCoroutine(moveTo)); } bool isMoving = true; while (isMoving) { isMoving = false; Array.ForEach(objectsToMove, s => { if (s.IsMoving) isMoving = true; }); if (isMoving) yield return null; } //    } 


The “do more things” block will start to run after the MoveCoroutine corortin is completed on each object in the objectsToMove array.

Well, already interesting.
But what if we want to create a group of korutin, with the possibility to check, at any place and at any time, whether the group has completed the job?
Let's do it!

For convenience, we will do everything in the form of extension methods:

 public static class CoroutineExtension { //     < ,   > static private readonly Dictionary<string, int> Runners = new Dictionary<string, int>(); // MonoBehaviour          public static void ParallelCoroutinesGroup(this IEnumerator coroutine, MonoBehaviour parent, string groupName) { if (!Runners.ContainsKey(groupName)) Runners.Add(groupName, 0); Runners[groupName]++; parent.StartCoroutine(DoParallel(coroutine, parent, groupName)); } static IEnumerator DoParallel(IEnumerator coroutine, MonoBehaviour parent, string groupName) { yield return parent.StartCoroutine(coroutine); Runners[groupName]--; } //   ,   ,       public static bool GroupProcessing(string groupName) { return (Runners.ContainsKey(groupName) && Runners[groupName] > 0); } } 


Now it is enough to call the ParallelCoroutinesGroup method on the corints and wait until the CoroutineExtension.GroupProcessing method returns true:

 public class CoroutinesTest : MonoBehaviour { // Use this for initialization void Start() { StartCoroutine(GlobalCoroutine()); } IEnumerator GlobalCoroutine() { for (int i = 0; i < 5; i++) RegularCoroutine(i).ParallelCoroutinesGroup(this, "test"); while (CoroutineExtension.GroupProcessing("test")) yield return null; Debug.Log("Group 1 finished"); for (int i = 10; i < 15; i++) RegularCoroutine(i).ParallelCoroutinesGroup(this, "anotherTest"); while (CoroutineExtension.GroupProcessing("anotherTest")) yield return null; Debug.Log("Group 2 finished"); } IEnumerator RegularCoroutine(int id) { int iterationsCount = Random.Range(1, 5); for (int i = 1; i <= iterationsCount; i++) { yield return new WaitForSeconds(1); } Debug.Log(string.Format("{0}: Coroutine {1} finished", Time.realtimeSinceStartup, id)); } } 


Done!

Source: https://habr.com/ru/post/216185/


All Articles