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

OK to use recursive function with IEnumerator?

Discussion in 'Scripting' started by Zaffer, Aug 21, 2012.

  1. Zaffer

    Zaffer

    Joined:
    Oct 21, 2010
    Posts:
    266
    Hi,

    I wrote a script that will play random animations attached to the same object (actually only 2 different animations at this point) and then pause for random seconds. I tried to do this using InvokeRepeating() but you can't pass in parameters to the function you are repeating that way. Here's what I came up with:
    Code (csharp):
    1. #pragma strict
    2.  
    3. private var windZ : GameObject;
    4. private var treeWind : String;
    5. private var treeWind_sm : String;
    6. private var treeWindLength : float;
    7. private var treeWind_smLength : float;
    8.  
    9. private var lengthOfBlow : float;
    10. private var ranSecs : int;
    11.  
    12. function Start () {
    13.     windZ = gameObject.Find("WindZone");
    14.     windZ.active = true;
    15.    
    16.     treeWind = "TreeWind";
    17.     treeWind_sm = "TreeWind_sm";
    18.    
    19.     treeWindLength = animation["TreeWind"].clip.length;
    20.     treeWind_smLength = animation["TreeWind_sm"].clip.length;
    21.    
    22.     lengthOfBlow = treeWind_smLength;
    23.     ranSecs = 5;
    24.     DoWind();
    25. }
    26.  
    27. function DoWind(): IEnumerator{// Don't know how the IEnumerator thing works -- just does
    28.                                 // allows recursion -- console comments gave it to me
    29.     if(lengthOfBlow == treeWindLength){
    30.         windZ.animation.Play(treeWind);
    31.         Debug.Log("treeWind");
    32.         Debug.Log(ranSecs);
    33.         yield WaitForSeconds(treeWindLength + ranSecs);
    34.        
    35.     }
    36.     if(lengthOfBlow == treeWind_smLength){
    37.         windZ.animation.Play(treeWind_sm);
    38.         Debug.Log("treeWind_sm");
    39.         Debug.Log(ranSecs);
    40.         yield WaitForSeconds(treeWind_smLength + ranSecs);
    41.     }
    42.    
    43.     var forMakingRandom : int = randomRangeInt(0, 2);
    44.     switch(forMakingRandom){
    45.         case 0:
    46.         lengthOfBlow = treeWindLength;
    47.         break;
    48.        
    49.         case 1:
    50.         lengthOfBlow = treeWind_smLength;
    51.         break;
    52.     }
    53.     ranSecs = randomRangeInt(5, 21);
    54.     DoWind();
    55. }
    56.  
    57. function randomRangeInt(min:int, max:int):int {
    58.     var randomNumInt : int = Random.Range(min, max);
    59.     return randomNumInt;    
    60. }
    61.  
    Some error messages from the console gave me the hint to make the DoWind() function typed as IEnumerator and that would allow it to work as a recursive function -- which it did. It works very well. What bothers me is that I don't understand how it works or why typing a function a IEnumerator will magically allow it to work as a recursive function. Is there a better or "healthier" way to do this? Is the IEnumerator thing a permanent part of Unity/JavaScript? Is it likely/not likely to be deprecated in the future? Thanks.

    Zaffer
     
  2. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    IEnumerator is a core mono feature. It is the technology that coroutines (functions with yield in it) are built upon. Unity's JavaScript tries to hide the technical details from the user, but apparently there are cases where it can no longer hide all the details from you. Any function with yield is always of type IEnumerator, it's just that your specific use is complex enough that you're forced to explicitly define it.
     
  3. andererandre

    andererandre

    Joined:
    Dec 17, 2010
    Posts:
    683
    I'm not entirely sure how yield is implemented, but your recursion looks like it causes a memory leak when your coroutine runs for a very long time. It looks to me like yield has to store the current position in the method to be able to continue execution at this point when the next item from the enumerator is being requested.

    With recursion your stack rapidly increases in size, especially if you pass a lot of parameters to the method or if your method creates a lot of local variables. Every recursive method call has some memory overhead on the stack, unless your compiler automatically optimizes tail recursion. So what happens if you make this recursion for a very long time? The memory overhead of every recursive call adds up.

    I would prefer writing it as an infiinite loop, e.g. while (true) { ... }
     
  4. Zaffer

    Zaffer

    Joined:
    Oct 21, 2010
    Posts:
    266
    Hi Tomvds and Plzdiekthxbye,

    Thanks for the clear explanations. I didn't know about while(true). Looked it up and apparently it's OK to set up an infinite loop as long as it has a yield statement in it -- won't crash. So I tucked my whole DoWind() function into while(true) {...} and it worked beautifully! Thanks for pointing me away from the "dark arts" of hidden core features.

    Zaffer