Live data from Hacker News

Looking at Unity made me understand the point of C++ coroutines

mropert.github.io

121–130 of 190 posts

Re: Looking at Unity made me understand the point of C++ coroutines

#121
post #35

Earlier quoted context omitted.

Echoing the thoughts of the only current sibling comment: lots of "serious" developers (way to gatekeep here) definitely use coroutines, when they make sense. As mentioned, it's one of the best ways to have something update each frame for a short period of time, then neatly go away when it's not needed anymore. Very often, the tiny performance hit you take is completely outweighed by the maintanability/convenience.

...and then crash when any object it was using gets deleted while it's still running, like when the game changes scenes, but it becomes a manual, error-prone process to track down and stop all the coroutines holding on to references, that costs much more effort than it saves. I've been a serious Unity developer for 16 years, and I avoid coroutines like the plague, just like other architectural mistakes like stringly…

So if you need to conditionally tick something or you want to wait for an effect to finish, etc., you're using Update() with if() statements?

The same code in a coroutine hits the same lifecycle failures as Update() anyway. You don't gain any safety by moving it to Update().

> No structured cancellation.

Call StopCoroutine with the Coroutine object returned by StartCoroutine. Of course you can just pass around a cancellation token type thing as well.

> Hidden allocation/GC from yield instructions.

Hidden how? You're calling `new` or you're not.

Instead of fighting them, you should just learn how to use coroutines. They're a lot nicer than complicated logic in Update().

Re: Looking at Unity made me understand the point of C++ coroutines

#122

Earlier quoted context omitted.

People love to complain about Rust async-await being too complicated, but somehow C++ manages to be even worse. C++ never disappoints!

I find C++ coroutines to be well-designed. Most of the complexity is intrinsic because it tries to be un-opinionated. It allows precise control and customization of almost every conceivable coroutine behavior while still adhering to the principle of zero-cost abstractions. Most people would prefer opinionated libraries that allow them to not think about the design tradeoffs. The core implementation is targeted at eff…

C++ standards follow a tick-tock schedule for complex features.

For the `tick`, the core language gets an un-opinionated iteration of the feature that is meant for compiler developers and library writers to play with. (This is why we sometimes see production compilers lagging behind in features).

For the `tock`, we try to get the standard library improved with these features to a realistic extent, and also fix wrinkles in the primary idea.

This avoids the standard library having to rely on any compiler magic (languages like swift are notorious for this), so in practice all libraries can leverage the language to the same extend.

This pattern has been broken in a few instances (std::initializer_list), and those have been widely considered to have been missteps.

Re: Looking at Unity made me understand the point of C++ coroutines

#123
I don't know, I'm not convinced with this argument.

The "ugly" version with the switch seems much preferable to me. It's simple, works, has way less moving parts and does not require complex machinery to be built into the language. I'm open to being convinced otherwise but as it stands I'm not seeing any horrible problems with it.

Re: Looking at Unity made me understand the point of C++ coroutines

#124

Earlier quoted context omitted.

> I'm not normally keen to "well actually" people with the C standard, but .. if you're writing in assembly, you're not writing in C. These days on Linux/BSD/Solaris/macOS you can use makecontext()/swapcontext() from ucontext.h and it will turn out roughly the same performance on important architectures as what everyone used to do with custom assembly. And you already have fiber functions as part of the Windows API t…

Unfortunately swap context requires saving and restoring the signal mask, which, at least on Linux, requires a syscall so it is going to be at least a hundred times slower than an hand rolled implementation. Also, although not likely to be removed anytime soon from existing systems, POSIX has declared the context API obsolescent a while ago (it might actually no longer be part of the standard).

Signal mask? What century are we in?

It can be safely ignored for the vast majority of apps. If you're using multithreading (quite likely if you're doing coroutines), then signals are not a good fit anyway.

Re: Looking at Unity made me understand the point of C++ coroutines

#125

Earlier quoted context omitted.

I'll take the bait. Here's a coroutine waitFrames(5); // wait 5 frames fireProjectile(); waitFrames(15); turnLeft(-30/*deg*/, 120); // turn left over 120 frames waitFrames(10); fireProjectile(); // spin and shoot for (i of range(0, 360, 60)) { turnRight(60, 90); // turn 60 degrees over 90 frames fireProjectile(); } 10 lines and I get behavior over time. What would your non-coroutine solution look like?

