Skip to content
Tech News
← Back to articles

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

read original get Unity C# Coroutine Guide → more articles
Why This Matters

This article highlights the practical use of C++ coroutines through Unity's implementation in C#, illustrating their potential to simplify asynchronous and ephemeral behaviors in game development. Understanding these real-world applications can inspire broader adoption of coroutines in C++, leading to more efficient and manageable code in complex projects.

Key Takeaways

Looking at Unity finally made me understand the point of C++ coroutines I had seen many talks about coroutines but it never really clicked where I could use them outisde of async IO. Until I looked at how Unity uses them in C#.

Coroutines have been around in C++ for 6 years now. And still I have yet to encounter any in production code. This is possibly due to the fact that they are by themselves a quite low-level feature. Or more precisely, they’re a high level feature that requires a bunch of complex (and bespoke) low-level code to plug into a project. But I suspect another, even bigger, issue with the coroutines rollout in C++ has been the lack of concrete examples. After all, how often do you need to compute Fibonacci in real life?

Recently, I have been looking at Unity, which mostly uses C# for client gameplay code (you can do C++ but it’s uncommon). And more specifically, I ran across their usage of coroutines for spawning effects and other ephemeral behaviours. Here’s an example from the manual I’ll reproduce here for the purpose of illustrating this article:

void Update () { if ( Input . GetKeyDown ( "f" )) { StartCoroutine ( Fade ()); } } IEnumerator Fade () { Color c = renderer . material . color ; for ( float alpha = 1f ; alpha >= 0 ; alpha -= 0.1f ) { c . a = alpha ; renderer . material . color = c ; yield return null ; } }

C# and/or coroutines purists might take offense at this usage of yield . After all the semantics are all wrong here. We’re yielding nothing where we’re trying to express something akin to await NextFrame() . From what I could read this is an artifact inherited from a lack of await support when they were initially added to C# (they only supported generator style yield ), which led Unity to use this hack which is still around today. I am not only mentioning it as a random piece of historical trivia, this will become relevant later.

Why coroutines?

This example is still a bit basic and might not make it immediately apparent why we would prefer to write our effects this way. After all, this could be made into a simple lambda with a mutable alpha variable that we would nudge each call. But let’s try with a slightly more complex effect:

IEnumerator TimeWarp () { // It's just a jump to the left transform . position . x -= 1.f ; yield return null ; // Then a step to the right for ( int i = 0 ; i < 4 ; ++ i ) { transform . position . x += 0.2f ; yield return null ; } // Put your hands on your hips // ... // Let's do the time warp again! for ( int i = 0 ; i < 4 ; ++ i ) { transform . Rotate ( 0.f , 90.f * i , 0.f ); yield return null ; } }

Now it would become actually painful to turn this into a regular functor or lambda. Writing it in C++ turns it into some sort of ugly state machine like this:

class TimeWarp { enum class State { Jump , StepRight , HandsOnHips , // ... DoAgain }; State _state = State :: Jump ; int _i = 0 ; Transform * _transform ; TimeWarp ( Transform & transform ) : _transform ( & transform ) {} bool operator ()() { switch ( _state ) { case State :: Jump : _transform -> position . x -= 1. f ; _state = State :: StepRight ; break ; case State :: StepRight : _transform -> position . x += 0.2 f ; if ( ++ _i == 4 ) { _state = State :: HandsOnHips ; _i = 0 ; } break ; // ... case State :: DoAgain : _transform -> Rotate ( 0. f , 90. f * i , 0. f ); if ( ++ _i == 4 ) { // Indicate we're done return true ; } break ; } return false ; } }

... continue reading