Skip to main content

00 — Overview

ZLua product goals, dual-runtime architecture, documentation map, and initialization flow. Terminology: GLOSSARY.md.


1. Product goals

1.1 Usage model

ZLua aligns conceptually with P/Invoke, MonoPInvokeCallback, and MarshalAs:

ConceptZLua counterpart
P/InvokeC# ↔ Lua interop; C#→Lua via GetFunction<T>
MarshalAs[LuaMarshalAs] — parameter / return Marshal
C# calling LuaLuaAppDomain.GetFunction<T> — obtain a Delegate, then Invoke

Unified interaction model:

  • C#→Lua: LuaAppDomain.GetFunction<T>(module, method) binds a Delegate by module and method name; callers Invoke and cache on the hot path themselves.
  • Lua→C#: types are lazily registered; members bind on first access to CSharp[assembly][typeFullName]. Static members go through the type table; instance members via obj:Method(), with semantics close to C#.
  • Codegen: Lua→C# bridges are generated in the Editor (Mono: Expression Emit; Il2Cpp: C++ stub + metadata), transparent to app developers; C#→Lua uses the GetFunction + Delegate bridge and does not rewrite user assemblies.

Deep integration: on host startup, initialize the CLR and lua_State, then load the zlua standard library and the CSharp root table.

1.2 Player release optimizations (Il2Cpp)

OptimizationNotes
Native bridgeHot paths are C++; no layer-by-layer jumps through LuaDLL extern
Stub reuseIdentical ReducedType signatures reuse bridge functions — not “one standalone C function per member”
Fields / propertiesIl2Cpp may use offsets + methodPointer for direct access
Managed objectsuserdata records the object pointer; ObjectRegistry slots are registered as GC roots, cleared when Lua releases the userdata

Mono (Editor) may use reflection / Emit slow paths, but Lua-visible semantics must match Il2Cpp.

1.3 Explicitly unsupported

ItemSpec behavior
Dedicated Event metatableNo { get, set, fire }; scripts use add_EventName / remove_EventName (same as ordinary methods)
__index missReturns nil
__newindex misserror
Runtime inheritance lookup for instancesNone; inherited members are flattened at Bind time into the current type’s three tables (see 02-TYPE-SYSTEM.md §5)

2. Dual-runtime architecture

LuaAppDomain.Initialize(moduleLoader)

┌───────────────┴───────────────┐
▼ ▼
ZLua.Mono (Editor) ZLua.Il2Cpp (Player)
LuaMonoAppDomain LuaIl2CppAppDomain
│ │
three-table Lua indexer / Emit bridge libil2cpp/zlua (C++)
│ │
└───────────────┬───────────────┘

