Compiling Lemur groovy glass style


I was curious to see if I can precompile and load groovy scripts later on. I decided to give it a try with Lemur glass style and see if I can precompile script and load it later. Fortunately I could gain some success in the way.

Well when I merge all scripts (StyleApi.groovy, glass-style.groovy) into a single script and compile it and save the bytecode into a file (GlassStyle.class) and load the class (it is a groovy Script class) and run that (using the method), it works fine but if I compile each script separately as StyleApi.class and GlassStyle.class and then load them separately an run them in order using a shared Binding then GlassStyle.class can not access the methods defined in StyleApi (for example the texture() method for loading textures) and throw a no such method found exception. It seems those defines in StyleApi.class are not shared through the bindings when running the script and thus GlassStyle.class can not access them? I thought someone might have an idea why it is not working or if I need to use some groovy magic to resolve it.

I am using the code snippet provided here to compile and save the script bytecode:

There is some weird magic that happens with the way the style stuff is loaded that allows those methods to be accessible in that “script environment”. I’ve found this magic to be unpredictable (as you’ve discovered).

…over the years my groovy-embedding knowledge has evolved and I’ve ended up making some compromises. The biggest one is that I’ve stopped writing shared methods the way StyleApi.groovy does and just started setting closure bindings.

So instead of:

Texture texture( String name ) {
    return gui.loadTexture( name, true, true );

I will instead do:

texture = { String name ->
    return gui.loadTexture( name, true, true );

The “compromise” about this is that I find it much squishier with respect to typing… ie: it’s less self-documenting because now the reader potentially has to hunt around to find out what is actually returned.

If I were writing the styling API today, it’s pretty clear that I would do a few things differently. (Perhaps not the least of which is to have a root style() block to specify the style name instead of passing it as a last parameter everywhere.)

Probably it would not affect backwards compatibility to switch these methods to bound closures… I don’t know if it would solve your problem but you could try it.

Edit: an aside: another trick I’ve started doing is having a debug mode where I compare the bindings before and after running the scripts… then I log the bindings that are new. This helps me catch places where I forgot to put a ‘def’ when I meant to do so. In my games, the scripts can get very complicated and accidentally sharing a variable across multiple places because I inadvertently shoved it in a binding instead of making it local… well, it’s rough. I’ve also at times set the closures up in a way that attempts to write back to the global bindings would result in error but it’s a little tricky and feels fragile.

In the end, I put a lot of these sorts of safety nets around that I will turn off when things are more stable.

1 Like

Will try it. Thank you :slightly_smiling_face:

When using closure binding, only one of them (the last one) is bound and thus accessible via bindings.

For example in the below case:

color = { Number r, Number g, Number b, Number a ->
    return color(r, g, b, a, true);

color = { Number r, Number g, Number b, Number a, boolean autoConvertSrgb ->
    if( autoConvertSrgb ) {
        return gui.srgbaColor(r.floatValue(), g.floatValue(), b.floatValue(), a.floatValue())
    } else {
        return new ColorRGBA(r.floatValue(), g.floatValue(), b.floatValue(), a.floatValue())

only the color = { Number r, Number g, Number b, Number a, boolean autoConvertSrgb } closure is accessible via bindings unless I use different names.

Yeah, I forgot. That’s the other limitation with the bindings approach and it really sucks. We go from something simple and clear to something less clear and then also have to do extra work.

It’s possible to get this to work but you have to have a single var-args closure that chooses what to do based on the argument list. In this case, 3 versus 4 arguments… which is at least easy to check.

1 Like