InterfacingWithLuaFromJava - airminer/jnlua GitHub Wiki
This section describes how to work with Lua from the Java point of view.
The Lua State
The LuaState
is the core class of JNLua. Each object of this class represents a Lua instance. For those familiar with the Lua C API, a LuaState object in Java represents a lua_State
pointer in C.
The interface of the Lua state class corresponds largely to the underlying Lua C API which is documented in the Lua Reference Manual. Method names have been adjusted to Java camel case and the lua_
and luaL_
prefixes haven been omitted since this information is implied in the Java package name. Also, the Java API takes advantage of method overloading and uses a single method name for C API functions that primarily differ in their number arguments. For example, the Java API provides rawGet(int)
and rawGet(int, int)
whereas the C API provides lua_rawget()
and lua_rawgeti()
.
A JNLua Lua state subsumes the Lua main thread and all additional Lua threads created by means of the coroutine API. Therefore, there is no separate Java Lua state instance for each Lua thread. Instead, Lua threads are treated as Lua values and referenced by stack index in the coroutine API methods. The coroutine API methods have been slightly adapted from their C behavior to that end. Functionality-wise, they pretty much correspond to the functions in the Lua coroutine module.
You should free Lua states that are no longer in use by explicitly invoking the LuaState.close()
method. Otherwise, resources on the native side are not freed until the Lua state (or more precisely, the finalize guardian of the Lua state) is collected by the Java garbage collector.
The following mini-program illustrates the creation, use and closing of a Lua state.
import com.naef.jnlua.LuaState;
public class Simple {
public static void main(String[] args) {
// Create a Lua state
LuaState luaState = new LuaState();
try {
// Define a function
luaState.load("function add(a, b) return a + b end", "=simple");
// Evaluate the chunk, thus defining the function
luaState.call(0, 0); // No arguments, no returns
// Prepare a function call
luaState.getGlobal("add"); // Push the function on the stack
luaState.pushInteger(1); // Push argument #1
luaState.pushInteger(1); // Push argument #2
// Call
luaState.call(2, 1); // 2 arguments, 1 return
// Get and print result
int result = luaState.toInteger(1);
luaState.pop(1); // Pop result
System.out.println("According to Lua, 1 + 1 = " + result);
} finally {
luaState.close();
}
}
}
For a comprehensive introduction to the Lua API, the chapters on the C API in Programming in Lua, Second Edition may be of interest in addition to the Lua Reference Manual. A full description of Lua API is beyond the scope of the JNLua documentation.
Implementing Java Functions
Lua functions are implemented in Java by implementing the JavaFunction
interface. The interface consists of a single method named invoke()
. When the invoke()
method is called, the arguments of the function are on the Lua stack. When the invoke method has completed its task, it returns the number of values it left on the stack as the return values of the function.
Java functions can be pushed on the Lua stack by means of the LuaState.pushJavaFunction()
method. The Lua state also provides the method LuaState.register()
which registers the Java function in the global scope. The register method requires that the Java function implements the NamedJavaFunction
interface. That interface extends base interface by an additional method that returns the name of the function.
In Java, checked exceptions are used to indicate application errors and unchecked exceptions are used to indicate programming errors. In Lua, return values are used to indicate application errors and Lua errors are used to indicate programming errors. In case a Java function requires to signal an application error, it should return an appropriate error code; in case a Java function requires to signal a programming error, it should throw an unchecked exception. The unchecked exception is caught by JNLua and translated to a Lua error.
The following example shows the implementation of a simple function in Java:
class Divide implements NamedJavaFunction {
@Override
public int invoke(LuaState luaState) {
// Get arguments using the check APIs; these throw exceptions with
// meaningful error messages if there is a mismatch
double number1 = luaState.checkNumber(1);
double number2 = luaState.checkNumber(2);
// Do the calculation (may throw a Java runtime exception)
double result = number1 / number2;
// Push the result on the Lua stack
luaState.pushNumber(result);
// Signal 1 return value
return 1;
}
@Override
public String getName() {
return "divide";
}
}
Implementing Modules in Java
JNLua supports the creation of Lua modules from Java. To that end, the Lua state provides the LuaState.register method which takes as arguments a module name and an array of named Java functions to populate the module with. When the method returns, the module table is on top of the Lua stack and can be further populated.
Continuing the example from above, the following code snippet shows the registration of a simple module:
public void registerSimple(LuaState luaState) {
// Register the module
luaState.register("simple", new NamedJavaFunction[] { new Divide() });
// Set a field 'VERSION' with the value 1
luaState.pushInteger(1);
luaState.setField(-2, "VERSION");
// Pop the module table
luaState.pop(1);
}
Implementing Java Interfaces in Lua
Java interfaces can be implemented in Lua. The Lua state provides the method LuaState.getProxy()
to that end. The method takes as arguments a stack index containing a Lua table and a Java interface type. The keys of the table are expected to match the names of the interface methods, the values are expected to be functions providing the corresponding implementations. Another signature of the LuaState.getProxy()
method allows creating proxies implementing multiple interfaces.
The following code provides an example for implementing a Java interface in Lua:
import com.naef.jnlua.LuaState;
public class InterfaceProxy {
public static void main(String[] args) {
LuaState luaState = new LuaState();
try {
// Open libraries
luaState.openLibs();
// Implement interface in Lua
luaState.load("runnable = { run = function() print(\"I am running\") end }", "=interface");
luaState.call(0, 0);
// Get proxy
luaState.getGlobal("runnable");
Runnable runnable = luaState.getProxy(-1, Runnable.class);
luaState.pop(1);
// Run it
Thread thread = new Thread(runnable);
thread.start();
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
luaState.close();
}
}
}
Threads
Lua states are conditionally thread-safe. This means that a Lua state performs enough internal synchronization to protect its integrity when used by multiple threads. However, the outcome of certain operations depends on the order by which the methods are invoked. Therefore, clients should synchronize on the Lua state at a higher level to ensure the consistency of their operations if a Lua state is used by multiple threads.
For example, if a client pushes a value on the Lua stack and performs an operation with that value in the next step, the client should synchronize on the Lua state to ensure that the two operations are not influenced by another thread working with the same Lua state. The following code fragment shows the proper use of a Lua state in environments with multiple threads:
synchronized (luaState) {
luaState.getGlobal("add");
luaState.pushInteger(1);
luaState.pushInteger(1);
luaState.call(2, 1);
int result = luaState.toInteger(1);
luaState.pop(1);
}
Error Handling
JNLua uses the following exceptions:
Exception | Thrown |
---|---|
LuaRuntimeException |
If a Lua runtime error occurs. |
LuaSyntaxException |
If the syntax of a Lua chunk is incorrect. |
LuaMemoryAllocationException |
If the Lua memory allocator runs out of memory or if a JNI allocation fails. |
LuaGcMetamethodException |
If an error occurs running a __gc metamethod during garbage collection. |
LuaMessageHandlerException |
If an error occurs running the message handler of a protected call. |
All exceptions are derived from LuaException
.
Note that all Lua exceptions are unchecked exceptions. It is recommended that clients use a try...catch clause to catch LuaException
and its subclasses.
The Lua runtime exception class provides the method LuaRuntimeException.getLuaStackTrace()
which returns the Lua stack trace of the error. Additional methods of the class allow printing the Lua stack trace to various outputs.