Newsflash

A good friend has spent his lunch time to create a new page header for my new site. You can already see it - I hope you like it.
 

Polls

How do you like JDK6 ?
Home arrow Java Code arrow Java arrow Continuations Library
Continuations Library PDF Print
Written by Matthias Mann   
This small library allows you to write your game AI or animation code in a simple sequential way, blocking on commands like walkTo() and all this without blocking your AI thread or the need for additional threads.

Some of you might be familiar with the features of scripting languages like Lua or AngelScript. One of these features are Coroutines. These allow a control transfer that is beyond a simple if/else or goto. Somewhere you define the entry point - the coroutine - and while executing inside the coroutine you can suspend your code and return to the coroutine call. This is comparable to stack unwinding of exceptions. But coroutines offer you the unique feature to return to the state where you suspended execution. This allows you to write code in a simple sequential way that would otherwise require either it's own thread or a hand written state machine.

But I'm writing this for the Java rubric - and this feature is not part of your standard Java runtime today. This library offers you a very easy and still power full way to use these features in Java without affecting your code by intrusive requirements.

Lets start with what you get:

  • Write simple sequential code - you no longer need to create state machines by hand
  • No Threads are created or needed - no multi thread synchronization issues
  • No garbage creation from code execution
  • Very small runtime overhead
    Only suspendable method calls are changed - all calls into your standard library (like java.util.* etc) are not affected at all.
  • Full serialization support
    You can store the execution state of coroutines as part of your game state in your save game without any additional code. This of course requires that your classes and data types which you use in your coroutines are serializable.
  • Full support for exception handling and finally blocks
  • Offline preprocessing does not slow down you application load time
    Of course runtime instrumentation is also possible.
  • Very small runtime library - less then 10 KByte (uncompressed JAR)
  • BSD License

With all these great features - you may be asking for the drawbacks. Well there are of course a few drawbacks:

  • Constructors and static initializers can't be suspended
  • Suspendable methods can't be synchronized or have synchronized blocks
  • You need to download ASM3 library to run the instrumentation task
  • You can't call suspendable method with reflection
  • The synchronization issue can be worked around by putting code which requires the use of synchronization into it's own method.

    Lets look at an example - an iterator. Writing iterators can be quite complex - especially if the implementation is more then just a table walk. This library comes with a utility class that allows you to write iterators in a simple sequential way - also known as producers. It is based on a JUnit test which is also distributed with the source code of this library.

    public class TestIterator extends CoIterator<String> implements Serializable {
        @Override
        protected void run() throws SuspendExecution {
            produce("A");
            produce("B");
            for(int i = 0; i < 4; i++) {
                produce("C" + i);
            }
            produce("D");
            produce("E");
        }
    }

    This class extends the class CoIterator which provides the produce method and implements all methods of the Iterator interface.
    The run method is an abstract method from CoIterator which is the body of our iterator. This method is declared to throw the special exception SuspendExecution. This exception must never be caught by any user code - it must be always propagated. But don't fear - you can still use the infamous catch(Throwable) if you like. This exception is the marker for suspendable methods - it tells the preprocessor that this method calls others methods which might suspend execution. In our example each call to produce will suspend execution until the consumer (the caller of next) has consumed the produced value. Let's take a look at code that uses our Iterator:

    Iterator<String> iter1 = new TestIterator();
    while(iter.hasNext()) {
        System.out.println(iter.next());
    }

    Which will produce the following output:

    A
    B
    C0
    C1
    C2
    C3
    D
    E

    This looks very familiar - it's your everyday iterator usage. Of course it also works with an enhanced for loop if the Iterator is returned from a class which implements Iterable.

    But you can also write this with standard Java code without support from this library:

    public class TestIterator implements Iterator<String>, Serializable {
        private int state;
        private int i;
        public String next() {
            switch(state) {
                case 0: state=1; return "A";
                case 1: state=2; i=0; return "B";
                case 2:
                  if(i == 3) state = 3;
                  return "C" + (i++);
                case 3: state=4; return "D";
                case 4: state=5; return "E";
                default: throw new NoSuchElementException();
            }
        }
        public boolean hasNext() {
            return state < 5;
        }
        public void remove() {
            throw new UnsupportedOperationException("Not supported");
        }
    }

    That is much more complex - and if your code gets more complicated it will be very hard to create a state machine for it. But with the help of this library you can spend your time to write your application.

    An important point is that the underlying Coroutine is only executed when hasNext() or next() of the Iterator is executed. hasNext() will return false if the run() method finishes it's execution. It also means that if you don't consume all values that the Iterator returns that the run() method of your CoIterator subclass never get the chance to finish it's execution - which includes any not yet executed finally blocks.

    If you look closely at our TestIterator class you will see that it also implements Serializable which means that you can store the iterator in a file. See the JUnit test for the complete example.

    To use the ANT task you need the full JAR and the full ASM3 JAR. It also works with ASM 3.1. All dependencies of your application needs to be in the class path of the instrumentation ANT task - otherwise it can't analyze the method calls. You can safely run this task over already instrumented class files - it will detect already transformed classes and skip them. This example ANT snippet assumes the Netbeans build properties:

    <taskdef name="continuations"
        classname="de.matthiasmann.continuations.instrument.InstrumentationTask"
        classpath="Continuations_full.jar:asm-all-3.0.jar:${run.classpath}"/>
     
    <target name="-post-compile">
        <continuations verbose="true">
            <fileset dir="${build.classes.dir}"/>
        </continuations>
    </target>

    The embedded fileset is used to specify the list of classes that should be instrumented. The task also has a few parameters that can be used to alter it's behavior:

    AttributeDescriptionRequiredDefault
    verboseOutputs informations about each processing stepNofalse
    checkIncludes the ASM verifier in the transformation chain. Mostly a debugging option.Nofalse
    debugOutputs internal and detailed information about the processing of the class files.Nofalse

    Enough with the talk for now - here is the link to the library:

    So have fun and let me know if you find it useful

    Comments
    Add NewSearch
    Only registered users can write comments!
    rrmckinley Unregistered | 2011-01-14 02:03:52
    Evaluated with other coroutine solutions (and attempted solutions) here: http://stackoverflow.com/questions/2846428/available-coroutine-libraries-in-java/4687050#4687050
    Elias Naur Unregistered | 2008-08-18 23:55:39
    Today, I managed to eliminate most of the nasty state machine code in Tribal Trouble 2 and replace it with much simpler and smaller continuations style code. Very nice!
    Elias Naur - Nice! Unregistered | 2008-04-05 16:21:47
    I've already integrated it into our next version of Tribal Trouble to be able to replace the nasty state machine logic in the AI with cleaner code. Thanks!
    Last Updated ( Sunday, 06 January 2013 )
     
    < Prev   Next >
    2014 Matthias Mann, www.matthiasmann.de
    Joomla! is Free Software released under the GNU/GPL License.