Tutorial: Continuations in Mozilla Rhino (a JavaScript interpreter for Java)

December 26th, 2011 by Josh
1

I normally post only about my own game development projects, but this is going to be an exception. This is going to be a tutorial on how to use “continuations” (pausing and resuming scripts) in Mozilla Rhino. It took me days to get this to work, because I wasn’t able to find a complete example.

I’ll also be showing, through this example, how to automatically bind all the functions of a given Java class into JavaScript. Thus, any function you write in that class (example: ScriptFunctions.pause()) will be usable as a global function in JavaScript (example: you can call the function from JavaScript simply as pause(). There’s no need to call it as ScriptFunctions.pause()). This is extremely convenient.

Note: My example will be assuming that you will be placing all of your JavaScript code into functions and calling them as you need them (as opposed to having the script do something useful as soon as it’s loaded). For example, imagine that you’re developing an RPG (role-playing game), and your JavaScript file represents an NPC (non-playable character): you can define a JavaScript function “onSpeak()” that will be called when the player speaks to that NPC.

You will need to download and install Rhino from the Mozilla Rhino website, because although Rhino is built into the Java Development Kit (JDK)’s ScriptingEngine, the version of Rhino built-in is outdated and doesn’t support continuations (at this time of writing). So, download the source code from the site, and import it into your project. You should have an org.mozilla.javascript package in your project now that you can import.

Here’s the code to initialize Rhino, load the JavaScript script from a file myscript.js, and bind all the functions in a ScriptFunctions Java class as global functions in JavaScript:

        // init Rhino (JavaScript scripting engine)
        Context cx = Context.enter();
        cx.setOptimizationLevel(-1); // use interpreter mode (necessary for continuations)
        scope = cx.initStandardObjects();

        // load the .js file
    	InputStream is = this.getClass().getResourceAsStream("myscript.js");
    	Reader reader = new InputStreamReader(is);
        cx.evaluateReader(Game.scope, reader, "myscript.js", 1, null);

	// bind all the functions in the ScriptFunctions.java class into JavaScript as global functions
	scope.put("scriptfunctions", scope, new ScriptFunctions());
	cx.evaluateString(scope, " for(var fn in scriptfunctions) { if(typeof scriptfunctions[fn] === 'function') {this[fn] = (function() {var method = scriptfunctions[fn];return function() {return method.apply(scriptfunctions,arguments);};})();}};", "function transferrer", 1, null);

Contents of myscript.js:

function myJSFunction()
{
	// your code to execute before the pause goes here
	pause(); // this is bound to a Java function that does the dirty work of pausing the script/storing the continuation
	// your code to execute after the pause goes here

}

Contents of ScriptFunctions.java (you can define more functions to do whatever you want, but the pause() function is for continuations):

import org.mozilla.javascript.*;

public class ScriptFunctions
{
	public void pause()
	{
        Context cx = Context.enter();
        try {
            ContinuationPending pending = cx.captureContinuation();
            pending.setApplicationState(1);
            throw pending;
        } finally {
            Context.exit();
        }
	}
}

Now, finally, we get to the fun stuff. This Java code will show you how to call your JavaScript function from within your Java code, catch any continuations (“pauses”), and then resume the script execution:

	Function f = (Function)(scope.get("myJSFunction", scope));
	cx.setOptimizationLevel(-1);
	try
	{
		cx.callFunctionWithContinuations(f, scope, new Object[1]);
	}
	catch (ContinuationPending pending)
	{
		System.out.println("The script was paused!");
		System.out.println("Resuming the script...";
		int saved = (Integer)pending.getApplicationState();
		cx.resumeContinuation(pending.getContinuation(), scope, saved);
	}

Of course, in regards to that last chunk of code, there’s not much point in resuming the script as soon as you pause it — I’m doing that for the sake of keeping the example simple. In a real scenario, what you would normally want to do upon catching the ContinuationPending is store the ContinuationPending’s information, and later use that information to resume the script at your leisure.

Useful links for extra reading:

Mozilla’s tutorial on Rhino continuations

Another full example of Rhino continuations (doesn’t load the script from a file, and doesn’t automatically bind a class of functions)

Rhino group on Google Groups (helpful people are here who can answer your questions)

Rhino tag on Stack Overflow (to see if anyone on Stack Overflow has posted a question that can be useful to you)

Posted in Programming tutorials

One Response to “Tutorial: Continuations in Mozilla Rhino (a JavaScript interpreter for Java)”

  1. Paul says:

    V8 is really amaizng to work with. I modified the provided shell to accept shebang notation and read and write files in less than a day. My goal is to add support for include and require so that scripts run in isolated namespaces, as with modules in Chiron, so that the standard library I’m building can be used both server and client side. Ultimately, I think that these projects need to be about writing AJAX features once, not twice nor in different languages, for the server and client.I attended Steve Yegge’s talk at Google IO. I believe, since he’s made extensions to Rhino that definitely don’t work on the client side, that the primary interest in JavaScript on the server is to provide a better balance between quick and easy fringe features and robust and hard features. That’s a good point, but not as painful and obvious in systems where both the server and client are in dynamic languages. I think that the goal should be portability and sharing code with no necessary modification.

Leave a Reply