- J2SE - Java 2 Standard Edition - is the Java version that is available on desktops. Kahlua core depends on J2SE 1.4 or higher, while the J2SE extensions require J2SE 1.5 or higher.
- CLDC - Connected Limited Device Configuration - is a slimmed down version of Java, with many features removed. Kahlua requires CLDC 1.1 or higher to work.
- Midlet - Application for mobile devices using CLDC (and MIDP, but let's not go too deep).
Kahlua2 is built with a modular design.
This means you can configure your system the way you want it but it also means that there are some basic things that need to be set up to get started.
The platform
First of all, you need a Platform.
The core of Kahlua2 has some dependencies that are platform specific. For instance, Kahlua2 needs to be able to create tables, and the table implementations are different for J2SE and CLDC 1.1. They are backed by ConcurrentHashMap and Hashtable respectively.
For that reason, Platform has the method:
KahluaTable newTable();
Fortunately for you, getting hold of a platform is easy!
Just call J2SEPlatform.getInstance() or CLDC11Platform.getInstance().
These platform implementations are immutable, so don't worry about sharing the platform with others.
Typically you want to use CLDC11Platform for writing midlets and J2SEPlatform for everything else. If for some reason, neither of these platforms suit your purposes, you can simply create your own.
An environment
The global scope in Kahlua (as well as in Lua) is just a regular table. The compiler makes sure to translate any variable that isn't declared as a local to a lookup in the environment table.
If the environment is just a regular table, can we simply create one by calling platform.newTable()? Yes! That works perfectly well.
However, this environment will be empty, so you can't find the standard library functions in it, and most of the scripts you want to run probably needs a couple
of standard functions to do anything interesting.
An alternative way of creating an environment is to call platform.newEnvironment(); This creates a new table, but also populates it with useful stuff. J2SEPlatform for instance loads the environment with BaseLib, MathLib, RandomLib, StringLib, CoroutineLib, OsLib, TableLib and the compiler.
If you're curious, take a look inside J2SEPlatform, it should be easy to follow.
Compiling scripts
Having an environment is not very exciting in itself, you probably also want to run some scripts! Kahlua has two ways of creating closures (or functions) for these scripts. The easiest and best way is to use one of the static methods in LuaCompiler; use loadstring if you have the input as a string, and loadis if you have an inputstream or reader. All closures needs environments, so you need to pass in your environment as the last parameter (or another environment if you prefer).
Note that the compiler will throw a KahluaException (which is a RuntimeException) if the script is malformed. You should be prepared to handle it.
The other option is to load the bytecode directly. This is mostly useful on devices where you dont want to bundle the full compiler, such as limited mobile devices. The solution is to compile the scripts offline, either with the standard luac bundled with Lua, or with Kahluas own Java class called LuaC. The resulting binary file can then be loaded with KahluaUtil.loadByteCodeFromResource.
The thread
With a platform, an environment and some closures, you're almost set to start running your scripts, but there is one more thing needed - a thread to run them in.
KahluaThread t = new KahluaThread(platform, env)
is all you need to create a thread.
Kahlua is concurrent in the way that you can create multiple threads that reference the same environment, but don't try to run scripts on the same KahluaThread from different Java threads! Things would blow up in mysterious and unexpected ways.
The most basic way of invoking your closure is by using thread.call(closure, args...). However, if the closure invocation should throw an error, a RuntimeException will propagate out to the Java code. A safer option is to use thread.pcall(closure, args...) which traps all Lua errors and returns the error information as part of the return values.
For J2SE, there are few more ways of invoking closures, but more on that in a later post.
Java functions
Most applications that use an embedded script engine have a need for the scripts to access domain specific functions. If I were to write a game engine, then my AI scripts might need a way to execute orders. So the opposite of Java invoking Lua scripts is needed; we need to be able to call Java methods from Lua.
There are several ways of doing this in Kahlua, but they all boil down to this core functionality:
interface JavaFunction {
int call(LuaCallFrame callFrame, int nArguments);
}
Kahlua can call any object that implements this interface. This is how all of the standard library in Kahlua is implemented. This is also the only way to do it in CLDC 1.1, due to lack of a reflection API.
The parameters to call is a callframe, which contain the arguments passed to the function, and the number of arguments. The return value of call should be the number of return values, since Lua supports multiple return values. The return values themselves need to be pushed on the Kahlua stack, and there are convenience methods for that in the LuaCallFrame object.
Here is a simple example a JavaFunction implementation intended to be used like this:
local x, y = GetUnitLocation("The unit name")
public class GetUnitLocation implements JavaFunction {
public int call(LuaCallFrame callFrame, int nArguments) {
if (nArguments < 1) {
throw new IllegalArgumentException("Expected at least one argument");
}
Object arg = callFrame.get(0);
if (arg == null || !(arg instanceof String)) {
throw new IllegalArgumentException("Expected a string argument but got " + arg);
}
String s = (String) arg;
Unit unit = unitService.getUnit(s);
callFrame.push(unit.getX());
callFrame.push(unit.getY());
return 2;
}
}
Due to the dynamic nature of Kahlua, there needs to be a lot of boilerplate error checking, but the rest of the implementation is straight forward.
Exposing things
Just having a JavaFunction implementation is not enough - it needs to reach the lua scripts in order to be useful. This can be done in several ways. The first, and most obvious, is to put it in the environment:
environment.rawset("GetUnitLocation", new GetUnitLocation());
The lua script can then call it by just doing:
GetUnitLocation("the unit name")
The lua script can also copy it somewhere, redefine it - "GetUnitLocation" is just a key in a table, and can be manipulated like any other object, so you can do things like this:
fun = GetUnitLocation
fun("the unit name")
or
local oldfun = GetUnitLocation
function GetUnitLocation(s)
print("calling GetUnitLocation here")
return oldfun(s)
end
Conclusion
That's all you need to know to get started with Kahlua. It may seem like there was a lot of steps involved, but each step is really very simple.
For mobile platform targets, there isn't much more to add but for J2SE users there are additional goodies available, which I'll go over some time in the near future.