Plugins
===
LÖ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.
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
:::note
On Unix systems, some plugin files might be prefixed with `lib` (e.g. `liblovr-plugin.so`). In this
case, be sure to require the plugin with the lib prefix: `require 'liblovr-plugin'`.
:::
:::note
On Android, LÖVR looks for plugins in the `lib/arm64-v8a` folder of the APK.
:::
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.
:::note
By default, the libraries from all CMake targets in the plugin's build script will be moved to the
executable folder. Plugins can override this by setting the `LOVR_PLUGIN_TARGETS` variable to a
semicolon-separated list of targets.
:::
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](https://www.lua.org/manual/5.1/manual.html#pdf-package.loaders) 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.
:::note
The plugin library must be compiled for the same architecture as the rest of the APK! Most Android
devices use the ARM64 architecture (but Magic Leap 2 uses x86\_64). On Unix systems, you can run
`file myplugin.so` to check the architecture: it will say `ARM aarch64` for ARM64 or `x86-64` for
x86\_64.
:::
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
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!
}
Troubleshooting
---
If a plugin isn't loading properly, it can help to see errors from the dynamic linker. On Linux,
the `LD_DEBUG` environment variable is able to print out a lot of information about library loading:
LD_DEBUG=libs lovr .
On Android, setting the following property will log messages and errors from the dynamic linker:
adb shell setprop debug.ld.app.org.lovr.app dlerror,dlopen