Skip to content

Lua Scripting Integration

This page documents how ORCA integrates Lua scripting, how modules are loaded, and the conventions used by generated bindings.


Global Variables

When the engine starts it exposes several directory paths as Lua globals:

Global Description
LIBDIR Directory containing compiled shared libraries (.so / .dll)
SHAREDIR Directory containing shared Lua scripts and assets
PLUGDIR Directory containing plugin files
PROJECTDIR Path to the currently loaded project directory
EXENAME Full path to the engine executable
DATADIR Alias for PROJECTDIR (deprecated alias)
SERVER Set to true when running in headless server mode

These globals are set before any project Lua code runs. They can be used to build paths portably:

local config = dofile(PROJECTDIR .. "/config.lua")
local atlas   = SHAREDIR .. "/textures/atlas.png"

Module System

Built-in Modules

All built-in engine modules are pre-registered in package.preload by luaopen_orca before any project code runs. They are loaded lazily on first require:

local core   = require "orca.core"
local geo    = require "orca.geometry"
local fs     = require "orca.filesystem"
local render = require "orca.renderer"
local sys    = require "orca.system"
local json   = require "orca.parsers.json"
local xml    = require "orca.parsers.xml"
local l18n   = require "orca.localization"
local back   = require "orca.system"

The following modules are excluded from WebGL and server builds:

Module Reason excluded
orca.network BSD sockets — not available in browser
orca.editor Native desktop file-dialog features

Short-hand orca.* Access

The orca table has a __index metamethod that automatically requires sub-modules on first access and caches them. This means:

-- Both of these are equivalent:
local geo = require "orca.geometry"
local geo = orca.geometry

The auto-require only works for modules with names of the form orca.<key>. Once loaded the result is cached directly in the orca table.

Plugin Module Loading (Native Builds)

On desktop builds, plugin shared libraries (.so / .dll) are placed in LIBDIR/lib*.so. The engine prepends LIBDIR/lib?.so to package.cpath so that require "orca.UIKit" finds and loads the plugin automatically:

local UIKit   = require "orca.UIKit"
local btn     = UIKit.Button()

Lua script plugins (.lua files) placed in SHAREDIR/plugins/ are loaded by load_plugins() at startup using dofile.

Plugin Module Loading (WebGL Builds)

In the WebGL / single-binary build there is no dynamic dlopen. Instead:

  1. The webgl Makefile target compiles all plugin sources directly into the WASM binary.
  2. build/webgl/plugins_luaopen.h is auto-generated by scanning the compiled sources for luaopen_orca_* symbols.
  3. luaopen_orca registers these via luaL_preload alongside the built-in modules.

The result is transparent: require "orca.UIKit" works identically in both native and WebGL builds.


Generated Lua Bindings

The *_export.c files generated by cd tools && make wire every XML-declared struct, interface, and class into Lua. Understanding the generated binding pattern helps when debugging.

Struct Bindings

For each <struct name="Foo" export="Foo">:

  • luaX_pushFoo(L, ptr) — pushes a userdata wrapping Foo*
  • luaX_checkFoo(L, idx) — checks and extracts Foo* from stack position idx
  • A Lua metatable named "Foo" is registered in the registry

Interface / Function Bindings

For each <interface name="Foo" prefix="FOO_" export="Foo">:

  • A table Foo is created in the module's Lua table
  • Each <method lua="true"> becomes a function in Foo

Component / Class Bindings

For each <class name="Bar" export="Bar">:

  • Bar() is a constructor that calls OBJ_Create + OBJ_AddComponent(obj, ID_Bar)
  • Property access is via __index / __newindex metamethods backed by OBJ_FindShortProperty
  • GetBar(obj) in C returns the typed component pointer from an Object*

Coroutines and Async

The engine exposes an orca.async function for fire-and-forget coroutines:

orca.async(function()
    -- runs as a coroutine resumed by the event queue
    local result = someLongOperation()
end)

Internally this posts a kMsgResumeCoroutine message to resume the coroutine on the next event loop tick. Unlike coroutine.wrap, orca.async coroutines are managed by the engine and resumed automatically.


Lua Path Setup

After the globals are set, the engine runs:

package.path  = PLUGDIR.."/?.lua;"..SHAREDIR.."/?.lua;"..package.path
package.cpath = LIBDIR.."/lib?.so;"..package.cpath  -- skipped on WebGL

This means any .lua file under PLUGDIR or SHAREDIR can be loaded with require. Project-local scripts can be placed in PROJECTDIR and required directly by path, or by adding PROJECTDIR to package.path in the project's entry-point script.


Lua State Lifecycle

Stage What happens
Engine init luaopen_orca registers all built-in and plugin modules in package.preload
Globals setup Directory paths (LIBDIR, SHAREDIR, …) pushed as globals
Path setup package.path and package.cpath extended
Project load RunProject(L, projectDir) locates and dofiles the entry point
Main loop Lua while true do … end calls axPollEvent; ASYNCIFY yields to the browser on WebGL
Shutdown Lua state closed; kMsgDestroy sent to all objects

Gotcha — module preloading order: Modules in package.preload are not executed at registration time. Each module's luaopen_* function runs on the first require call. If your module initialization has side effects that depend on another module (e.g. registering classes), make sure to require the dependency explicitly rather than relying on registration order.