coroutines are fun but it's a very obvious case of moving complexity elsewhere and with the cost of not knowing what is running at any one time.
What do you mean?
Correct me if i'm wrong, but the way coroutines are used is -
you have a list of N active coroutines
and a list of M coroutines which are suspended (and are going to get activated after X ms).
all coroutines are "updated" at once at a certain place in your main loop, so its easy to measure their runtime. And are comparable to N function calls + the overhead of setjmp, longjmp, and whatever else goes into storing/restoring the state of the stack
You can always add a debug string to each coroutine so you always know the names of the functions which are being "stepped through".
Its like having N suspended function calls that run on your main thread.
Where each yield statement is like adding a state in the state-machine without having to write code to branch into that state, or write the "struct" to hold all the state data - its all "described" in that single function, with state stored on stack.
It is like cheating. It certainly does feel like cheating.
Since it's running on a single thread there are no "surprising" side effects, unless you go into complete crazy town with them.
I find them invaluable when doing gameplay programming.
Super useful for cutscenes in RPG-like games, or scripted events of any sort, in pseudocode:
say_text("bla bla bla bla bla bla");
There's so much more that can be done with them.
(calling a coroutine from coroutine, spawning multiple coroutines in parallel and waiting for both of them to finish before continuing on, etc)
Super powerful construct for games.
If they can be implemented in C in a way which is not cumbersome to use, it's a definite win (I use them in C# for gameplay all the time)