Alterantive to Coroutines for timers and cooldowns?

Hi,

Lately I’ve been hearing a lot of arguments against Coroutines, which is fair. But at the same time Coroutines are great, they make things super easy and intuitive.

So I was wondering. How do you do Timers in your projects? Whats the best way to wait for a few seconds and then do something?

I tend to use Coroutines and in some cases even the infamous Invoke. I can’t really bother to do Update() logic to handle this sort of thing, it’s not very flexible and it’s extremely verbose.

Is there any “best alternative” the people that complain against Coroutines use?

I would ask them. If they don’t have one you like, their complaints are not worth taking seriously. And, really, just what are these complaints? I love coroutines for the exact reasons you mention: easy, intuitive, flexible, and compact.

For a delay, I just use the WaitForSeconds class:

    StartCoroutine(DoAfterDelay(1.5f, () => Debug.Log("Done!")));

IEnumerator DoAfterDelay(float delaySeconds, Action thingToDo)
{
    yield return new WaitForSeconds(delaySeconds);
    thingToDo();
}

Really? I mean REALLY!?

void Update()
{
   if (gunheat > 0)
   {
     gunheat -= Time.deltaTime;
   }
   if (PlayerTriesToShoot())
   {
      if (gunheat > 0)
      {
           // cooling still, no shoot
      }
      else
      {
           /// shoot!
           gunheat = 1.0f;  /// cooldown time
      }
   }
}
3 Likes

I personally use Update/Coroutines a bunch. I have yet to have a single issue with either.

I guess you could alternatively use ‘async/await’. The newer 4.x .net targeting has enabled it. Though honestly when it comes to the synchronization context and all that, I’ve yet to actually USE it in Unity. I primarily use async/await in my day job (business software). So as it comes to it “just working out of the box” I don’t know the status of it. Early on you had to implement your own syncrhonizationcontext, then I know some asset store things popped up… word was Unity would add direct support “out of the box” but I don’t know the status on that. I’m still back on an early Unity 2018 build without the 4.x .net support (just a couple more weeks and we’ll be moving to projects with newer versions of unity).

Anyways, maybe someone could give you more specifics on the status of async/await working in Unity without issues (note there can be invisible issues in the background). But once you got it working (may you have to import some asset or be on a specific version of unity) it’d be as simple as:

public async Task DoSomething()
{
    await Task.Delay(1000); //wait 1 second
    this.transform.position = Vector3.zero; //do something, in this case move to origin
}

Though I mean… performance wise I’m unsure about it compared to Coroutines. I’m going to guess it’s more efficient (again haven’t used async/await in unity specifically). But it’s going to have its own overheads as well for sure.

Alternatively, you could also write some wrapper logic that uses Update, but makes it straightforward/easy to use.

3 Likes

Question though…

Why are you looking to avoid Coroutines?

You seem to like them:

Who cares about other people’s opinions?

If they work for you, and you’re getting the performance you need from Unity. Why fix what ain’t broke?

3 Likes

Async Await works fine.

I think it might actually be slower than a coroutine, due to the fact that async await is designed to automatically sync thread contexts and unwrap tasks. I think there is a cost that you pay there for that convenience even if you aren’t using it to return results from other threads. I might be wrong though.

Coroutines apparently take unity timescale into account. Async Await won’t. Definitely something to keep in mind.

I often use Update for simple timers. It might be a couple extra lines of code, but it is really simple to keep track of exactly what is going on each frame. If the number of timers themselves is variable, or the timer is rather complex, then I’ll default to coroutines.

1 Like

Some really interesting perspectives here! I think Kurt-Dekker’s example shows how integrating cooldown into Update can be easy and sensible, particularly when “TriesToShoot()” is (I assume) a polling method that necessarily needs to be called on each Update.

Now, if code that TriesToShoot conditions were replaced by an event-driven call to a method (which might well be the case if one were using the Input System instead of the Input class), moving the cooldown out of Update and into its own coroutine looks appealing to me. The coroutine could run until gunheat <= 0, then exit. That avoids constantly checking gunheat in Update, over and over, when it’s going to be zero a lot of the time. If one is counting CPU cycles, this is all small potatoes, and one method is as good as another, really. If one has an Update method that begins to accumulate a lot of conditionals, that might be another story. I like coroutines as an alternative there, but it makes just as much sense to break the “God” class containing that Update into multiple small classes, each of which can have its own (much cleaner, smaller) Update method.

I tell my students that Update is probably the way to go when something will need to run repeatedly, often, and for the lifetime of the object it’s in (like TriesToShoot, for example), and that coroutines might be the better bet when something happens from time to time, and for only a limited time.

I rarely use async/await, but I have done a lot of multithreaded coding. Something else I find I have to tell my students is that coroutines are not multithreaded. (The students who have done any concurrent coding tend to see the similarities between that and coroutines, and some of those students get really anxious about synchronization and cache-coherence issues that just don’t exist with single-threaded coroutines.) If you can do anything to get your mutli-threaded code to run on another core than the main thread, you get some real gains there. If the async/await stuff is running on the same core, I expect it would slightly degrade performance as, albeit light, there is some overhead in switching thread contexts.

Keep it coming! I love this kind of shop talk (when you work at home, alone, it’s all there is).

2 Likes

I also tend to favor code with extremely clear flow paths… I just ASSUME code will fail and have bugs, so when I’m writing it I am always thinking,

“Where can I put the breakpoints?”

… always… It’s like a mantra. I know that I am GOING to need breakpoints, that’s not even negotiable. Make it easy now, air the code out vertically, one thing at a time (which btw is all the CPU is going to be doing!), and step, step, step.

I have used Linq but trust me, there are no chained().on().things().in().my(codebase);

I use them all the time. Here are some alternatives:

2 Likes

They allocate garbage and aren’t very fast. Which to be honest is ok by me. I am just really curious and willing to learn about some alternatives. Thanks for your response btw :smile:

Thank you! :smile:

What do you have in mind?

What makes you say so?

1 Like

Doing new yield for WaitForSeconds(n); allocates memory. And StartCorutine ain’t free.

Which to be honest is ok by me. I use Coroutines all the time. I was just really curious and willing to learn about some alternatives :smile:

This thing looks great and works great if you want to take a look. Is more flexible than Coroutines too: https://github.com/Cysharp/UniTask

If you mean yield return new WaitForSeconds(n);, you are correct. But you can use a single WaitForSeconds object as many times as you want.

What is?

You still haven’t answered my earlier question:

I dunno where rumors get started. Here is how timing works in Unity3D:

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

Also, there is absolutely nothing magical about coroutines. They’re not “heavy hairy objects” the way you’re implying.

Coroutines in a nutshell:

https://discussions.unity.com/t/825667/6

https://discussions.unity.com/t/749264/9

Man, if you ever do, please post it. I used to be a politician, which was when I learned that Einstein was wrong when he said nothing moves faster than light. All politicians know that nothing moves faster than bullshit. Gamedevs probably know this too.

I’m a little frustrated that our OP hasn’t really addressed this. Just what are these complaints against coroutines?

1 Like

I think you should avoid starting coroutines from a semi hot loop and absolutely not from a hot loop. Thats about it.

I’m not familiar with these terms. What are a “semi hot loop” and a “hot loop?”

I am curious about how UniTask works. The main page at the github repo says this:

But the code includes lots of await instructions. Await forks and creates a new thread.