-
Notifications
You must be signed in to change notification settings - Fork 5
Home
I, Simon Ferquel, am a humble developer working mostly in Microsoft environments using mostly C++ and C#. I picture myself as an experienced developer but not as some kind of guru or any warden of knowledge. This paper is the result of some thinking I have had lately, and might contain some wrong statements. It's published on Github on purpose, to receive feedback, pull requests and contributions. So feel free to interact, complete or correct me !
Additionally, I am actually not a native English speaker. I found the subject so interesting that I thought writing this paper in English was worth the effort. So please excuse the expected English-related issues (and please submit corrections!)
This sample requires Visual Studio 2015 preview (as it's the only compiler out there to implement await). It has also been built on top of Windows 8.1 SDK (but nothing specific to Windows 8/8.1 is used in the code, so it should not require too much work to make it build for Windows 7 and the DirectX 11 SDK).
The code associated with the article can be cloned or forked from the github project : https://github.com/simonferquel/AwaitInGameLoopSample.
The await keyword and resumable functions form a proposal for a future version of C++. You can see the last version of the paper here : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4134.pdf. Await is traditionally understood as some magic stuff that makes source code seem sequential (with all the language structures that we love in sequential code, like loops, conditional statements,...) but behave asynchronously that does not block. It's fantastic for asynchronous IO, it's fantastic to synchronize various asynchronous processes (like reading asynchronously a block from a file, then pushing the block asynchronously to a web API and so on until the file is completely processed), and it's pure awesomeness in terms of readability and maintainability. Await appeared a few years ago in C# and it has dramatically changed the way C# developers code asynchronous workflows, and many developers who were previously afraid of asynchronous development have changed their mind thanks to await.
So why this strange sample about await in a potentially high performance 60 frame per second game loop ? It's because of how await works in terms of extensibility, and how it relies on a generalization of the coroutine concept (await is not tied to threading and async I/O), and how this could help game developers write simpler and leaner game logic related code with little to no negative impact on performance (and in more mature implementations, perhaps performance boosts).
To answer this question, first we need to remember what is a routine. A routine is something we can trigger, that runs until completion, and can optionally produce a result. To put it clear, a routine is any kind of synchronous API or custom function. A coroutine, is something we can trigger that will eventually complete and optionally produce a result. The important word is eventually. So a call to std::async can be considered as the invocation of a coroutine, as well as any call to an asynchronous I/O related API, or anything returning some kind of promise (Microsoft PPL tasks, Intel threading block tasks, std::future,...).
Asynchronous I/O and Tasks are not the only types of coroutine. Await works perfectly with them, but not only them. Anything that would not synchronously complete is a coroutine: a timer is a coroutine (completing eventually when its time has elapsed), an animation is a coroutine (completing eventually when it has reached its final state). We can even think of "expected user interaction" as a coroutine (in a game, for example, showing a pause overlay could be thought of as a coroutine completing eventually when the user clicks the "resume" button). In a game the whole game logic could also be considered as a coroutine executing alongside the game loop. The code related to this paper is a single-threaded DirectX based application illustrating exactly that: it shows how await can allow a developer to express the game logic in a simple, very clean, and sequential looking way, even if at runtime it will behave as a state machine updated 60 times per second by the game loop.
The program is built like a single-threaded game around a classic Update/Render loop. The "game logic" is quite simple, it's an interaction loop with the following body:
- Make a message appear using an fade-in animation
- then wait for the user to click
- then make the message disappear using a fade-out animation
- then change the window background color randomly
- then wait for half a second before the next loop run.
The keyword there is "then". There is clearly some kind of synchronization implied. The second important thing is the notion of time (fade-in and fade-out animation time, the 0.5 sec timer, and the time waiting for the user to click) in most of the different steps, and the fact that the game logic must run inside a game loop that should run at 60 frame per second. Obviously we cannot write classic synchronous code like this:
for(;;){
message->fadeIn().WaitForCompletion();
engine->blockUntilUserClick();
engine->changeBackground();
sleep(500);
}
because it would block the game loop and the game would never render. We need to create some kind of a state machine accepting timing events, animation state events and user input events to implement this user interaction loop. But it would come at the price of less clear code (reading a complex state machine-like game logic component is not as easy as reading a simple loop). But thanks to the way await works to create coroutines from other coroutines with suspend/resume points, the final result looks almost exactly as the pseudo code above:
GameAwaitableSharedPromise<void> gameLogic(Engine* engine) {
auto animatedText = std::make_shared<AnimatedText>();
engine->addSceneObject(animatedText);
std::default_random_engine re((unsigned int)(std::chrono::steady_clock::now().time_since_epoch().count()));
std::uniform_real_distribution<float> dist(0.0f, 0.7f);
while (true) {
__await animatedText->fadeIn();
__await engine->waitForMouseClick();
__await animatedText->fadeOut();
engine->changeBackground(DirectX::XMFLOAT4(dist(re), dist(re), dist(re), 1.0f));
__await engine->waitFor(duration_cast<steady_clock::duration>(.5s));
}
}
First we have to understand what await does. In a resumable function, an await expression takes any awaitable coroutine object, and suspends the execution of the function until the coroutine completes (this suspension is the reason why a resumable function is itself a coroutine and must return an await compatible coroutine object).
So what is an awaitable coroutine object ? It's simply any object that support 3 simple free functions :
-
bool await_ready(TCoroutine)
is called before suspending (to check if suspension is really needed). -
void await_suspend(TCoroutine, callback)
is called on suspension. the callback is a simple functor object that must been called on the coroutine completion. -
T await_resume(TCoroutine)
is called on resuming and must return the value produced by the coroutine (or propagate an exception)
So what we have to do is make animatedText->fadeIn()
, engine->waitForMouseClick()
and engine->waitFor()
return an object supporting these 3 functions. GameAwaitablePromise.h contains classes that supports them : GameAwaitableSharedPromise that can be copied between different components (each copy points to a common shared state), and GameAwaitableUniquePromise that produce less overhead but can't be copied nor moved (but can be returned by pointers - pointers to GameAwaitableUniquePromise support await_* functions).
These objects support the await part of the contract, but they also act as promise handles. That is, they provide a way for an actor to notify its completion using a setResult method. So for example, if we take the animation case (see animation.h), that is obviously a state machine (at each frame it interpolates a value from a start state to a end state). It provides a GameAwaitableUniquePromise object that will be notified as complete on the frame that will complete the animation (thus resuming the execution of our game logic). The same thing as been done for the timer (implemented as a state machine updated at each frame), and for the user click coroutine (completion is signalled to all the awaiters via a GameAwaitableUniquePromise completion).
The setResult method will call the callback given when await_suspend is called, and the await expression will resume.
So now, we have demystified await and we also explained how to build things on top of it that are asynchronous, but not necessarily in a way involving threads, asynchronous I/O or task based parallelism. As it did with C#, I have no doubt that await will have a tremendous impact on how we write asynchronous code bases, and the way it allows to create our own awaitable data structures or adapt existing promise-like structures brings so much to the table, that the variety of possible applications seem huge !
Next time, I'll write about funny things that we could do with generators (a concept presented in the same paper as await and using the same kind of tricks).