Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Question I Can't Call a Coroutine from a List for Second Time??

Discussion in 'Scripting' started by makelojan_, Nov 3, 2020.

  1. makelojan_

    makelojan_

    Joined:
    Mar 21, 2020
    Posts:
    3
    I created a class and a subclass for calling coroutines with a probability and a level threshold.

    class SC
    {
    public int probability;
    public int scoreThreshold;
    }
    class SCEvent : SC
    {
    public IEnumerator spawnEvent;
    }

    And I created a list of SCEvent objects in my script, then i inserted the variables in the list in Start() method.
        List<SCEvent> SCEvents = new List<SCEvent> {};


    SCEvents.Add(new SCEvent { spawnEvent = SpawnBlockRandom(), probability = 70, scoreThreshold = 0 });
    SCEvents.Add(new SCEvent { spawnEvent = SpawnBlockUnD(), probability = 30, scoreThreshold = 10 });

    Now my problem is, when i call my coroutine like that for the first time
    StartCoroutine(SCEvents[1].spawnEvent);

    it works just perfectly but at the end of the coroutine when i call it again with the same line it just doesn't work.
    I checked the list of SCEvents for every other element and they are fine. And I tried calling the coroutine for the second time directly (like
    StartCoroutine(coroutineName())
    this), it works too. Oddly I can't call my object's coroutine element twice. What should i do?
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    IEnumerators are kind of "one-shot" objects. Once you iterate through them, they're done. They cannot be enumerated twice, and therefore cannot be used as a coroutine twice or more.

    In contrast, IEnumerable is an interface that provides a GetEnumerator() method which provides a fresh IEnumerator each time. if you want to be able to reuse your SCEvents, perhaps make them provide an IEnumerable instead of IEnumerator and call GetEnumerator() on them each time you want to start a coroutine.
     
    Bunny83 and makelojan_ like this.
  3. makelojan_

    makelojan_

    Joined:
    Mar 21, 2020
    Posts:
    3
    Thank you very much. I changed the element IEnumerator in my SCEvent object into IENumerable and used .GetEnumerator() on my new spawnEvent element when i want to start the coroutine. It solved my problem. But i still don't get it. Why this works
    StartCoroutine(functionName());
    IEnumerator functionName()
    {
    yield return new WaitForSeconds(1);
    StartCoroutine(functionName())
    }

    and this doesn't
    StartCoroutine(SCEvents[0].spawnEvent);
    IEnumerator functionName()
    {
    yield return new WaitForSeconds(1);
    StartCoroutine(SCEvents[0].spawnEvent);
    }
     
    PraetorBlue likes this.
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    Because what you are doing whenever you call
    functionName()
    is creating a new IEnumerator object. In the first example you're calling the function every time and creating a new one. In the second example you're calling the function just once and storing the result in an array. Therefore when you read the value from the array you're trying to reuse the same IEnumerator object instead of creating a new one.
     
    Bunny83 and makelojan_ like this.
  5. makelojan_

    makelojan_

    Joined:
    Mar 21, 2020
    Posts:
    3
    Oh ok, I get it now. If I want to use them more than once, I shouldn't assign them to a variable or store them in an array, or else I should use IEnumerable instead of IEnumerator. I'm just discovering coroutines and I was clueless before you came. So thanks a lot again 0:)
     
    PraetorBlue likes this.
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,539
    I've written a coroutine crash course recently which may give some more insight in what the yield keyword actually does, how it's related to the interfaces IEnumerator / IEnumerable and how Unity uses them to implement coroutines.
     
    PraetorBlue likes this.
  7. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    As PraetorBlue said. IEnumerators are reseteable at Coroutines. Each time you call IEnumerator.MoveNext() (i.e. each time unity execute some code from the coroutine), it's been "disordered" and unity can't come back from.
    You can instead of create the method as an IEnumerator, create it as an IEnumerable in order to "save" the 1st state.
    And you start your Coroutine like this
    StartCoroutine(YourIEnumerableCoroutine().GetEnumerator());


    Anywy, I've written an special Coroutine asset which allows you to pause and restart/reuse coroutines based on the example above but still it's not uploaded to the Asset Store.
    Btw at this GitHub repo you've the source code and a package to install the full asset with a documentation an examples.
    And here you have the documentation and some tutorials... I've tried to make it intuitive since uses the same yield return syntax than standard Coroutines. I hope your feedback if you use it.