Sequental workflow coroutine

What is the best way of achieving multiple tasks, frame by frame, one after another with coroutine or with anything else?

StartCoroutine(c1);
StartCoroutine(c2);
StartCoroutine(c3);
...........................................

These will start all of them(c1, c2, c3) simultaneously. But I want c2 to start only after c1. So a chain of tasks forms like c1->c2->c3 such. I will DO NOT want to chain them inside coroutine:

...inside c1
yield return null;
StartCoroutine(c2);

Now I have found a way:

StartCoroutine(SequenceStart());

IEnumerator SequenceStart()
    {
        yield return StartCoroutine(c1);
        yield return StartCoroutine(c2);
        yield return StartCoroutine(c3);
    }

However are there some other more elegant ways of doing this? I would love to know. :slight_smile:

1 Like

IMHO, I think this way is already fairly elegant. (Not in all ways but some)
if you could get c# async to work then you could use them but it would just get a similar result anyway.

Yes:

StartCoroutine((new[] {
    Cr1(),
    Cr2(),
    Cr3()
}).GetEnumerator());

The array gets allocated, though, so it’s not free. I tried some tricks replacing the array with a call to a method with params, but that just increased the allocation, so I don’t think there’s a way to do a generic “run these coroutines in order” method without allocating something. So it’s probably best to stick to just writing them out like you’re already doing.

1 Like

I like to use behaviour trees for implementing any process that runs over several frames, including task sequences. I’ve made a little scripting framework based on behaviour tree (see Panda BT), it could become an alternative to coroutines for you.

First, you need to implement some “tasks” (e.g: Task1, Task2 and Task3). They are just functions (no coroutines) that get called at the right time by the framework, and will be running on each frame as long as they are doing something:

using UnityEngine;
using Panda;

public class SomeMonoBehaviour : MonoBehaviour
{
    [Task]
    void Task1()
    {
        if (Task.current.isStarting)
        {
            // Use this for initalization
        }

        //Do some work here on every frame.

        if ( /*Some ending condition*/ )
        {
            Task.current.Complete( /*Either fail (false) or succeed (true)*/ );
        }
    }
 
    [Task]
    void Task2()
    {
        // ...
    }
 
    [Task]
    void Task3()
    {
        // ...
    }
}

Then you just define your sequence in a BT script:

sequence
    Task1
    Task2
    Task3

Note that you can do more than just sequences with behaviour trees:

  • Organizing your tasks into hierarchies
  • Running tasks under guard conditions
  • Running tasks in parallel
  • Conditional branching
  • Alternative running (fallback to other tasks in case of failure)
  • Repetitions
  • …

Trying to implement anything from the above using coroutines, would en up pretty soon into an unmanageable nested-coroutines-hell. Whereas with this framework your process is described in a straight forward little script. Furthermore, the execution of the script can be visualized at runtime, so you can live-inspect at a glance exactly what’s going on (which is valuable for debugging).

Async and await are not available in Unity, are they? Or am I missing something?

They arent. but I assume you have seen the thread which tries to add newer features to Unity. I believe they have it working on that.

EDIT:
I think this may be it:
https://bitbucket.org/alexzzzz/unity-c-5.0-and-6.0-integration/overview

EDIT # 2:
“Namely, you can’t use async/await,”
I dont remember then but I swear I saw it somewhere.

We are still on .Net 3.5 (aproximately, it’s really a bastard version of mono. But in most cases it behaves like .Net 3.5). So newer language features like await, async and dynamic don’t work.

There are plans to upgrade at some point in the indefinite future.

I saw this thread recently. it seems to be pretty experimental but it sounds promising.
https://forum.unity3d.com/threads/c-6-0.314297/

Async/await would be a great addition. It would basically render Couroutine as deprecated (which is great imho). Turning a coroutine (for instance the one provided in the coroutine documenation) into an asynchronous method is straight forward:

IEnumerator Fade()
{
    for (float f = 1f; f >= 0; f -= 0.1f)
    {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

Using async/await, that coroutine would become:

async Task Fade()
{
    for (float f = 1f; f >= 0; f -= 0.1f)
    {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        await NextUpdate(); // BTW that won't allocate GC, contrary to yield return new WaitForSomething ...
    }
}

Then, we would use it, like this from Update.

async void Update()
{
    if (Input.GetKeyDown("f"))
    {
        await Fade();
    }
}

That would be really neat!

2 Likes

Wow interesting! I will check on it, thanks you so much :slight_smile:

I would second behaviour trees as being good for complex, sequential logic. And I’d recommend trying out PandaBehaviour.

It wouldn’t be that simple. Async and Await are both functions for dealing with different threads. So it would require UnityEngine to support multi threading. Which it doesn’t.

Coroutines have the advantage of being asynchronous but on the same thread, in a way that’s not possible with await and async.

2 Likes

At a first glance, It’s easy to assume that async and await are some new multi-threading facilities. But they are not. Asynchronous methods run on the same thread. Of course, you could add multi-threading into the sauce, but that’s not mandatory. Using async/await is very similar to using call-back methods. In fact they have been created as syntactic sugar to decrease usage of callbacks (as well as avoiding usage of thread as a solution to avoid blocking function calls). But behind the curtains the compiler will turn an asynchronous method into a state machine and callbacks without involving new threads.

So, the UnityEngine does not have to be thread-safe to support async and await.

I stand corrected then. They would be useful. Since they can’t be used in Unity (without hoop jumping), I’d never bothered to dig into the details.

Hi. I found this thread by google. I made it yesterday.
if you have suggestion, leave message.

if you use this, code change below.

CoroutineChain.Start
         .Sequencial(c1,c2,c3);

//or
CoroutineChain.Start
         .Play(c1)
         .Play(c2)
         .Play(c3);

Interesting approach! But I see you used unity’s co-routine under the hood. Due to garbage collection, I don’t think I can afford this right now. :frowning:

For anyone interested, I took a deeper dive into the topic of replacing coroutines with async-await and wrote about it here

I’ll just leave it here…

public static IEnumerator InvokeChainCallbacksResult(params Func<float, bool>[] callbacks)
        {
            float t = 0;
            int ic = 0;
            while (true)
            {
                t += Time.deltaTime;
                if (callbacks[ic](t) && callbacks.Length <= ++ic) break;
                yield return null;
            }
        }

       
        StartCoroutine(InvokeChainCallbacksResult(
                        (float t) =>{ Debug.LogError("1"); return true; },
                        (float t) =>{Debug.LogError("2"); return true;}
                      ));