Plugins
EditLÖVR has a small core. Extra features can be provided by Libraries
written in Lua, or by plugins. Plugins are similar to libraries -- they can be require
d from Lua
to access their features. However, instead of Lua files in a project folder, plugins are native
libraries (.dll
or .so
files) that are placed next to the lovr executable.
Contents
Using Plugins
To use a plugin, place its library file next to the lovr executable and require
it from Lua:
-- myplugin.dll is next to lovr.exe
local myplugin = require 'myplugin'
function lovr.load()
myplugin.dothething()
end
Plugins are not officially supported in WebAssembly yet, but this is theoretically possible.
List of Known Plugins
lovr-luasocket | HTTP and socket support via luasocket |
lua-cjson | Fast native JSON encoder/decoder |
lua-cmsgpack | Lua MessagePack C implementation. |
lua-deepspeech | Speech recognition using Mozilla's DeepSpeech library |
lua-enet | enet for UDP multiplayer servers/clients |
lua-https | Lua HTTPS module using native platform backends. |
luv | libuv bindings for Lua |
Building Plugins with CMake
LÖVR's CMake build system has support for automatically building plugins from source code. In the
main lovr folder, a plugins
folder can be created, containing a subfolder for each plugin to
build. CMake will check all the subfolders of plugins
, building anything with a CMakeLists.txt
file. Their libraries will automatically be moved next to the final lovr executable, or packaged
into the apk on Android.
Inside the plugins' CMakeLists.txt
scripts, the LOVR
variable will be set to 1
, so libraries
can detect when they're being built as lovr plugins. Plugins also automatically have access to the
version of Lua used by LÖVR, no calls to find_package
are needed.
This makes it easier to manage plugins -- they can be copied, symlinked, cloned with git, or added as git submodules. A fork of lovr can be created that has this custom plugins folder, making it easy to quickly get a set of plugins on multiple machines. Version control also means that the plugins are versioned and tied to a known version of lovr.
Creating Plugins
Internally, a plugin is no different from a regular native Lua library. A plugin library only needs to have a Lua C function with a symbol named after the plugin:
int luaopen_supermegaplugin(lua_State* L) {
// This code gets run when the plugin is required,
// and the value it leaves at the top of the stack
// is used as require's return value.
}
All of Lua's rules for native
plugin loading, including processing of dots and hyphens and all-in-one loading, apply to LÖVR
plugins. However, note that LÖVR plugins do not use package.cpath
or Lua's default loader.
The lovr.filesystem
module has its own loader for loading plugins (it always looks for plugins
next to the executable, and checks the lib/arm64-v8a
folder of the APK).
Android
Adding Plugins to an APK
Instead of adding plugins to the plugins
folder and building an APK with CMake, it is possible to
add a plugin library to an existing APK without recompiling the whole framework.
First, add the plugin to the APK. APKs are just zip archives, so the zip
command can do this.
It's important to add the library without any compression, using the -Zs
flag. The library also
needs to be in a lib/arm64-v8a
folder, so it gets added to the correct path in the APK.
zip -u -Zs lovr.apk lib/arm64-v8a/myplugin.so
Next, run zipalign on the APK. This ensures the library is aligned to a 4096 byte page boundary, which Android requires for libraries loaded from APKs.
zipalign -f -p 4 lovr.apk lovr.apk.tmp
Finally, resign the APK:
apksigner sign --ks /path/to/key.keystore --ks-pass pass:hunter2 --in lovr.apk.tmp --out lovr.apk
This will produce a new, signed APK with the plugin in it!
Using JNI in Plugins
Android currently offers no way for a native library to get access to the JNIEnv*
pointer if that
native library was loaded by another native library. This means that LÖVR plugins have no way to
use the JNI. To work around this, before LÖVR calls luaopen_supermegaplugin
, it will call the
JNI_OnLoad
function from the plugin (if present) and pass it the Java VM. Example:
#include <jni.h>
static JNIEnv* jni;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
(*vm)->GetEnv(vm, (void**) &jni, JNI_VERSION_1_6);
return 0;
}
int luaopen_supermegaplugin(lua_State* L) {
// can use jni!
}