Yet Another Wrapper: Rhino (JavaScript in Java)

For everybody that had to deal with Rhino, or was scared off when walking through the tutorials… how to mix Contexts in interpreted mode and how all those Scriptables interact with eachother, and all those other quirks.

I got sick of all the boilerplate code I had to write, so I simplified it so the extend of being maybe even a bit too easy :persecutioncomplex:

As always, sourcecode and JAR available:
http://www.katav.nl/html_stuff2/ => jawnae.js.*

Example 1: (creating a script)
String code = "var my = 'javascript code';"; JsScript script = new JsScript(code); script.execute();

Example 2: (creating a function)
String code = "function sum(a, b) { return a + b; }"; JsFunction func = new JsFunction(code); func.invoke(13, 14);

Example 3: (using a function from an external script)
`
JsScope shared = new JsScope();

String sumCode = “function sum(a, b) { return a + b; }”;
JsFunction func = new JsFunction(code, shared);

String useCode = “var x=3; var y=4; var z=sum(x, y)”;
JsScript script = new JsScript(useCode, shared);
script.execute();
script.eval(“x = y + z;”);`

Example 4: (green threads!!)
`
JsScope scope = new JsScope();
scope.setInterpretedContext(true);
scope.put(“out”, System.out);

String code = “”;
code += “out.println(“step 1”);”;
code += “Thread.yield();”; // continuation!
code += “out.println(“step 2”);”;
code += “Thread.sleep(1000);”; // continuation!
code += “out.println(“step 3”);”;
// thread1: code += “Thread.wait(‘this_is_a_lock’);”;
// thread2: code += “Thread.notify(‘this_is_a_lock’);”;

JsScript script1 = new JsScript(code);
JsScript script2 = new JsScript(code);

JsCPU cpu = new JsCPU();
cpu.addThread(new JsThread(script1));
cpu.addThread(new JsThread(script2));

while(cpu.hasThreads())
{
cpu.tick();
}
`

There is also a debug mode (JsStepper), so that you can read the (local) scope of a function while it is executing.

For more examples: http://www.katav.nl/html_stuff2/source/test/jawnae/js/JsUnitTest.html

Have fun!

somehow example 3 doesn’t jive with me.

Have you opted going for something like this?:


JsScope scope = new JsScope();
scope.addFunction("sum(a, b) { return a + b; }");
scope.executeScript("var x=3; var y=4; var z=sum(x, y)");
scope.executeScript("x = y + z;");

or probably better and more sophisticated:


JsScope scope = new JsScope();
scope.addFunction("sum(a, b) { return a + b; }"); // Convience method for scope.add(new JsFunction("sum(a, b) { return a + b; }"));
scope.addScript("var x=3; var y=4; var z=sum(x, y)"); // Convience method for scope.add(new JsScript("sum(a, b) { return a + b; }"));
script.eval("x = y + z;"); // execute scripts in the scope then eval the script.

Having to inspect the Function to see what scope belongs to kinda makes it useless and leaves it dangling. This way you can share functions among scopes though the way JS works this is probably bad(lets make a function an abstraction of a a function definition and we’re in the clear) Hopefully this would allow some form of caching of optimalisations under the hood somewhere.

Keep up the good work :slight_smile:

Well, that’s all possible, and easy to implement, because nothing changes under the hood.

scope.addScript(JsScript) makes little sense however, because you don’t have a handle to execute it, unless I return that, but then you end up with:

JsScript script = scope.addScript(code);
as opposed to
JsScript script = new JsScript(code, scope);

JsFunction and JsScript both extend JsScope, so those convenience methods really can get quirky:
scope.addFunction("code1").addFunction("code2").addFunction("code3");
which are nested scopes.

dont have much time to look at it right now but seems so cool that I answer just to track this thread :wink:

I added your suggestions, and I made some further changes.

scope.printProperties();
which is like PHP var_dump

It is now possible to modify the prototype of an Object.