Given a coroutine body ``` int f() { a; co_yield r; b; co_return r2; } ``` this transforms into ``` auto f(auto then) { a; return then(r, [&]() { b; return then(r2); }); }; ``` You can easily extend this to arbitrarily complex statements. The main thing is that obviously, you have to worry about the capture lifetime yourself (coroutines allocate a frame separate from the stack), and the syntax causes nesting for ever…

How is this better than the equivalent coroutine code? I don't see any upsides from a user's perspective.

> The main thing is that obviously, you have to worry about the capture lifetime yourself

This is a big deal! The fact that the coroutine frame is kept alive and your state can just stay in local variables is one of the main selling points. I experienced this first-hand when I rewrote callback-style C++ ASIO code to the new coroutine style. No more [self=shared_from_this()] and other shenanigans!

Re: Looking at Unity made me understand the point of C++ coroutines

#126

Earlier quoted context omitted.

You are describing a single async step, not a state machine. "Create a promise, set it when done", that's one state. A real async state machine has N states with transitions, branching, error handling, and cleanup between them. > "The only 'assembly' required is creating the associated promise" Again, that is only true for one step. For a state machine with N states you need explicit state enums or a long chain of .t…

I only mentioned co_yield() since that's what the article was (ab)using, although perhaps justifiably so. It seems the coroutine support was added to C++ in a very flexible way, but so low level as to be daunting/inconvenient to use. It needs to have more high level facilities (like Generators) built on top. What I was thinking of as a state machine with using std::future was a single function state machine, using sw…

> as to be daunting/inconvenient to use

I don't even know how to respond to that. How in the world are you using C++ professionally if you think coroutines are "daunting"? No one uses C++ for it's "convenience" factor. We use it for the power and control it affords.

> What I was thinking of as a state machine with using std::future was a single function state machine, using switch (state) to the state specific dispatch of asynch ops using std::future, wait for completion then select next state.

Uh huh. What about error propagation and all the other very real issues I mentioned that you are just ignoring? Why not just let the compiler do all the work the way it was spec'ed and implemented?

Re: Looking at Unity made me understand the point of C++ coroutines

#127

Earlier quoted context omitted.

Unfortunately swap context requires saving and restoring the signal mask, which, at least on Linux, requires a syscall so it is going to be at least a hundred times slower than an hand rolled implementation. Also, although not likely to be removed anytime soon from existing systems, POSIX has declared the context API obsolescent a while ago (it might actually no longer be part of the standard).

Signal mask? What century are we in? It can be safely ignored for the vast majority of apps. If you're using multithreading (quite likely if you're doing coroutines), then signals are not a good fit anyway.

Aside from the fact that the signal mask is still relevant in 2026 and even for multithreaded programs, that doesn't have anything to do with the fact that POSIX requires swapcontext to preserve it.

Re: Looking at Unity made me understand the point of C++ coroutines

#128

Earlier quoted context omitted.

People love to complain about Rust async-await being too complicated, but somehow C++ manages to be even worse. C++ never disappoints!

async is simply a difficult problem, and I think we'll find irreducible complexity there. Sometimes you are just doing 2 or 3 things at once and you need a hand-written state machine with good unit tests around it. Sometimes you can't just glue 3 happy paths together into CSP and call it a day.

It's quite simple in Golang.

Re: Looking at Unity made me understand the point of C++ coroutines

#129

You can roll stackful coroutines in C++ (or C) with 50-ish lines of Assembly. It's a matter of saving a few registers and switching the stack pointer, minicoro [1] is a pretty good C library that does it. I like this model a lot more than C++20 coroutines: 1. C++20 coros are stackless, in the general case every async "function call" heap allocates. 2. If you do your own stackful coroutines, every function can suspend…

A much nicer code base to study is: https://swtch.com/libtask/ The stack save/restore happens in: https://swtch.com/libtask/asm.S

Single OS thread only, FWIW (no M:N scheduling). And like any stackful implementation, requires relatively huge stack allocations if you actually call into stdlib, particularly things like getaddrinfo().

Re: Looking at Unity made me understand the point of C++ coroutines

#130

You can roll stackful coroutines in C++ (or C) with 50-ish lines of Assembly. It's a matter of saving a few registers and switching the stack pointer, minicoro [1] is a pretty good C library that does it. I like this model a lot more than C++20 coroutines: 1. C++20 coros are stackless, in the general case every async "function call" heap allocates. 2. If you do your own stackful coroutines, every function can suspend…

Stackful makes for cute demos, but you need huge per-thread stacks if you actually end up calling into Linux libc, which tends to assume typical OS thread stack sizes (8MB). (I don't disagree that some of the other tradeoffs are nice, and I have no love for C++20 coroutines myself.)
Post reply on HN