Replacement for Coroutines in non-Monobehaviour derived classes?

Been playing around with finite state machines.

Built a system the fairly standard way using abstract base State and derived states from that etc.

In original (pre-FSM) code I was using a coroutine to check status of a gameObject to save having to put it in update() and check every frame. Now that I’m using classes that don’t derive from MonoBehaviour, seems it won’t let me use coroutines.

What’s the alternative? Is there one?

Why?!!! Are you just trying to make it difficult on yourself?!

One unit of CPU work done in Update() costs PRECISELY the same amount as if it is done anywhere else.

The reason: from the C# scripting perspective, Unity is single-threaded. No really, go back and read that again. SINGLE threaded. One thing must complete executing before the next can happen, and ALL the things must complete before the next frame is rendered.

If you doubt me, here is some timing diagram help:

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

Note how ALL the Update()s are called, THEN the coroutines are processed. Every single frame.

I don’t know exactly where this fetish for “get it out of the Update()” loop began but it leads to horrible code. If you check something every frame in a coroutine, that is exactly as expensive as putting it in Update(), but now you have confused your code if a coroutine doesn’t actually model what you’re trying to do.

If something is appropriately “fired off and forgotten,” then maybe it belongs in a coroutine. Nothing ever MUST be in a coroutine, and there is rarely any performance benefit to it.

You can still use coroutines anywhere you like. It’s up to you. Somebody has to “run” them. Bare classes cannot run coroutines, but they can certainly hand back IEnumerators, which other things can “run,” things like MonoBehaviors.

But if you prefer, you can take an IEnumerator and “pump” it yourself periodically by calling .MoveNext(). That’s all a MonoBehavior does, nothing more, nothing less. That’s why you don’t get any magical benefit because you stuck something in a coroutine. It is still executed by precisely the same CPU thread as if it were in Update(). Honest!

Coroutines in a nutshell:

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

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

1 Like

Hey @Kurt-Dekker , good to have you helping me again, and thanks for the lengthy and informative reply.

I eventually found a way to reference a MonoBehaviour and used that to do my coroutines, so that was one thing nailed.

The reason why I’m using them is because I want an object to pause for a second, then do the next thing (in this case, move to a patrol point, pause, then start moving to the next one). Hence using co-routines because I actually wanted that pause in the code. Not because it opens up a magical thread.

Having said that, I was under the impression that polling every 0.25/1/etc. seconds was meant to be better than every frame in Update. I only ever use coroutines with WaitForSeconds - I was under the impression that creating a temp variable in update and counting down is a) unreliable and b) hacky.

In this circumstance I don’t think I’m checking every frame am I? I asking it to check ever 0.25 seconds etc.

For example:

IEnumerator patrolStatusCheck()
    {
        yield return new WaitForSeconds(checkTime);

        if (agent.remainingDistance < distanceToTarget)
        {
         
            FSM.StartCoroutine(WaitThenChoose());
        }
        else if(Vector3.Distance(player.transform.position, FSM.NPCgO.transform.position) < chaseTriggerDistance)
        {
            FSM.MoveToState(FSM.s_Chase);
        }
        else
        {
            FSM.StartCoroutine(patrolStatusCheck());
        }

        // Trigger to move to chase if player is near.
     
    }

So this code is checking every 0.25 seconds if it’s close enough to a target to ‘do a thing’, and then in the WaitThenChoose coroutine it ‘pauses for thought’ (for a second), then chooses and new target and moves.

This is probably a good use of coroutines isn’t it? [takes shelter just in case…]

My main objection to this pattern is if halfway through that delay you decide to make the enemy do something else, there’s no “clean” way to get yourself out of the coroutine. Sure, you can StopCoroutine on the object returned, but that Coroutine object has to now be passed anywhere that something else might need to stop it.

The ONLY thing that yielding a WaitForSeconds() does is get you out of running your timer yourself and checking it every frame. Internally Unity is actually doing this, and you can’t see it, so you can’t do anything about it.

An example might be casting a “Personal Distraction” spell on a patrolling enemy which makes it just extend its wait before it checks again… doing that with your own timer is trivial… doing it with a coroutine delay would be awkward: you can’t really get in there and say “No wait, longer!” It’s out of your control.

Well I’d just change the value passed to WaitForSeconds wouldn’t I?

It still seems cleaner, clearer, more reliable (frame rates etc.) and less code that if I coded up my own timer. What am I missing here?

Is your argument that there is no use for Coroutines at all? Are they a particularly bad structure? Is it that they’re over-used?

You can’t change the value after it has begun. For instance, you put the patrol to sleep for 5 seconds then decide after 2 seconds it needs to wake up. Somehow you have to know that a) it IS sleeping, and b) somehow interrupt that specific inner portion of the coroutine, which it might not even be doing at that moment because externally, you can’t “see into” the coroutine.

This may be. I’m basing my commentary on having to dozens if not hundreds of inappropriate coroutine usages that I have encountered, where the use of a coroutine simply made adding the next logical feature nearly impossible to reason about. If you’re happy with what you got, carry on!

Oh no, I properly wanted to understand your point. What I have is working well, but it never hurts to learn of and understand other perspectives!

So the coroutine is essentially like a black box for (x) seconds which we can’t see into? Whereas in an ‘Update timer’ (for lack of a better word), you can put your own stops and interrupts in later on if you wanted.

Thanks for taking the time to explain.

So the upshot of all this is…there isn’t a direct replacement for coroutines in non-MonoBehaviours, and even if you ARE using MonoBehaviours, you should seriously consider rolling your own timer/WaitForSeconds-equivalent?

The specific uses you list are for an FSM that controls AI behavior like patrol, wait, attack, etc.

I have used and made things like this before, and I think an implicit use case of any of it is this:

  • no matter what the agent is doing, if there rises a condition where I want him to do something else, I want that to be trivial and take effect as soon as possible.

Can we agree on that?

To that end, if I fire off some kind of coroutine that does a wait, my only recourse is to terminate that coroutine early (with StopCoroutine()) or else implement some sort of fudgy “everywhere in the coroutine should check at all times if it should stop and go do something else.”

I think we don’t really want the latter (because that’s just check every frame… why not check every frame then in Update()?), and even the former could be problematic.

Now if it’s okay for your design that the agent blindly continues even his longest possible “I’m going to file down my nails for a little bit, and even when incoming rifle rounds start hitting the wall next to me, I will continue filing my nails until I’m done,” then I suppose coroutines could work.

But I’m thinking if I surprise an enemy in the throes of a five seconds “checking my nails” and start shooting at him, he immediately abandons his self-manicure and takes appropriately defensive (or offensive) action.

Yep, that makes perfect sense. Thanks for your input. Much appreciated.

1 Like

Hmmm…one final thing then.

Given the disadvantages of using ‘WaitForSeconds/Coroutines’ (i.e. can’t be interrupted), what’s the best way to implement it in an ‘Update’ friendly type fashion?

It’s really up to how complex a behaviour you’re doing.

My pattern is to make Update() functions named what the mode is:

void UpdatePatrol()
{
}
void UpdateIdle()
{
}
void UpdateSeek()
{
}
void UpdateEngage()
{
}
void UpdateFlee()
{
}

And then in the real Update() loop it’s just a big switch statement.

I would probably have entrance functions, such as:

void EnterAttack()
{
  mode = "Attack";
  gunStatus = GunStatus.AimDownSights;
}

It’s not perfect but you know exactly where to put the breakpoint!

And you can easily drive it with data external to this, such as changing the odds that he will engage or flee, etc.