Search Unity

Making helper class full of coroutines, what is the best approach?

Discussion in 'Scripting' started by paternostrox, Apr 19, 2019.

  1. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    So I think it would be useful to me to have a helper class full of coroutines of lerp movement functions ready to use (like sinusoids, exponencial, smoothstep). Something tells me that this is not a good idea but I have two ways in mind.

    1) Having this class as a lazy-singleton (gets instanciated upon call) and calling it from the user class like
    Code (CSharp):
    1. Lerpies.instance.StartCoroutine(Lerpies.instance.Exponencial(...))
    2. // or
    3. StartCoroutine(Lerpies.instance.Exponencial(...))
    I also don't know if there is any consequences to performance or stability with starting the coroutine from the singleton also. Any clue?


    2) Having this class as static and calling it from the user class like
    Code (CSharp):
    1. StartCoroutine(Lerpies.Exponencial(...))

    If you have another take on this or just some random thought please share, any feedback is really appreciated :)
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,776
    Why you think you need coroutines for such simple thing?
     
    SparrowGS, paternostrox and xVergilx like this.
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Note that each StartCoroutine is a potential allocation which will drain your performance.

    If you need tweening, try DOTween (although its evil as well, kinda), but atleast it has recycling.
     
  4. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    I think I described what I want poorly, thanks for your patience. I want to be able to call a function to move my objects around from currentPos to a goalPos for me, just like:
    Code (CSharp):
    1. StartCoroutine (Lerpies.instance.Exponencial (myGameObject, myEndPoint, timeToLerp));
    I don't see how to do this in an organized manner using Update for example.
     
  5. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    Thanks for the input, that is also a thing that worries me because I'm not entirely sure. My take is it's going to allocate memory but like any other function (it will free the memory after it's done), coroutines used to generate garbage but ~I think~ they don't do it anymore.

    This is the reddit discussion but there is a link to the issuetracker there
    https://www.reddit.com/r/Unity3D/comments/4l0r65/finally_coroutines_dont_allocate_garbage_every/
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    They do not do it every frame that is true. However, coroutines do generate garbage each time they're started, since you're allocating a new IEnumerator each time.
    Unfortunately, you cannot .Reset Unity's coroutines, so there's no other way of doing it, except not running coroutines at all.

    Also, note that not every function do allocate memory. Potential problems only arise if you allocate heap memory, because it has to be released back, and the garbage collector is quite slow.

    That means new class instances (such as new Class()), boxing (value type to object), delegates (linq closure captures), etc will cause allocation. There's plenty of info what allocs and what doesn't in C# on the net, so it might be worth googling.

    Also, there's a plugin for Jetbrains Rider that automatically highlights what's an alloc / potential alloc.
    (Not sure if VS has one)
     
    Last edited: Apr 19, 2019
    paternostrox likes this.
  7. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    Oh yeah it is nice to remember this. Performance isn't a bigger concern with the current project, but I would like to do everything as good as possible without hurting the maintainability/readability of the code, so I guess some allocation to the heap isn't a issue if it's going to improve those.

    Can you tell if one of the ways I pointed in the OP has any other bad consequences? I'm worried about that since I never saw a piece of code using this approach.

    Thank you so much for the great input <3
     
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,776
    You could use for example simple timer check, in IF statement, and have your methods there. That is, if you want run something periodically. But not only.

    For single player coroutines may not be bad, if you really need them.
    But if you are planning multiplier, moving objects outside of main loop, may cause potentially desyncing.
    Depends how you program things.
     
    paternostrox likes this.
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,776
    Potentially debugging may be harder.
     
    Suddoha and paternostrox like this.
  10. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Moving that logic to a single place is not bad at all. Tween libraries do exactly that, no matter if they use coroutines under the hood or dispatch it to a seperate component that updates all the queued actions.

    There's nothing wrong with having a provider for coroutine objects, as you can adjust and bug-fix your code in a single place, you can implement recycle mechanisms etc. The maintainability aspect is especially useful for more complex routines, like code-driven animations that do more than just a movement or rotation logic.

    The only thing you need to take care of is that you should at least run the coroutines as if they were started off the component that needs a coroutine, so you should pass the calling component and call StartCoroutine on it.

    Reason being that they stop in the following situations (if you do not stop them explicitly):
    - runs to the end
    - stops when component is destroyed (disabling does not stop it)
    - stops when GO of the component is deactivated
    - stops when GO of the component is destroyed


    You could implement other behaviour, but I'd first stick to that.
     
    Last edited: Apr 20, 2019
    paternostrox likes this.
  11. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    Hey everyone sorry for the late reply.

    Yes! :D I think I'll do just that, I was worried by maybe maintainability/stability issues it may cause, but now I see in a better angle what the drawbacks of this approach are and I guess it should work just fine, it feels right also.

    Thanks Suddoha, and also thanks Antypodish and xVergilx for the valuable input. I wish you were close so I could buy you all a beer :)
     
    Antypodish likes this.
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Just noticed you probably only define the IEnumerators somewhere else, and start the coroutines from your components. So you don't even need to pass the component on which you want to start it (skipped the initial post too quickly).

    Despite the benefit of the defintion in a single place, the debugging aspect mentioned by @Antypodish is something you might find a little important. You'll no longer be able to debug a specific couroutine defined by a component, but you'll always debug that one definition which is potentially called from many different places / components.

    It may take a little more effort to find the offending coroutine instance, but the debugging tools (at least in VS) are generally good enough to compensate it.
     
    paternostrox and Antypodish like this.