In games it is very common to have scripted sequences - for instance, a piece of dialogue or a cutscene. I don’t mean these sequences are written in a scripting language (though they might be), but that they follow a script like a play or a movie does. Of course, contrary to a movie script in a game the script may have some conditional branches (an NPC only saying something if the player choses to ask a certain question, for instance).
Expressing such a sequence in code should be straight-forward – after all, computer code is very much like a script. However, these scripts often span minutes and at the same time a bunch of other things are also happening (sound, animations, ...). The obvious solution to this is to put the script in a separate thread - but now you open yourself up to race conditions and other nasty thread-related bugs. So what to do?
One solution is a finite-state machine. But rewriting a script as a state-machine is always a pain in the ass and the resulting code is much harder to comprehend.
A much nicer solution, and the one we will be focusing on here, is the coroutine. A coroutine is, simply put, like a function you can jump in and out of. This means you can execute a piece of the routine (one line of the script), return to the main thread, then later continue in the coroutine where you left off. This means it acts much like a thread does but only runs when asked to, and gives back execution to the calling thread cooperatively.
Coroutines are an integral part of many languages, such as Lua, but if you have decided to make your game in pure C++, you are out of luck. The third-party library implementations you’ll find (like the ones in boost) often focus on the use case of having thousands of coroutines, meaning they must be very lightweight. This focus on performance comes at the cost of easy-of-use and portability.
For scripted sequences in games you may only need one or a few coroutines running at the same time. This completely removes the constraint on them being light-weight. For this use case I decided to create a very simple coroutine implementation that is just a wrapper around a
std::thread but with mechanisms to pass the execution from the outer (owning) thread to the inner (coroutine) thread, so that only one thread is running at once. The advantage of using a proper thread is that there is no limit what we can do from the thread. It also plays well with many other tools, like the debugger which shows all running threads and where they are at. Since the calling thread is paused when a coroutine is executed there is no need for mutexes or other syncing of the game state.
In the end, this allows us to write code like this: