Skip to content

Lua exports

Open Markdown

An addon can give some functions of its Java to Lua. The Lua of any mod can call them. You mark a function with @LuaExport. The card of the addon lists the functions, and says what they can reach.

Permission: lua. Without it, the addon does not start.

addon.properties
permissions=lua
lua.globals=Fireworks

Put @LuaExport on a public static method. Or put it on a class: then all the public static methods that the class declares are exports.

Fireworks.java
@LuaExport
public final class Fireworks {
private Fireworks() {}
public static int launch(String color, int count) {
// ...
return count;
}
@LuaExport(name = "stop")
public static void cancelAll() {
// ...
}
}

name is the name that Lua uses: for a method, in place of its Java name; for a class, in place of its simple name. Lua has no overloads. If two methods have the same Lua name, Viewpoint refuses both.

A mod that you port from ZombieBuddy can keep its @Exposer.LuaClass classes: Viewpoint reads them as exports of their public static methods. See Migrating from ZombieBuddy.

A method that is not public and static is not an export. The instance methods of a class are never exports.

Lua finds each export as Viewpoint.Addons["<id>"].<Class>.<method>:

local n = Viewpoint.Addons["example.fireworks"].Fireworks.launch("red", 3)

If lua.globals lists the name of a class, Lua also finds the class under that global name:

local n = Fireworks.launch("red", 3)

Viewpoint refuses a global, and writes the reason in console.txt, in these cases:

  • the global already exists when your addon starts;
  • another mod sets the global at the top level of its Lua. A statement in an if or do of a file counts, because it runs when the file loads. A local does not. X = ..., X.y = ..., function X(), _G.X = ... and _G["X"] = ... all set X.
  • Viewpoint could not read the Lua of another mod completely.

A name in lua.globals that no exported class has is written in console.txt too, and the addon starts.

A refused global does not stop the addon. The name Viewpoint.Addons["<id>"] always works. Write your Lua with that name if you must not depend on a global.

A function can take and give only these types. Any other type refuses the function. The other functions of the addon stay.

TypeIn Lua
boolean, BooleanA boolean.
byte, short, int, long (and their boxes)A whole number in the range of the type. long goes up to 2^53: Lua numbers are double. A long result past 2^53 is an error in the Lua that called, not a rounded number.
float, double (and their boxes)A number. A float result is the number that it prints as (0.1f is 0.1).
char, CharacterA string of one character.
StringA string. A string passes as it is. It can hold a NUL byte, .. or anything else.
LuaTableA table. See Tables.
LuaHandleA number. See Handles.
A game class of the list belowAn object of the game, as it is.
voidNothing. Only as a result.

A nil of Lua gives null for a box, a String, a LuaTable, a LuaHandle or a game class. For a primitive type it is an error.

The game classes that a function can pass are: IsoPlayer, IsoZombie, IsoGameCharacter, IsoGridSquare, IsoObject, InventoryItem, HandWeapon and BaseVehicle. The list can grow. Ask for a class that you need.

A function cannot use Object, an array, a list, a map, a stream, a reader, a writer or a class of your own. A varargs function (String...) is an array, so it is refused.

LuaTable is a wrapper. It is not the table of the game. It reads and writes these values: Boolean, Double (every Lua number), String and LuaTable. A key is a string or a whole number. A Lua value of another type (a function, an object of the game) reads as null. A value of another type that you set is an error.

public static LuaTable summary(LuaTable input) {
LuaTable out = input.newTable();
out.set("count", input.length());
out.set("name", "summary");
return out;
}

A LuaTable is good only while the call runs. Do not keep it.

Lua cannot hold an object of your addon. Give it a handle instead. ctx.handle(object) makes a LuaHandle. Lua sees a number. When Lua gives the number back to a function that takes a LuaHandle, Viewpoint finds the object again.

public static LuaHandle open(String name) {
return context.handle(new Session(name));
}
public static void close(LuaHandle session) {
session.release();
}
  • A number is chosen at random from the whole numbers that Lua holds exactly, so Lua cannot find a handle by counting. A number is not used for two handles in the life of the game, and the number of a handle of an earlier start of your addon names nothing in the next. The Lua of all the mods is one trust domain: a number that a mod has can be given to any function of your addon that takes a handle, so do not use a handle as a secret.
  • ctx.handle, release() and the methods of a LuaTable work on the main thread of the game only. From another thread they throw an IllegalStateException.
  • An addon can have 4096 handles at one time. More is an error. Call release() for the ones that you do not need.
  • A handle that you released, a handle of another addon, and a number that no handle has are errors in the Lua that uses them.
  • When the addon goes off, Viewpoint clears all its handles.
  • Lua calls an export on the main thread only. A call from another thread is an error.
  • Viewpoint counts each call and adds its time to addon.<id>.mainMs. The Performance tab shows the sum.
  • If an export throws, Viewpoint suspends the addon and gives a Lua error. Do not let an export throw for a wrong argument of the caller: check the arguments, and return a result that tells the caller.
  • Treat every argument as if a stranger sent it. Any mod can call your exports. For a path, use AddonFiles. For an address, use AddonDownloads. Their rules apply to an argument from Lua too.

An error is a Lua error. A Lua author can use pcall to catch it. The text names the function and what to change:

The textThe cause
argument 1 of Viewpoint.Addons["x"].C.m must be a whole number, not a stringThe type of an argument is wrong.
... must be a whole number from -128 to 127The number is out of range for the type.
... must not be nilA nil for a primitive type.
... takes 2 arguments, not 1The number of arguments is wrong.
... must be a handle number that this addon made and has not releasedThe handle is gone, or is not from this addon.
... can only be called on the main thread of the gameA call from another thread.
... failed: <the exception>. The addon is suspendedThe export threw.
... addon x is suspended because one of its functions failedA call after a throw.
... addon x is offThe addon is off. A function that Lua kept in a variable gives this error too.

A mod runs its top-level Lua while the game loads. The exports must be there before that. So at the first Lua start of the game, Viewpoint waits for your addon. It checks the addon and starts it, and then it installs the exports. This wait is about 5 seconds for all the addons together. Viewpoint tests the time between the starts of addons, so a slow enable can pass it. An addon that needs the wait:

  • holds the lua permission, and
  • the player has it on, with the consent for its jar.

While it waits, Viewpoint writes “Viewpoint is checking N addons” in console.txt and shows it as a notice.

If an addon is not ready after 5 seconds, it starts at the first frame. This is also true when Viewpoint could not finish finding the packages in time: then every addon with exports is late. Its exports are then there after the top-level Lua of the mods ran. The card of the addon says “Its Lua functions came late at this start.” A Lua mod can call your functions later, from an event.

The game starts its Lua again at some times: when the player joins or leaves a server, changes an option, loads a world with another set of mods, or returns to the menu. Each time, the Lua is new and empty, and Viewpoint installs the exports again. This second time there is no wait: the addon is on already.

Each export becomes a stub. It gives the error “addon x is off”. Viewpoint clears the handles. The class loader of the addon can then unload. Do not keep a LuaTable or a LuaHandle in a static field of a class that the game keeps.