views:

666

answers:

5

I've heard that coroutines are a good way to structure games (e.g., PEP 342: "Coroutines are a natural way of expressing many algorithms, such as simulations, games...") but I'm having a hard time wrapping my head around how this would actually be done.

I see from this article that coroutines can represent states in a state machine which transition to each other using a scheduler, but it's not clear to me how this applies to a game where the game state is changing based on moves from multiple players.

Is there any simple example of a game written using coroutines available? Or can someone offer a sketch of how it might be done?

+2  A: 

Two links that you might find interesting:

http://aigamedev.com/open/articles/coroutine-state-machine/

http://www.dabeaz.com/coroutines/ (search for 'task' in this page and pdf)

Sridhar Ratnakumar
+5  A: 

Coroutines allow for creating large amounts of very-lightweight "microthreads" with cooperative multitasking (i.e. microthreads suspending themselves willfully to allow other microthreads to run). Read up in Dave Beazley's article on this subject.

Now, it's obvious how such microthreads can be useful for game programming. Consider a realtime strategy game, where you have dozens of units - each with a mind of its own. It may be a convenient abstraction for each unit's AI to run as such a microthread in your simulated multitasking environment. This is just one example, I'm sure there are more.

The "coroutine game programming" search on Google seems to bring up interesting results.

Eli Bendersky
+4  A: 

One way coroutines can be used in games is as light weight threads in an actor like model, like in Kamaelia.

Each object in your game would be a Kamaelia 'component'. A component is an object that can pause execution by yielding when it's allowable to pause. These components also have a messaging system that allows them to safely communicate to each other asynchronously.

All the objects would be concurrently doing their own thing, with messages sent to each other when interactions occur.

So, it's not really specific to games, but anything when you have a multitude of communicating components acting concurrently could benefit from coroutines.

Ryan
+5  A: 

The most prominent case of coroutines are probally old graphical point&click adventure games, where they where used to script cutscenes and other animated sequences in the game. A simple code example would look like this:

# script_file.scr
bob.walkto(jane)
bob.lookat(jane)
bob.say("How are you?")
wait(2)
jane.say("Fine")
...

This whole sequence can't be written as normal code, as you want to see bob do his walk animation after you did bob.walkto(jane) instead of jumping right to the next line. For the walk animation to play you however need to give control back to the game engine and that is where coroutines come into play.

This whole sequence is executed as a coroutine, meaning you have the ability to suspend and resume it as you like. A command like bob.walkto(jane) thus tells the engine side bob object its target and then suspends the coroutine, waiting for a wakeup call when bob has reached his target.

On the engine side things might look like this (pseudo code):

class Script:
    def __init__(self, filename):
        self.coroutine  = Coroutine(filename)
        self.is_wokenup = True

    def wakeup(self):
        self.is_wokenup = False;

    def update():
        if self.is_wokenup:
          coroutine.run()            


class Character:
   def walkto(self, target, script):
       self.script = script
       self.target = target

   def update(self):
       if target:
           move_one_step_closer_to(self.target)
           if close_enough_to(self.target):
               self.script.wakeup()

               self.target = None
               self.script = None

objects = [Character("bob"), Character("jane")]
scripts = [Script("script_file.scr")]

while True:
    for obj in objects:
        obj.update()

    for scr in scripts:
        scr.update()

A litte word of warning however, while coroutines make coding these sequences very simple, not every implementations you will find of them will be build with serialisation in mind, so game saving will become quite a troublesome issue if you make heavy use of coroutines.

This example is also just the most basic case of a coroutine in a game, coroutines themselves can be used for plenty of other tasks and algorithms as well.

Grumbel
+1  A: 

It is quite common to want to use coroutines to represent individual actor AI scripts. Unfortunately people tend to forget that coroutines have all the same synchronisation and mutual exclusion problems that threads have, just at a higher level. So you often need to firstly do as much as you can to eliminate local state, and secondly write robust ways to handle the errors in a coroutine that arise when something you were referring to ceases to exist.

So in practice they are quite hard to get a benefit from, which is why languages like UnrealScript that use some semblance of coroutines push much of the useful logic out to atomic events. Some people get good utility out of them, eg. the EVE Online people, but they had to pretty much architect their entire system around the concept. (And how they handle contention over shared resources is not well documented.)

Kylotan
Coroutines have some synchronization problems, but only if their state cannot be consistent between yield() calls. Having an implicit lock around all the code between yield() calls can greatly simplify things.The biggest gotcha is that any time a routine is created, one should decide then and there whether the routine will always be able to complete before it has to yield(). If a routine's caller isn't expecting a routine to yield, it may not have things in a consistent state when the routine is called. Adding a yield() would be a breaking change.
supercat
The problem is that you typically want to be able to yield all over the place for such routines to be any use (as in UnrealScript where 'latent functions' yield implicitly), but this leads to combinations of coroutines interacting in unpredictable ways. eg. If one is a patrol coroutine saying "go north; look; go south; look; repeat;" and the other is a reaction routine saying "if (sound heard to the west) { go west; look; go east; } " then interleaving the two could result in the actor moving rather unpredictably. That's why UnrealScript only allows such functions in precisely one 'coroutine'.
Kylotan
And even if you only have 1 coroutine per actor, that still isn't sufficient to ensure the logic will always work at the language level: if your coroutine says: "draw sword; wait 1 second; attack opponent with sword" then what happens if the sword disappears due to you being disarmed by another player? In particular, what if your language implements that coroutine by using a local variable for a sword which now refers to a null object? It's very tricky to consider all these cases if you're using a general purpose language in its normal way.
Kylotan