Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Resolved Coroutines instead of Update()

Discussion in 'Scripting' started by neginfinity, Apr 13, 2022.

  1. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,623
    "Coroutines instead of Update()". Has anyone tried this sort of approach?

    Basically, it seems that it is possible to write fairly clean code if you launch a control coroutine within OnEnable(), and make it work instead of the usual Update() cycle.

    Caveats, though:
    Coroutines are killed by OnDisable().
    OnEnable() cannot be a coroutine. Start() runs only once, so you can't use Start() as a coroutine if the object is being enabled/disabled often.

    If you create something like Texture, Material or RenderTarget within coroutine, it will leak, because those are not garbage-collected (is there a wrapper for those, maybe?)

    Thoughts?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    8,192
    I suppose it depends what you're wanting to do in the loop?

    If I want something to happen over a period of time but ultimately end at some point, Coroutines. If you want something to always be happening, don't see the issue with Update.

    Though to be honest if I want something to happen over a set period, I prefer asyc methods with UniTask. I find just being able to
    await
    blocks of code to be the cleanest approach.
     
  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,932
    Counterintuitively, I recently found out that this is not true.
    Assuming a coroutine was started via:
    Code (CSharp):
    1. myMonoBehaviour.StartCoroutine(SomeCoroutine());
    This will stop the coroutine:
    Code (CSharp):
    1. myMonoBehaviour.gameObject.SetActive(false);
    But this will not stop the coroutine:
    Code (CSharp):
    1. myMonoBehaviour.enabled = false;
    Note that OnDisable runs in both cases.
     
    Bunny83 likes this.
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,623
    I believe I've run into a post regarding that before, and it is possible that you've been among the people discussing it.

    It is an important caveat, but normally I'm not disabling components one by one, but instead disable the whole object. And that kills the coroutine.
     
    PraetorBlue likes this.
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,565
    Sure. I've used coroutines for all sorts of things through out the years. For instance using a coroutine for AI logic or some other thing that updates at some interval that yield instructions were better suited for.

    I've gotten around this by having my own custom Coroutine class that has various modes that allow me to control when/how it disables/cancels.
    Code (csharp):
    1.     /// <summary>
    2.     /// Flagging enum to define how a Coroutine should deal with its operating MonoBehaviour is disabled or deactivated.
    3.     /// </summary>
    4.     [System.Flags()]
    5.     public enum RadicalCoroutineDisableMode
    6.     {
    7.         PlayUntilDestroyed = 0,
    8.         /// <summary>
    9.         /// The default action from Unity. The routine cancels on deactivate, and plays through disable.
    10.         /// </summary>
    11.         Default = 1,
    12.         /// <summary>
    13.         /// The default action from Unity. The routine cancels on deactivate, and plays through disable.
    14.         /// </summary>
    15.         CancelOnDeactivate = 1,
    16.         /// <summary>
    17.         /// Cancels on disable, still cancels on Deactivate as well.
    18.         /// </summary>
    19.         CancelOnDisable = 2,
    20.         StopOnDeactivate = 4,
    21.         StopOnDisable = 8,
    22.         Stops = 12,
    23.         Resumes = 16,
    24.  
    25.         PausesOnDeactivate = 20,
    26.         PausesOnDisable = 24,
    27.         Pauses = 28
    28.     }
    My custom Coroutine class has several other features that I setup long ago before Unity even had the 'Coroutine' class for representing state (you used to only be able to stop by string/ienumerator, this was years ago). It's evolved over the years. Though now a days I've been moving away from it just because of things like 'async' now being available.

    This isn't really related to Coroutines. Those will leak regardless of where you create them. It has to do with those classes are wrappers around unmanaged objects. You have to call 'Destroy' on them.

    I just want to clarify this because if you create new instances of these in Update, or Start, or well... anywhere aside from normal resource loading (like it was already attached to an object in a scene/prefab that you loaded). Then yeah, they're potential memory leaks if you don't destroy them correctly.

    Do what works for you.

    There is nothing wrong with Coroutines.

    But if you just need something that does a thing every frame... what really is the point of a Coroutine that yields null constantly when Update can get the job done? I'd only use the Coroutine as Update if my Update would get ridiculously complicated otherwise.
     
  6. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,623
    Are you familiar with C++ RAII pattern? Would be nice to have that.
    Also see SafeHandle class.
    https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle?view=net-6.0
    https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization

    It can easily wait, or switch to doing something else. Managing timers through Update can be done, but it is really awkward.
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    Note there's an important point that a lot seem to not understand about coroutines. Coroutines themselfs are just generator methods. When executed they create a compiler generated statemachine class which is returned (an object that implements the IEnumerator interface). When you pass it to StartCoroutine you essentially hand over the statemachine to Unity's coroutine scheduler of the MonoBehaviour you call StartCoroutine on. The crucial part is: the coroutine / method does not have to be run on the same MonoBehaviour. In fact, coroutines do not have to be implemented in a MonoBehaviour at all, you just need one as a coroutine "host".

    If you want to create coroutines which keep running even when the object is deactivated, you can simply run them on a MonoBehaviour singleton that is never destroyed or deactivated.

    Though keep in mind that when coroutines use / reference anything from the class they are defined in, the statemachine object is essentially a closure and will keep that object alive. This is also true when the actual object has been destroyed since the managed part of that class will exist until all references to it are gone. That means you can even have a running coroutine that belongs to a dead object as long as you run it on a different (still alive) MonoBehaviour. Though keep in mind that you can not use any Unity related things of dead objects (no GetComponent, no access to "name", transform, etc...). So since with this approach the coroutine is managed on a seperate object, you have to be careful how you start your coroutine and how you're ending / stopping it.
     
  8. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,342
    I mean sure, go for it if there's a good reason for it.

    The big problem I can see is that if the GameObject gets disabled, you lose all the implicit state of the IEnumerator, so everything you want to survive disables needs to be stored in fields. That reduces the cleanliness of just keeping state-local stuff in the coroutine somewhat.

    Unless you're doing a ton of different waits all around the place, an Update with a state machine and some timers might end up being approximately as readable, and have less overhead.

    If it's a single instance of this running, I find that writing a PlayerLoopSystem is cleaner as there's no need for the singleton or whatever that runs it.
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    Well, yes and no ^^. It depends. Of course when you completely stop the coroutine when you disable the associated gameobject, the internal state of the coroutine would of course be lost. However the point was that the coroutine can continue to run, even when the gameobject the coroutine belongs to is deactivated. You can make the coroutine simply wait until the object is active again.

    Code (CSharp):
    1. while (!gameObject.activeInHierarchy)
    2.     yield return null;
    You could also include a safety net to make sure the coroutine is terminated / stopped when the associated object is destroyed

    Code (CSharp):
    1. if (this == null)
    2.     yield break;
    Here you can also decommission any resources you may had allocated at the beginning of the coroutine. I would always recommend to exit a coroutine gracefully from the inside. I would not recommend terminating it from the outside. The reasons are similar to terminating threads from the outside, though coroutines are more predictable in that sense. Though complex statemachine could be in any state, so terminating from the outside could produce unwanted edge cases. So it's always better to let the coroutine itself finish.
     
  10. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,623
    Last time I tried that the coroutine silently died when the object was disabled. As mentioned above, they're silently killed off when gameObject gets disabled, but they continue to run when only component gets disabled.

    I also highly doubt that a lot of peole in this thread don't understand that it is just a generator.
     
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    Well, maybe you missed the key point of my reply. I suggested to run the coroutine on a singleton object that is not deactivated. This avoids any trouble for the actual object containing the coroutine itself.
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,565
    My point had nothing to do with what Unity could do to make the experience better.

    My point is that your statement:
    Suggests that it happens because it's "within a coroutine". Which the coroutine had nothing to do with.

    Do you read like HALF of what people say and then just jump to conclussions?

    Because... this was my point.

    I said:
    I'm saying.... use update if you're just yielding null. Use a coroutine if you need timers, or to wait for other stuff that would be "ridiculously complicated otherwise".

    Referencing back to my first statement of my post:
    Now of course your quoting me may have just been a way for you to just state your feelings, rather than contest what you were quoting of me.

    But that's really weird. Why would you quote my statements to just say non-sequiturs and/or repeat yourself?

    Combined with how you glossed over what @Bunny83 said, just confuses me as to what your intent is exactly. Are we just having a really awkward conversation about "are coroutines useful?"

    There's an easy answer to that... yes. They are useful.
     
    Last edited: Apr 13, 2022
    Baydogan and Kurt-Dekker like this.
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,033
    This post burst onto the scene with so much misinformation and false assertions I don't even know where to begin, but it looks like others have addressed most of the silliness.

    Just keep in mind there is ONE SCRIPTING THREAD IN UNITY AND IT DOES EVERYTHING.

    Coroutines are there as a flow control construct and have nothing to do with garbage or leaking or performance. You're not gonna be sooper-sneeky-cheeki-breeki and find some clever new way to make Unity go faster by using Coroutines. That's not a thing. Work is work and work has to be done by the one Unity main thread.

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    Good discussion on Update() vs FixedUpdate() timing:

    https://jacksondunstan.com/articles/4824
     
  14. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,090
    Coroutines are just to risky of a feature, all this uncertainty in this thread is a proof of it.

    From time to time I'm using enumerator and "methods with yield" in update function.
    It does work, it is predictable, and is ended when I want it to.

    I generally consider coroutines an anti feature I don't remember finding usage for them that would not be some kind of hack.
     
  15. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,623
    My intent was to discuss and ask for experience of other people in hopes for that tiny chance of learning something that I do not already know.

    And if you're gonna do this over something this simple:
    Then next time I'll just dump that in General instead.

    I have no idea what you're talking about here.

    The order of function execution is defined here:
    https://docs.unity3d.com/Manual/ExecutionOrder.html
    Is there a mention anywhere that coroutines are running in another thread? Or that they're faster? No.
    It is the same thread.
    However you are able to perform multi-frame code in a linear fashion, which allow you to keep things in one place, hide variables in scopes, use subroutines and functions easily, where every one of them can also run across multiple-frames, while in Update() method that will result in a clumsily done statemachine with a lot of if/else or you'll have to start writing decorators. Yes, it is a generator expression, and a generator expression creates an anonymous class/state machine with encapsulates variables within.

    So what happens when the state machine stops advancing and is terminated mid-way? Anything it had is going to be released to gc, but because scope-based lifetimes are not a thing, because there's no SafeHandle for GameObject's, Materials and Textures, because of that unity-based created resources within coroutine are going to leak.

    I didn't ever write anything about them being "faster", and instead wrote "cleaner", so I'm honestly baffled, where the heck did you find this amazing idea you mentioned here:
    Because it was about code cleanliness and not speed.
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,565
    You'll dump it in general if I point out you only read half of what people say?

    OK...
     
  17. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,623
    No, that's not the problem.

    Honestly, I'll just put you both on cooldown and call it done.

    Unsubscribing.