Search Unity

Question Why would couroutine.Current == null?

Discussion in 'Scripting' started by Brady, May 31, 2022.

  1. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Any idea where code like the following would result in the .Current property being null at the point indicated in the sample code? It seems to me like it should always be a boolean, since it would either hit one of the cases, return a boolean and break, or else not satisfy any case and hit the "yield return false" line at the bottom. So in no circumstance should it be null after yielding the routine. What am I missing?

    Code (CSharp):
    1. public IEnumerator MyCoroutine(int value)
    2. {
    3.    switch (value)
    4.    {
    5.       case 0: yield return false; yield break;
    6.       case 1: yield return true; yield break;
    7.    }
    8.  
    9.    yield return false;
    10. }
    11.  
    12. ...
    13.  
    14. public IEnumerator MyCaller(int someValue)
    15. {
    16.    var routine = MyCoroutine(someValue);
    17.    yield return routine;
    18.  
    19.    routine.Current <-- this now equals null instead of a bool
    20. }
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,000
    I'm not sure you understand how coroutines and generator methods in general work. When you do:

    Code (CSharp):
    1.  yield return routine;
    Your outer coroutine (MyCaller) will wait until that coroutine has finished. Once a coroutine / IEnumerator has finished the Current value is not longer valid / defined. The IEnumerator interface in general works by calling MoveNext and if that method returns true, the iterator has moved to the next element and that element is available through the Current property. However when MoveNext returns false it means the enumerator has reached the end and no further "elements" are available.

    As I said, starting a coroutine means you hand the IEnumerator instance to the Unity coroutine scheduler which will automaticlaly continue the enumerator (calling MoveNext) and based on the yielded value decides when to re-schedule this coroutine. Unity does this until MoveNext returns false and at this point the coroutine has finished. Finished means it eather reached the very end of the coroutine or you called "yield break;" inside the coroutine.

    So your MyCaller would continue when your "routine" has finished so there is no valid Current element. The actual behaviour of the Current property is an implementation detail any may vary from implementation to implementation. When I test your code in Unity 2019.4.19f1 I actually see the last yielded value. However that is not guaranteed. This depends on how the compiler constructs the internal statemachine for your generator method.

    If you have trouble following what I just said, I've written this coroutine crash course which has an example and explains the compiler magic behind the yield keyword.

    Your coroutine could be decomposed into something like this

    Code (CSharp):
    1. int state = 0;
    2. inf value;
    3. public bool MoveNext()
    4. {
    5.     if (state == 0)
    6.     {
    7.         switch(value)
    8.         {
    9.             case 0:
    10.                 Current = false;
    11.                 state = 1;
    12.                 return true;
    13.             case 1:
    14.                 Current = true;
    15.                 state = 1;
    16.                 return true;
    17.         }
    18.         Current = false;
    19.         state = 1;
    20.         return true;
    21.     }    else if (state == 1)
    22.     {
    23.         Current = null;
    24.         return false;
    25.     }
    26. }
    However other implementations may have a final step like this:

    Code (CSharp):
    1.     else if (state == 1)
    2.     {
    3.         return false;
    4.     }
    5.  
    At least here in my test project I do see the last yielded value. But as I said, that's not something you should rely on. The Current property of a statemachine object created by a generator method is only valid / defined when MoveNext returns true.

    A good counterexample (which is not a generator method which used yield but is simply the struct Enumerator object that is used by the generic List class) is the List Enumerator. Once you reached the end of the enumeration MoveNext specifically sets current to null (default(T)) and returns false since no more elements are available. As I said what "Current" may return once MoveNext returns false is simply undefined and purely depends on the implementation.

    So while I can not reproduce your result, it's not something you should rely on in the first place. Read the remarks section of the Current property of the IEnumerator interface.

     
    spiney199 and GroZZleR like this.