Jump to content
Sign in to follow this  
Slapstick

Take on Groovy

Recommended Posts

Few random remarks:

  • the engine should be be able to run code written in any programming language that can compiled for Java Virtual Machine but we did not experiment with it so far ( quite few, Logo anyone ;) )
    up

This is the one comment that got me the most excited so I've decided to be one of the people to help test this claim. My language of choice is Groovy and so far I can report some success; I can use .class files generated from Groovy source code, but there are some problems.

While Groovy does compile to plain .class files, the generated classes do require the Groovy runtime classes to be available on the class path. I tried putting the groovy.jar everywhere I could think of (the mission directory, beta/jre, beta/jre/lib, beta/jre/lib/ext, etc.) and nothing worked. I finally extracted all the classes from the groovy.jar file and copied them into the mission directory. Then the Groovy classes worked... sort of.

While I can call and use the Groovy classes the Groovy classes seem to be unable to use the RVEngine object. I say "seem to be unable" because TOH also seems to silently swallow exceptions thrown by the code so I have no idea what is going wrong... No CTD, no error messages, no stack trace, the code just silently does nothing and the mission continues on.

However, if the Groovy code doesn't try to use RVEngine I can call methods in Groovy classes from the Java code just fine. That is, the following does work:

// In Sample.java
public static class Sample {
public static Object showHint(Object[] args) {
	Object obj = GSample.hint();
	RVEngine.hint(obj.toString());
}
}

// In GSample.groovy
static class GSample {
static Object hint() { return "Take on Groovy" }
}

So the Groovy class is being recognized and loaded, but there are definitely other class loading issues going on.

However, any meaningful debugging is impossible because TOH swallows any exceptions thrown. For example, while the above works, the following silently does nothing:

// In Sample.java
public static class Sample {
public static Object showHint(Object[] args) {
	try
	{
		RVEngine.hint(GSample.hint());
	}
	catch (Exception e)
	{
		RVEngine.hint(e.getMessage());
	}
}
}

// In GSample.groovy
static class GSample {
static Object hint() { 
	throw new NullPointerException()
}
}

While the game engine does have to continue when the Java code fails, we really do need to see the exceptions and stack traces during development. Are exceptions logged anywhere? The takeon.RPT file in the AppData folder doesn't contain anything and I can't find anything else logged anywhere.

Having said all that, I am quite pleased with what I see so far and have high hopes for the future.

Edited by Slapstick
Chnaged title.

Share this post


Link to post
Share on other sites

How about attaching the debugger to the process and reviewing the output there?

Share this post


Link to post
Share on other sites

I don't know why I didn't think of that ... other than the hour and too much beer...

When I step through the code with the debugger I can see the exception(s) before TOH swallows them, and on the bright side I am able to step through the Groovy code.

Now that I have solved all the ClassRefNotFound exceptions (by copying ~13MB of class files into the mission directory) I am running into AccessControlExceptions. If I try to instantiate a Groovy class I get an AccessControlException inside the Groovy framework. I get the same exception (and stack trace) when I try to use RVEngine in a static method of a Groovy class. However, I can call static methods on the Groovy class and it works as long as I don't use any TOH objects (RVEngine, RVDirectories etc.). I tried deleting the files in beta/jre/lib/security but that didn't change anything.

Groovy does a lot of "magic" in the background and I suspect it is attempting something TOH isn't allowing, or at least isn't allowing users classes to do.

Here is my test code:

// In JSample.java
public class JSample {

  public static Object staticTest1(Object[] args) {
     GSample.staticMethod1();		// So far so good.
     return null;
  }

  public static Object staticTest2(Object[] args) {
     RVEngine.hint(GSample.staticMethod2(); // Works!
     return null;
  }

  public static Object instanceTest1(Object[] args) {
     GSample object = new GSample();	// AccessControlException
     object.instanceMethod1();
     return null;
  }

  public static Object instanceTest2(Object[] args) {
     GSample object = new GSample();	// AccessControlException
     String msg = object.instanceMethod2();
     RVEngine.hint(msg);
     return null;
  }
}   

// In GSample.groovy
class GSample {
  static void staticMethod1() {
     RVEngine.hint("Take on Groovy")      // AccessControlException
  }  