`
String printlnCode = “”;
printlnCode += “println( obj )”;
printlnCode += “{”;
printlnCode += " if(typeof obj == ‘undefined’)";
printlnCode += " obj = ‘undefined’;";
printlnCode += " else if(typeof obj == ‘object’)";
printlnCode += " obj = obj.toString();";
printlnCode += " out.println(obj);";
printlnCode += “}”;

  JsScope scope = new JsScope();
  scope.putVariable("out", System.out);
  scope.addFunction(printlnCode);

  JsFunction vector = scope.addFunction("Vector( x, y, z ) { this.x=x; this.y=y; this.z=z; }");

  scope.eval("var vec1 = new Vector(3,4,5);");
  scope.eval("var vec2 = new Vector(8,5,2);");

  // this will now print "[Object object]"
  scope.eval("println('vector.toString() => '+Vector.prototype.toString);");
  scope.eval("println(vec1);");

  // modify the prototype
  JsScope prototype = vector.getPrototype();
  prototype.putVariable("isUnitVector", Boolean.FALSE);
  prototype.addFunction("add( that ) { this.x+=that.x; this.y+=that.y; this.z+=that.z; }");
  prototype.addFunction("toString()  { return this.x+','+this.y+','+this.z; }");
  prototype.printProperties(); // very useful for debugging

  // this will now print "{x},{y},{z}"
  scope.eval("println('vector.toString() => '+Vector.prototype.toString);");
  scope.eval("println(vec1);");
  scope.eval("println(vec2);");
  
  scope.eval("println('sum is: ');");
  scope.eval("vec1.add(vec2);"); // prototype function
  scope.eval("println(vec1);");

`

Thanks :slight_smile:

P.S.
You can scroll to the first post and press Notify to get… notified:
[Reply] [Notify] [Mark unread] [Send this topic] [Print]

Well my notion was that addScript simply adds inline javascript


function foo() { .. }
var inline = ...

and eval was run against the scope(as opposed to added and executed)

whats the difference between eval vs (addscript + execute)

that would be the equivialent of:

that’s screwy indeed. I suppose you could return this and if ppl need the handle they just need to create it manually. which would make if you wanted the above kinda scewy:

Or something it’s getting late :stuck_out_tongue: I’ll have a closer look tomorro

[quote]whats the difference between eval vs (addscript + execute)
[/quote]
addScript + execute compiles the javascript, eval interprets it - massive diff in performance.

with compiled javascript, you can get up to 10% of Java’s performance - which is surprisingly good.

Boy was I wrong…

No difference between [eval] and [script] at all…! Both are compiled, as long as the optimisation levels are set in the Context. Functions however are twice as fast!

Java code


      int count = 0;
      for (int i = 2; i < 7500; i++)
      {
         boolean isPrime = true;

         for (int k = 2; k < i; k++)
         {
            if (i % k == 0)
            {
               isPrime = false;
               break;
            }
         }

         if (isPrime)
         {
            count++;
         }
      }
      return count;

Javascript code


for(var i=2; i<7500; i++)
{
   var isPrime = true;
   for(var k=2; k<i; k++)
   {
      if(i%k==0)
      {
         isPrime = false;
         break;
      }
   }
   if(isPrime)
   {
      count++;
   }
}

`
js [script opt level 9] took: 848ms
js [script opt level 9] took: 834ms
js [script opt level 9] took: 796ms
js [script opt level 9] took: 839ms

js [script opt level -1] took: 2405ms
js [script opt level -1] took: 2537ms
js [script opt level -1] took: 2641ms
js [script opt level -1] took: 2246ms

js [function opt level 9] took: 438ms :o
js [function opt level 9] took: 420ms
js [function opt level 9] took: 399ms
js [function opt level 9] took: 328ms

js [function opt level -1] took: 1646ms
js [function opt level -1] took: 1967ms
js [function opt level -1] took: 1722ms
js [function opt level -1] took: 1670ms

java took: 27ms ::slight_smile:
java took: 27ms
java took: 26ms
java took: 22ms`

So in this example, with compiled functions, we’re dealing with factor 20 performance loss.
Interpreted scripts (required for continuations / green threads) are factor 100 slower.

Use with caution :slight_smile:

Update:
Appearantly not that bad:


Lua is about twenty to thirty times slower than C

If the main reason are continuations, you can use them in Java too: Continuations Library

And good thing is that performance is very good as most compute heavy methods can stay untouched, just the control code gets modified (thus somewhat slower).

Hrrr… everytime that link shows up. No, the continuations are not the main reason. This is a Javascript wrapper, as Rhino is just a pain in the ass to get up and running - it’s a lot of trial and error to figure things out. I ‘extended’ it with fancy Thread.* functionality, like wait/notify/notifyAll among green threads, yielding and sleeping.

Anyway, I’m not a huge fan of Java continuations. I’ve never needed them, they only come in handy when writing scripts, in which case I use Javascript.

I have good experience with using them in game code, it’s nice to have everything in Java as you can reuse the same stuff, etc. and can mix it without need to hard separate things. Of course it’s separated somehow, but the softer borderline allowed nicer overall architecture of my engine :slight_smile: