Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Coroutines are badass

Discussion in 'iOS and tvOS' started by n0mad, Aug 22, 2009.

  1. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Why are people always using the complicated way or the lazy way if the clean way is just 1 line longer ...

    Code (csharp):
    1. foreach(ArrowShot arrowShot in arrowList)
    2. {
    3.     //do stuff with arrowShot
    4. }
    5.  
    6. //assume same is true for "for( in )" in JS
    just becomes

    Code (csharp):
    1.  
    2. ArrowShot arrowShow = null;
    3. foreach(arrowShot in arrowList)
    4. {
    5.     //do stuff with arrowShot
    6. }
    7.  
    8. //assume same is true for "for( in )" in JS

    General rule of thumbs: declare stuff deep down the line as needed so its not created if not needed but declare it far away out that it is not recreated more than once.
    Anything that comes after for / foreach and before the corresponding } is going to be recreated over and over
     
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Because your way is wrong and does not work. ;)

    Code (csharp):
    1. ArrowShot arrowShot = null;
    2. foreach(arrowShot in arrowList)
    3. {
    4.     // This fails with a "Type and identifier are both required in a foreach statement" error
    5. }
    Yes, it's the same...the loop variable is local in scope to the loop ONLY and cannot be declared outside it.

    --Eric
     
  3. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    The comment below the code was just copied.


    And it seems like you are right :oops:
    And I was so sure I had similar code working already in own code I wrote, because the "local scope" declaration is something I would never do, neither on the desktop nor the iphone
     
  4. kenlem

    kenlem

    Joined:
    Oct 16, 2008
    Posts:
    1,630
    How would you remove the foreach when enumerating through child transforms?

    Code (csharp):
    1. foreach (Transform xform in root)
    2. {
    3.    ...
    4. }

    would become


    Code (csharp):
    1. Transform xform;
    2. int index;
    3. for (index=0; index<root.childCount); index++) 
    4. {
    5.    xform = root.???;
    6.    ...
    7. }
     
  5. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732

    I'm using a foreach too, unfortunately. But I try to avoid searching like that on runtime and prefer to index what interests me on Awake().
     
  6. kenlem

    kenlem

    Joined:
    Oct 16, 2008
    Posts:
    1,630
    I use the foreach when saving a level. I don't know another way to get all the objects under a tranform that includes hierarchy.
     
  7. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Yup, I guess there isn't any unlooped way to save such data :?

    I hope I'm wrong.
     
  8. SteveB

    SteveB

    Joined:
    Jan 17, 2009
    Posts:
    1,451
    Just putting my two (albeit ignorant) cents here kenlem...

    ...but unless you're constantly saving that data, wouldn't it otherwise just be at checkpoints, menu saves or application quits? In this case who cares about using foreach? Keep in mind I was following this thread for the sake of my education so my knowledge is limited in this matter! :D
     
  9. kenlem

    kenlem

    Joined:
    Oct 16, 2008
    Posts:
    1,630
    Normally it would but I'm working on a persistent world that gets saved and loaded from disk as the player moves around the world. It's probably not going to be that big a deal. I expect loads and saves to take place every few minutes.
     
  10. Deleted User

    Deleted User

    Guest

    I saw a mention of a Transform.GetChild function on another thread and started using that in conjunction with Transform.childCount in for loops. But when I submitted a bug report that GetChild was missing in the documentation, I got this reply:

    [
     
  11. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Ok, now things are clearer. If UT recommands using loops, it shouldn't be that much performance hitting.

    Thanks for the info ;)
     
  12. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Hey there, a quick update ...

    Docs scripts in these forums always presented coroutines as simple Time listeners, but I was convinced overall performance could benefit from its use with complex event listeners as well.
    (Ani.Mate scripts seems to use that approach)

    Long story short, instead of spamming statement tests in an Update() loop, we could just organize it into events. Concretely, putting a value test in an Update() would be the same as putting it in a Coroutine's while(), I do realize that.
    But Unity engine seems to handle coroutine inner instructions much better than Update() ones.
    Maybe I'm wrong, but well...

    The other real advantadge I'm seeing is that coroutines let you handle situations by "objectives" ("do that, and then, wait for your turn to do that again"), instead of the insane Update() series of modifiers / statement massive checks.
    Once again, I realize that it's possible to avoid infinity in Update(), but I found it much more natural with coroutines.

    So I'm trying to translate everything I can find in Update() to coroutines.

    Actually, I just turned an Update() if() statement from a level animation into a coroutine, and now I don't need Update() anymore.

    Plus I'm now using parametric methods, which turn my code into a much more clearer package.

    Here was the former pseudocode used :

    Code (csharp):
    1. //_bg_elements are Transforms of background Gameobjects
    2. //This script is a simple loop of 3 textured quads going from left to right in the horizon
    3.  
    4. void Update() {
    5.  
    6. _bg_element1.Translate(0F,0F,10F);
    7. if (_bg_element1.position.x > 200F)
    8. bg_element1.position = new Vector3(0F,0F,0F);
    9.  
    10. _bg_element2.Translate(0F,0F,10F);
    11. if (_bg_element2.position.x > 200F)
    12. bg_element2.position = new Vector3(0F,0F,0F);
    13.  
    14. _bg_element3.Translate(0F,0F,10F);
    15. if (_bg_element3.position.x > 200F)
    16. bg_element3.position = new Vector3(0F,0F,0F);
    17.  
    18.  
    19.  
    20. }
    So yeah, this is brute spam.

    And here is how I translated it into a coroutine :
    Code (csharp):
    1.  
    2.  
    3. //it is a very situational coroutine, checking if a transform has reached a position limit on the X axis.
    4. // but you can turn it into any generic method you'd like
    5.  
    6. IEnumerator TranslateToX(Transform _transf, float _speed, float _val) {
    7.  
    8.  
    9. while(_transf.position.x <= _val) {
    10. _transf.Translate(0F,0F,_speed); //(Translate Z modifies the X axis in my scene)
    11. yield return 0;
    12. }
    13.  
    14. }
    15.  
    16. IEnumerator AnimateBG(Transform _elem, float _from, float _to, float _speed) {
    17.  
    18. while(true) {
    19. yield return StartCoroutine(TranslateToX(_elem, _speed, _to));
    20. _elem.position.x = _from;
    21.  
    22. }
    23.  
    24. }
    25.  
    26. void Start() {
    27.  
    28. StartCoroutine(AnimateBG(_bg_element1, 0F, 200F, 10F));
    29. StartCoroutine(AnimateBG(_bg_element2, 0F, 200F, 10F));
    30. StartCoroutine(AnimateBG(_bg_element3, 0F, 200F, 10F));
    31.  
    32. }

    So it does exactly the same, but with more control.

    It seems to run smoother, too, I'm not sure. I'll do some tests this week end.

    Hope it helps.
     
  13. patch24

    patch24

    Joined:
    Mar 29, 2009
    Posts:
    120
    Good stuff! Can't wait to hear your results.
     
  14. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Well, I guess that any C# expert would giggle while reading my "discovery", like a little child discovers colors :D ...

    But here we go for the perf tests. I took the highest frametime peak in the different profiles.

    Update method :

    Coroutined method :


    Visually, I sensed a sensible smoothier display.
    But I used the 2 methods only for 2 bones, each controlling 3 huge textured quads. So the scale is poor.
    Maybe I'm wrong, maybe that smoothier feeling was only a lucky bias ... But the control coroutines can give over classic Update() spam seems powerful to me, so I will definitely convert all my Updates into coroutines. I'll tell you if the overall fps has changed.

    Another great advantage coroutines can give is a powerful checkup over dozens of complex mecanisms : Yield return something, placed in a given loop, is just a selective return. So if we turn that loop into a complex parser, we could just turn that Coroutine into a state checker ...

    Don't have the time to create an example, but I believe it would save much more horsepower to parse an already asynchronously parsed result, instead of massively parse a whole block, in one shot.

    As some far more better coders than me stated in these forums, this looks like threading. So I'm starting to believe that it can be propagated to every controller we can find in a game.
     
  15. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Confirmed once again :

    I jumped from an average 29.0 frametime to 27.0 just by turning to a coroutine a simple Translate on 2 5-bones driven GameObjects, coupled with a X-axis limit test on each one.

    And visually, the whole scene has less micro-hickups.

    Ok I'll stop the demonstration series now, I think we got it that coroutines are better than Update() spamming :)
     
  16. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Another performance boost update, but not coroutine related this time :


    I don't know if some of you guys tried the MAIN_LOOP_TYPE EVENT_PUMP_BASED_LOOP aproach in the compiler, but for me, with a kMillisecondsPerFrameToProcessEvents set to 3, it just gave me 5 to 8 more fps.

    (Was using Thread_loop before)

    Also, after an intensive touch test, I didn't notice any touch input missed recording.
     
  17. rozgo

    rozgo

    Joined:
    Feb 7, 2008
    Posts:
    158
    Found out that:

    CharacterController.Move

    allocates tons each frame.

    By the way, all the optimizations mentioned here are right on. Most are .Net related, but its Unity's responsability to provide allocation-free access, which they do (mostly).

    Data locality, through the use of structs, is another optimization.

    For example, for 100 projectiles:

    Projectile[100] projectiles; // script components
    ProjectileData[100] projectilesData; // struct component data

    Iterate/change/test over the struct. When needed, the component (Update, OnSomeEvent, etc.) can access this data.
     
  18. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Yes, Struct is another front advice from UT in docs.
    Thanks for the precision though, as I quite don't get how to integrate them in an optimized way.

    Basically, they are var containers ?
     
  19. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    structs once were nothing else than nicely name accessable "continous memory blocks"

    today though they are totally bloated to the point where they are just "differently handled classes" that by default are passed by value than by reference unless you work with them to interface between .NET and C plugins where they are really just continous memory blocks.
     
  20. rozgo

    rozgo

    Joined:
    Feb 7, 2008
    Posts:
    158
    The benefits of structs is apparent when used with native arrays: MyStruct[] myStructArray.

    This creates data in contiguos memory, with straight index lookup and no (boxing/unboxing).

    You can use structs to quickly read/write tight data that doesnt require bringing the entire component code, nor data into place.

    For example (the projectiles again), you could read and update hundreds of projectiles at once, probably fitting the entire code and data on the cpu cache.

    Iterating over big components, class instances and or other containers, may break data locality and miss the cache.
     
  21. rozgo

    rozgo

    Joined:
    Feb 7, 2008
    Posts:
    158
  22. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Normally yes.

    But on the iphone with its microscopic cache and missing L2 / L3 cache you have to be very cautious with such stuff or you actually enforce straight misses forcing the cpu to double fetch each time, wasting masses of cpu cycles for nothing.

    It also does not help against page misses, another potential use on desktops, as there aren't any pages at all that could be missed on the iPhone.
     
  23. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    That's vital infos, thank you for that, gentlemen.
     
  24. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Another fair use of coroutines :

    I see most of the scripts given by users about touch control management are Update() spams.

    Every finger checkups are made in a single loop, one after the other.

    It's not so perf impacting, as iPhoneInput class is not really cpu hungry.
    But here I think coroutines would be of better use, by binding a detection coroutine to each finger.

    This would register each finger state to variables, asynchronously, depending on the touches builtin array length. Something like (didn't have the time to test it yet, so don't hesitate to correct me if I'm wrong) :

    Code (csharp):
    1. IENumerator CheckFinger(int _finger) {
    2.  
    3. while(true) {
    4.  
    5. if (iPhoneInput.touches.Length > _finger
    6.  
    7. //checks if finger is registered in the array.
    8. //If not, it means that it's not active.
    9.  
    10.  (myPhaseArray[_finger] != iPhoneInput.touches[_finger].phase
    11. || myPositionArray[_finger] != iPhoneInput.touches[_finger].position) {
    12.  
    13. //only run your touch scripts if a
    14. //state has changed, instead of spamming.
    15. //this includes to have set cache arrays
    16. //for phase and position.
    17.  
    18.  myPhaseArray[_finger] = iPhoneInput.touches[_finger].phase;
    19. myPositionArray[_finger] = iPhoneInput.touches[_finger].position;
    20.  
    21. // insert whatever reaction you want here
    22.  
    23. MyTouchFunctions();
    24.  
    25. yield return 1;
    26.  
    27. } else {
    28.  
    29. //returning simple switchers in case of a further
    30. //sort of "touches history". Could be every data you
    31. //want, like position, motion etc.
    32.  
    33. yield return 0;
    34. }
    35. }
    36.  
    37. }
    And in the Start() function, you would launch as many coroutines as you want fingers to be active in your game :

    Code (csharp):
    1. void Start() {
    2.  
    3. for (int i = 0; i < _maxFingers; i++)
    4. StartCoroutine(i);
    5.  
    6. }

    It's a more event approach than brute Update() spamming, and as there are so many functions that depend on touch inputs, I believe it can save a lot of horsepower.

    The only side effect would be that it is framerate dependant, but honestly what's the point to register user's reactions on screens that he doesn't see ? ;)


    Well, once again I'm not a big coder so it surely can be optimized. But I thought it could be useful.

    Cheers
     
  25. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    I'm relatively new to coroutines, so I have a question...

    I've seen where people control the frequency of coroutines using WaitForSeconds(). But I've also seen examples like the one above where you simply do "yield return 1;". In the latter case, what controls the frequency of the coroutine and how do we know what kind of frequency results from such usage? Wouldn't it just run as frequently as possible, resulting in a situation not unlike Update()? My impression is that it wouldn't, but I'm just wondering why. And why you would want to use that approach instead of WaitForSeconds().
     
  26. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    In fact, the frequency is entirely decided by where you put the yield keyword and what does it return.

    Concretely, yield will hold the current IEnumerator script until its associated function is finished.
    By default in Unity, it just waits for the next frame.

    Ex :
    yield return 1; // would pause until next frame

    You can put a custom return (and that's where it becomes very powerful), but its Type has to be enumerable.

    Ex :
    yield return StartCoroutine(DoSomeStuff());
    // would pause until DoSomeStuff has a yield inside it, at some point.

    But where you can unleash the full power of yield is if you use it as an real enumerator.

    Like in this C# example :

    Code (csharp):
    1. public static IEnumerable Power(int number, int exponent)
    2.     {
    3.         int counter = 0;
    4.         int result = 1;
    5.         while (counter++ < exponent)
    6.         {
    7.             result = result * number;
    8.             yield return result;
    9.         }
    10.     }
    11.  
    12.     static void Main()
    13.     {
    14.         // Display powers of 2 up to the exponent 8:
    15.         foreach (int i in Power(2, 8))
    16.         {
    17.             Console.Write("{0} ", i);
    18.         }
    19.     }
    With that, not only can you spread hundreds of asynchronous Coroutines, but you can also make them enumerable.

    Abstractly, this would be like launching hundreds of parallel processes, and turn their inner data checkable whenever you want.

    The advantage over Update() is simply that you can have hundreds of enumerable Update() in one class, with the added ability to be relative to each other.

    Seems like Yield is a pretty new concept for every coder (introduced with .NET), but I feel like it's a really clever and powerful new addition in our arsenal.
     
  27. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    I see, so then without WaitForSeconds(), or unless the content of the coroutine was sufficiently taxing, you would still wind up with a routine that runs each frame - just like Update()? I'm not trying to minimize the other benefits, just trying to clarify the issue as it regards saving CPU cycles.

    Currently, my project uses WaitForSeconds() to update AI at below-framerate frequencies so that the update is only every few frames (depending on framerate). Am I missing out on anything you can see by taking this approach?
     
  28. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    I'm not a coroutine expert, but I believe it is like you described, yes ;)

    The default rate is each frame.

    From that point, it depends on what you put inside your IEnumerator.
    For ex :

    Code (csharp):
    1. IEnumerator WaitForNthFrames(int _n) {
    2.  
    3. for(int i = 0; i < _n; i++) yield return i;
    4. //wait for _n frames
    5.  
    6.  
    7. }
    Here you can turn your waitForSeconds into WaitForFrames, and use it where you want :

    Code (csharp):
    1. void DoStuffAfterNFrames() {
    2.  
    3. yield return StartCoroutine(WaitForNthFrames(120));
    4. DoStuff();
    5.  
    6. }
     
  29. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Cool, thanks! I've always been a vanilla C++ guy, so coroutines seem like magic to me since I'd normally have to do all kinds of threading code to do that.
     
  30. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Yes, it's a very useful little word :)


    And its mechanics let coders to keep easy control over huge amounts of data.
     
  31. rozgo

    rozgo

    Joined:
    Feb 7, 2008
    Posts:
    158
    yield is a little beast

    i'm not sure its an actual keyword as much as it is a hack that creates VM code (I suspect class wrapping)

    the iphone uses .Net 1.1, but still manages to have yield working

    that simple word has to do tons of stuff to make these functions work like they do

    also coming from C++, (where I had to copy around the stack and move the frame pointer to achieve the same), the yield (and coroutines) to me, are a beautiful mystery that works like a charm
     
  32. Brady

    Brady

    Joined:
    Sep 25, 2008
    Posts:
    2,474
    Yes, sometimes ignorance is bliss. :)
     
  33. VoxelBoy

    VoxelBoy

    Joined:
    Nov 7, 2008
    Posts:
    240
    One thing I don't understand, reading through this thread, is why we're not getting any insider information from Unity Tech. with regards to how coroutines "actually" work, instead of having to run crazy tests to maybe figure it out ourselves, but probably not.

    A 2-3 paragraph long explanation from a Unity Tech specialist on the topic would clear up the whole discussion and save us a lot of trouble.

    I see two options:
    1 - They need to start addressing serious questions like this in the forums.

    2 - Or they need to really work on the documentation to provide sufficient details so we don't need to ask these kind of questions.

    What do you think?
     
  34. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    I must agree on this point. There are several technical questions that have been asked around here (including a few by me :wink:), that are very boring and time-consuming to test, and to which no community members have the answer, when an answer from UT would give proper, useful information to us all.
     
  35. kenlem

    kenlem

    Joined:
    Oct 16, 2008
    Posts:
    1,630
    How about someone sends a nice, polite PM to one of the devs and ask them to join the thread?
     
  36. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    That's worked in the past. I'm not knocking their willingness to help in the least. It's just an issue of getting official word without having to do that, and feeling like a pest. The devs aren't around these boards like they used to be, which is totally understandable.

    I suggest that people, the likes of these two helpful individuals, should try to keep an eye on the technical questions that aren't getting answered, and approach the lead guys with a small collection of topics to be addressed, perhaps once a week or so.
     
  37. n0mad

    n0mad

    Joined:
    Jan 27, 2009
    Posts:
    3,732
    Actually, we shouldn't blame Unity team for that :)

    Because Yield is a C# (and even Java) feature.

    So if we ask UT to implicitly be clear about that, they should also put a doc on every C# / BOo / Mono / Javascript little secret ;)

    The most they could do is to spontaneously give their opinion about how they tweak things. Plus there is already a clear advice in iPhone Unity docs drivin to use Coroutines.

    But of course, it would be a huge benefit for all of us if they were a bit more "technical" about those advices, I agree with you on that :)


    Cheers
     
  38. crafTDev

    crafTDev

    Joined:
    Nov 5, 2008
    Posts:
    1,820
    Really helpful stuff in here. Thanks!
     
  39. ptdnet

    ptdnet

    Joined:
    Apr 20, 2011
    Posts:
    100
    I was freaking out reading this thread until I realized it's 2-year-old information! wtf
     
  40. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    It's still just as relevant today.

    --Eric
     
  41. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    Hi all, wow OK just trying to understand. So to paraphrase the issue from C# point of view:
    * using a foreach allocates memory for every iteration.
    * it's not a unity bug, it's just how the mono runtime works.
    * it's not a memory leak technically, because it will get garbage collected at some point.
    * using a for loop then the int i is allocated on the stack, not growing the memory.
    * so one should be careful not to use foreach in long running loops like coroutines that wont ever get GCed.

    edit: also don't use foreach in Update* or other every-frame methods, that could introduce GC-related stuttering

    Is that more or less it or am I totally confused?

    edit: I personally disagree with the conclusion "never use foreach" because that seems overkill when it's such an extremely useful code idiom!

    Thanks
     
    Last edited: Apr 7, 2012