Finite Improbability

Just programming and math, no spontaneously jumping undergarments

Picnic Invasion! Day 5

Worked on integrating the Mozilla Rhino javascript engine today. Here’s a brief example (in Java) of using it with slick:

/*
  This file demonstrates using the Rhino Javascript engine within the
  Slick 2d game engine.

  Rhino can be found at: http://www.mozilla.org/rhino/
  Slick can be found at: http://slick.cokeandcode.com/
 */

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ScriptableObject;


public class Rhino extends BasicGame {

    /*
      The ScriptProxy class defined here is one half of the interface
      between Java code and the Javascript environment. It is used to,
      among other things, provide access to Java objects within Rhino.

      The ScriptableObject class implements the majority of the
      Scriptable interface. The Scriptable interface is used to define
      functions and objects that will be available to the javascript
      environment.
     */
    class ScriptProxy extends ScriptableObject {
    String test1;

    /*
      This is the only method required to fully implement the
      abstract class ScriptableObject. I'm not sure what it is
      supposed to do. The "global" value is used in at least one
      example in the Rhino documentation.
     */
    public String getClassName() {
        return "global";
    }

    /*
      Access to the "test" object in the javascript environment is
      implemented through calls to the setTest and getTest
      methods.
     */
    public void setTest(String str) {
        test1 = str;
    }

    public String getTest() {
        return test1;
    }

    /*
      A test function that can be called within the JavaScript
      environment.
     */
    public void testfunc(String s) {
//      System.out.println("test function called");
        System.out.println(s);
    }
    }

    /*
      A Context is an instance of the javascript environment. The
      context is used to load and interpret .js files and interpret
      Javascript code as strings, among other things. In this example
      it is used exclusively to interpret Javascript strings.
     */
    protected Context scriptContext;
    protected ScriptProxy gameProxy;

    public void init(GameContainer container) throws SlickException {
    /*
      The static method Context.enter is, apparently, the easiest
      way to obtain a javascript environment.
     */
    scriptContext = Context.enter();
    gameProxy = new ScriptProxy();

    /*
      We'll set a string for the "test" property that will be
      exposed in Javascript now so we know it is calling across
      the bridge.
     */
    gameProxy.setTest("This was set within java, but called from javascript.");

    /*
      This must be called before scripts can be evaluated in this
      Context. It creates some of the basic Javascript objects.
     */
    scriptContext.initStandardObjects(gameProxy);

    /*
      Functions provided by a ScriptableObject (such as our
      example 'testfunc') are initialized in the following manner.
     */
    String[] scriptAvailableFunctions = { "testfunc" };
    gameProxy.defineFunctionProperties(scriptAvailableFunctions, ScriptProxy.class, ScriptableObject.DONTENUM);

    /*
      A "property" is a Javascript object that is exposed through
      getters and setters in the ScriptableObject. Rhino
      automatically prepends the "set" and "get" terms and
      uppercases the first letter, so exact naming is important.
     */
    gameProxy.defineProperty("test", ScriptProxy.class, ScriptableObject.DONTENUM);

    }

    public void render(GameContainer container, Graphics g) {
    /*
      Here we are using the context (javascript engine) to
      evaluate a string. The first parameter is our
      ScriptableObject, which is used to provide definitions for
      the engine. The second argument is the code to be evaluated.
      In this case simply writing "test" evaluates the Javascript
      object named "test", which is looked up in our gameProxy,
      which passed the result of it's getTest() method to the
      Javascript engine.

      Since this is the only instruction in the string to be
      evaluated it is used as the return value for
      evaluateString. To provide flexibility in return type
      handling evaluateString returns a java.lang.Object, which is
      why the result must be cast back into a String.

      The third parameter is a string that describes the source
      for the Javascript code being evaluated. This may be a
      filename if that is where the string comes from.

      The fourth parameter is the starting line number for this
      script, which might be useful if a script is being pieced
      together from a variety of components.

      The last parameter is used to provide a security domain for
      the script to run under. I *think* this is used to ensure
      that the Javascript code cannot execute certain methods or
      instantiate some objects, but haven't done enough research
      to be sure. Passing a null value here does not restrict the
      evaluation of Javascript code.
     */
    String result = (String) scriptContext.evaluateString(gameProxy, "test;", "js", 1, null);
    g.drawString("Javascript result: " + result, 40, 120);


    }

    public void update(GameContainer container, int delta) {

    }


    public void keyPressed(int key, char c) {
    if(key == Input.KEY_ESCAPE) {
        /*
          The script context will most likely be shut down
          properly, but it's a good idea to be neat and tidy,
          right?
         */
        scriptContext.exit();
        System.exit(0);
    }
    if(key == Input.KEY_SPACE) {
        /*
          This example is largely similar to the above, except
          that now we are calling the javascript function
          "testfunc()". This is found and executed on gameProxy,
          printing a string to the console."
         */
        scriptContext.evaluateString(gameProxy, "testfunc(\"testing console output\");", "js", 5, null);
    }
    }


    public Rhino(String s) {
    super(s);
    }

    public static void main(String[] Args) {
    try {
        AppGameContainer container = new AppGameContainer(new Rhino("Picnic Invasion!"));
        container.setDisplayMode(600,480,false);
        //       container.setShowFPS(false);
        container.setMinimumLogicUpdateInterval(30);
        container.start();
    } catch (SlickException e) {
    e.printStackTrace();
    }
    }
}
No comments

No comments yet. Be the first.

Leave a reply