1
\$\begingroup\$

I'm working on a game, and as a typical game (here using Monogame) it uses frame based approach, where all logic happens in Update() method called every frame.

As a consequence, any sequential logic of operation, like going from one level to another, moving some entity to a different place over time or having a bunch of entity movements happening one after another has to be implemented in some complicated way, using states and finite state machines to track what is where. I obviously can't do anything like this:

foreach (var level in Levels)
{
    while (!level.LevelCompleted)
    {
        level.ShowIntroduction();
        WaitForClick();

        level.ShowMapOverview();
        WaitForClick();

        level.Run();

        level.ShowResult();
        WaitForClick();
    }
}

(might be little dumb example, but you get what I mean)

This while and foreach would block everything. But the thing is, it is very often the best way to describe the algorithm behind, way better than state machines, and these algorithms do get complicated sometimes.

And I just learned a lot about TPL recently and WOOOOOWW, what a great thing it is. Now I can do this. I tested this already both for simple few second actions of moving things in a game, as well as for complete control of scenes and intermissions, where one async method takes minutes or even hours. It can look like this:

foreach (var level in Levels)
{
    while (!level.LevelCompleted)
    {
        level.ShowIntroduction();
        await WaitClickAsync();

        level.ShowMapOverview();
        await WaitClickAsync();

        await level.RunAsync();

        level.ShowResult();
        await WaitClickAsync();
    }
}

To make this work, I have some AsyncManager, which allows me to wait for next frame, like this:

private async Task WaitClickAsync()
{
    while (true)
    {
        await asyncManager.WaitNextFrameAsync();

        if (World.Keyboard.JustPressed.Contains(Keys.Space))
            return;
    }
}

This thing await asyncManager.WaitNextFrameAsync() is used to break the execution of a method and continue it at the next frame. This makes it very easy to make code like above, or to make movement of entities etc. All other stuff in Update() still works, things gets rendered, and all is done without using any additional threads.

Of course in reality it needs to be more complicated than this, to support cancellation of these async methods, but TPL seems to have good support for this.


So first of all I wanted to share it with. And then ask the following, before I start modifying all of my game logic to use TPL:

  • am I not going in trouble using it everywhere in my game? I'm not really sure if TPL designers really thought about using it for such scenarios, and have those methods run for hours. Any cons?
  • what could the price in terms of performance be, if anything significant? Am I going to get some problem here?
\$\endgroup\$
  • 1
    \$\begingroup\$ I think it's generally easier to keep your game loop in a single thread. You may run into synchronization issues by splitting your game loop into multiple threads like this. \$\endgroup\$ – milk Jul 22 '16 at 21:15
  • 1
    \$\begingroup\$ @milk: it's asynchronous, but not multithreaded. \$\endgroup\$ – Arek Jul 22 '16 at 21:57
  • \$\begingroup\$ A Task is a separate thread. If you're not actually creating any Tasks anywhere then you're code is synchronous even if you're using the async/await keywords (the compiler may not be necessarily be smart enough to pick up on synchronous code marked async). await can almost be thought of as syntactic sugar around Task.ContinueWith(). \$\endgroup\$ – milk Jul 22 '16 at 22:29
  • \$\begingroup\$ A task might have a separate thread, but doesn't have to. Of course I can't say for sure what .net is doing under the hood, but I'm pretty confident my solution doesn't create new threads. \$\endgroup\$ – Arek Jul 22 '16 at 22:40
  • \$\begingroup\$ @Arek - You most certainly do need to worry about synchronization issues. While it is a bit more complicated than this it is usually sufficient to think of async/await as syntactic sugar for firing off a new thread and returning from the function that called await. The remaining code (the stuff after await) is then executed in response to the async thread completing (like a callback function). IOW, if your UI thread kicks off an await there is nothing stopping both the UI thread and the await thread from modifying the same variable. \$\endgroup\$ – Dunk Oct 20 '16 at 14:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.