Same Lua-visible semantics (spec/**)
LayerMonoIl2Cpp
AssemblyZLua.MonoZLua.Il2Cpp (thin InternalCall shell)
Interop implementationC# + Lua indexerlibil2cpp/zlua/**
BridgingExpression Emit per public memberReducedType stub + generated metadata
Indexerthree-table Lua closurenative Dispatch* + MetaBinding / TypeRegistry
Shared definitionsZLua.Common: LuaMarshalAsAttribute, LuaAliasAttribute, LuaAppDomainsame

Il2Cpp source layout (Unity build):

  • libil2cpp/lua — Install overlays the selected engine: PUC-Rio is a compileable source tree (patched; see build/01-OFFICIAL-LUA.md); LuaJIT is public headers only (static libs placed by the developer under Plugins; see build/02-LUAJIT.md)
  • libil2cpp/zlua — ZLua native implementation (from package ZLua~/zlua-runtime)

Editable development reference: build-win64/Il2CppOutputProject/IL2CPP/libil2cpp/zlua. Package layout, multi-Unity / multi-Lua, patches, DLL naming, Il2Cpp ZLuaConf.inc / Compatible headers → 11-MULTI-VERSION.md (§12).


3. Documentation map

Docs/
├── GLOSSARY.md Glossary
├── spec/
│ ├── 00-OVERVIEW.md ← this file
│ ├── 01-HOST-API.md LuaAppDomain, GetFunction
│ ├── 02-TYPE-SYSTEM.md CSharp, type tables, constructors, arrays
│ ├── 04-METHOD-OVERLOAD.md dispatch, aliases, signatures
│ ├── 05-LIB.md zlua.* API
│ ├── 10-LIFETIME.md Registry, GC, exception boundaries
│ ├── 11-MULTI-VERSION.md Unity/Lua multi-version, Install, DLL, ZLuaConf
│ ├── 12-MIGRATION-ADAPTORS.md xLua/toLua/SLua Lua→C# type-path adaptors
│ ├── 13-EXTENSION-METHODS.md C# Extension → extended type IMT
│ ├── build/ Official Lua / LuaJIT build; Mono gate; EmmyLua debugger
│ ├── metatable/ __index, three tables, layout
│ └── marshal/ Push/Pop, [LuaMarshalAs]
├── impl/ Implementation notes (do not change Lua semantics)
├── guides/ Tests, migration
└── compare/ Comparison with xLua / toLua / SLua

Suggested reading order:

  1. This file → 01-HOST-API.md (host integration)
  2. 02-TYPE-SYSTEM.md + metatable/README.md (how Lua accesses C#)
  3. marshal/README.md (how arguments are passed)
  4. 04-METHOD-OVERLOAD.md + 05-LIB.md (overloads and standard library)
  5. 10-LIFETIME.md (memory and GC)
  6. Packaging / switching Unity or Lua versions → 11-MULTI-VERSION.md
  7. Official Lua build → build/01-OFFICIAL-LUA.md
  8. LuaJIT build → build/02-LUAJIT.md
  9. Editor Mono callback error boundary (all series) → build/03-MONO-LUAJIT-CALLBACK-GATE.md
  10. Editor EmmyLua debugger → build/04-EMMYLUA-DEBUGGER.md
  11. Third-party native modules (socket / cjson, etc.) → build/05-NATIVE-MODULES.md
  12. Migrating type-access adaptors from xLua / toLua / SLua → 12-MIGRATION-ADAPTORS.md
  13. C# Extension methods → 13-EXTENSION-METHODS.md

Conflict resolution: spec/** > Il2Cpp source > impl/**.


4. Initialization flow

4.1 C# entry

LuaAppDomain.Initialize(moduleName => {
// Return module source string, or byte[] / other loader-agreed type
return LoadLuaModule(moduleName);
});

LuaAppDomain resolves the backend by Application.isEditor:

  • Editor → ZLua.LuaMonoAppDomain.Initialize
  • Player → ZLua.LuaIl2CppAppDomain.Initialize → native InitializeInternal

After init, register LuaFramePump for housekeeping such as pending ref releases on Unity frame callbacks.

Optional Editor: when Settings enableDebugger is on, start EmmyLua at the end of Mono Initialize (see build/04-EMMYLUA-DEBUGGER.md).

4.2 Native / Mono side (conceptual order)

StepAction
1Create the main lua_State (single-state model; see 10-LIFETIME.md)
2Open standard libraries; run zlualib.lua (ZLuaLib::RegisterGlobals registers __zlua_*)
3Initialize registries: ObjectRegistry, TypeRegistry, Opaque scope, etc.
4Create the global CSharp root table (assembly / type lazy __index)
5Install the module loader (__zlua_load_module searcher)
6Optional: run project scripts such as globals.lua

4.3 First type access

CSharp.__index(assemblyName)
→ create assembly table, rawset cache

assembly.__index(typeFullName)
→ CLR resolve Type, EnsureBinding
→ build SMT / IMT, three tables, dispatch
→ PushTypeTable, rawset cache

Afterwards Lua accesses members via the type table / userdata metatable — no [MonoLuaCallback] or manual Export required.

4.4 Domain Reset / shutdown

The public host API has no Shutdown; for hot reload or clearing the script world use LuaAppDomain.Reset(loader) (schedule only at the call site; this frame’s EndOfFrame runs teardown + rebuild via the Initialize path). Internal teardown conceptually:

  1. Drain the pending Lua ref release queue
  2. ObjectRegistry::Shutdown, struct registry shutdown
  3. Close lua_State (then rebuild and install the new loader)

Old GetFunction delegates are all invalid after Reset takes effect. Ordering details: 10-LIFETIME.md §7 and impl/IL2CPP.md.


5. Boundaries with other docs

TopicDocument
__index / three tables / miss semanticsmetatable/
Push / Pop / ref / Opaquemarshal/
zlua.make_* / register_method05-LIB.md
GetFunction and Delegate bridge01-HOST-API.md
ObjectRegistry / GC root10-LIFETIME.md
xLua / toLua / SLua type-path adaptors12-MIGRATION-ADAPTORS.md

6. Example: minimal script

-- Assembly alias (optional)
CSharp.AC = CSharp['Assembly-CSharp']

local Demo = CSharp.AC.Demo
local demo = Demo()

demo:SetX(10)
print(demo:GetX())

-- Explicit overload (see 04-METHOD-OVERLOAD)
demo['Run(System.Int32)'](demo, 42) -- full-signature key
local run = demo['Run(System.Int32)']
zlua.register_method("run_i32", run) -- short name then colon call
demo:run_i32(42)

C# side:

var onStart = LuaAppDomain.GetFunction<Action>("main", "OnStart");
onStart();

public event Action<int> ValueChanged;
// Lua: demo:add_ValueChanged(function(v) ... end)
// demo:remove_ValueChanged(handler)