Search code examples
c#unity-game-engineclosuresfunctorcoroutine

Clarification using closures in IEnumerator coroutines


I have 2 IEnumerator coroutines that are similar enough that they should be combined:

A:

IEnumerator CountDown_A() {
  timeLeft_A = totalTime_A;
  while (timeLeft_A > 0) {
    yield return new WaitForSeconds(1); 
    timeLeft_A--;
  }
}

B:

IEnumerator CountDown_B() {
  timeLeft_B = totalTime_B;
  while (timeLeft_B > 0) {
    yield return new WaitForSeconds(1); 
    timeLeft_B--;
  }
}

The only 2 differences are the variables totalTime_A vs totalTime_B and timeLeft_A vs timeLeft_B. These vars are from outside the scope of this function.

The problem I'm having modularizing this coroutine is that the incremented value of timeLeft_A and timeLeft_B needs to apply outside this function, so I need to pass a reference to them somehow.

User "Kurt-Dekker" posted a great solution in this thread but I'm having trouble applying it to my code. He says to "use a closure (functor) to allow the coroutine to modify it by callback":

IEnumerator CountDown( System.Action<int> callback){
    ....
}

which I think would be called like so:

StartCoroutine ( CountDown( (i) => { timeLeft_A = i; } ) );
StartCoroutine ( CountDown( (i) => { timeLeft_B = i; } ) );

What I don't understand is how to then reference/modify the actual value of the int being passed in, inside the IEnumerator, if all I have to work with is a callback function. For example to do the following:

while(callback > 0){

or:

callback--;

Any help appreciated.


Solution

  • I think this may answer your question, on how to use the System.Action<float> within a coroutine.

    Basicaly, when you call StartCoroutine you give the maximum time for you counter as a normal parameter and a callback, this callback takes a float as argument (here it is i).

    If you call callback(--timeLeft); in your coroutine, it will execute the System.Action you passed in.

    Here it will set the timeLeft_A or timeLeft_B to the timeLeft variable of the corresponding coroutine.

    public float timeLeft_A = 0f;
    public float timeLeft_B = 0f;
    
    public float totalTime_A = 15f;
    public float totalTime_B = 20f;
    
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(Cooldown(totalTime_A, (i) =>
        {
            timeLeft_A = i;
        }));
    
        StartCoroutine(Cooldown(totalTime_B, (i) =>
        {
            timeLeft_B = i;
        }));
    }
    
    IEnumerator Cooldown(float totalTime, Action<float> callback)
    {
        var timeLeft = totalTime;
        while (timeLeft > 0)
        {
            yield return new WaitForSeconds(1);
            callback(--timeLeft);
        }
    }