  static String staticMethod2() {
     return "Take on Groovy"	// Works
  } 

  void instanceMethod1() {
     RVEngine.hint("Take on Groovy")	// Never gets this far.
  }

  String instanceMethod2() {
     return "Take on Groovy"	// Never gets this far.
  }

}

And for completeness, the stack trace. However, since these are all Groovy classes it's likely not much help.

Java HotSpot(TM) Client VM[localhost:8008]	
Thread [main] (Suspended (exception AccessControlException))	
CachedClass$3(LazyReference<T>).getLocked(boolean) line: 54	
CachedClass$3(LazyReference<T>).get() line: 33	
CachedClass.getMethods() line: 250	
MetaClassRegistryImpl.registerMethods(Class, boolean, boolean, Map<CachedClass,List<MetaMethod>>) line: 183	
MetaClassRegistryImpl.<init>(int, boolean) line: 91	
MetaClassRegistryImpl.<init>() line: 61	
GroovySystem.<clinit>() line: 29	
InvokerHelper.<clinit>() line: 49	
ScriptBytecodeAdapter.initMetaClass(Object) line: 777	
GSample.$getStaticMetaClass() line: not available	
GSample.<init>() line: not available	
JSample.instanceTest1(Object[]) line: 30	

Share this post


Link to post
Share on other sites

After some more digging I've finally been able to get the code to break near where the execption occurs. The exception message is:

Access not enabled outside game directories (D:\\Games\\Take On Helicopters\\beta\\jre\\lib\\ext\\sunec.jar)

However, nothing I have should be accessing anything outside the game directory. This looks like a real class loading bug.

I only saw this stack trace after modifying the java.policy file in beta/jre/lib/security. I was never able to actually modify the security permissions of anything, but the changes did cause Eclipse to break at different points in the code allowing me to capture the above message.

Share this post


Link to post
Share on other sites

WOOHOO :yay:

After finding (and fixing) two bugs in the jniscript.jar's ClassReloader (Issue #29214 and Issue #29146) my simple Groovy tests work! Now it's time to try more complicated tests, but at least the basics are working.

I realize not everyone is a Groovy fan, but at least I've demonstrated that other JVM languages can be used. Hopefully this bodes well for people that want to use other languages. Other brave souls that want to see if my fixes help them can find the fixed jniscripting.jar file here. Simply copy it to the jre\lib\ext folder in the TOH directory (make a backup of the original first).

P.S. I hope I am not breaking to much of the TOH License agreement by reverse engineering the jniscripting.jar and posting modified versions!

Share this post


Link to post
Share on other sites

Great job Slapstick! Examplary… Great findings and workarounds past days!

Share this post


Link to post
Share on other sites

Thanks for the kind words!

Continuing my proof of concept mission I've now thrown Scala into the mix, so I now have a single mission that combines Java, Groovy, and Scala code:

// In Java.java
public class Java {
public static Object test(Object[] args)
new Groovy().test();
}

// In Groovy.groovy
class Groovy {
void test() {
	new Scala().test()
}
}

// In Scala.scala
class Scala {
def test() = {
	import com.bistudio.jniscripting._;
	RVEngine.hint("Take on Scala")
}
}

// In the player's initialization field
SLAP_Java = jLoad "Java"; SLAP_Result = SLAP_Java jCall ["test", []];

The most difficult part was making Eclipse happy with a three language project, and I finally gave up on that and just compiled everything from the command line.

NOTE: I am NOT recommending this as the way to mix and match classes written in different languages. This is just a proof of concept, but there is a difference between saying something can be done and actually doing that something.

The process to use Scala was identical to getting Groovy working and simply involved unzipping the Scala runtime library (scala_library.jar) into the mission folder (its safe to ignore the META_INF folder and library.properties file). In fact using Scala didn't require a modified jniscripting.jar or any changes to the java.policy file (Groovy requires both).

However, Scala doesn't have the same concept of static class methods that Java does. It has some syntax that looks similar, but the bytecode generated by the Scala compiler is considerably different. I only mention this because people trying other languages might experience something similar; just because a compiler generates bytecode doesn't mean it will generate the same kind of bytecode javac would generate.

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×