Skip to main content

Why ZLua

xLua, toLua, and SLua proved that “Lua in Unity” works. ZLua targets the next layer: make Lua↔C# truly modern, complete, and fast and lean enough on Il2Cpp—instead of piling on more config, whitelists, and mountains of Wrap.

Detailed matrix: Comparison; migration: migration.


Seven reasons (30 seconds)

One-liner
EasierMore modern design; extremely simple; zero config (no per-type C# Wrap whitelist)
More completeStandard, complete C#↔Lua interop covering nearly all common C# features
Faster98.2% of aligned cases faster than xLua; Lua→C# avg ~2.62×; common fields/properties/calls ~
Less & faster GCReference types and structs (including structs with ref fields) default to 0 GC; plus OpaqueValue and other strategies
Tiny bridgeSame-signature merge + efficient C++ codegen; footprint can be an order of magnitude smaller; 0 bridge functions still stays fast
Broader versionsLua 5.1–5.5, LuaJIT (Il2Cpp JIT Android / iOS only); Unity 2021+, Tuanjie Engine
More active maintenanceFull-time professional team; faster bug response and feature iteration

1. Easier: modern, simple, zero config

Traditional schemes often burden you with:

  • Maintaining LuaCallCSharp / export lists
  • Re-Generating huge C# Wrap whenever APIs change
  • Imperative C#→Lua via LuaEnv / GetInPath / Call

ZLua models interop closer to declarative P/Invoke:

What you wantZLua
C# call LuaLuaAppDomain.GetFunction<T>(…) then Invoke the Delegate
Override Marshal[LuaMarshalAs]
Lua access C#Lazy-loaded CSharp root table — no per-type Wrap config
// Must be after Initialize (e.g. Awake); do not use static field initializers
var AppAdd = LuaAppDomain.GetFunction<Func<int, int, int>>("app", "add");
// AppAdd(10, 20);
CSharp['AC'] = CSharp['Assembly-CSharp']
print(CSharp.AC.Demo.Add(3, 5))

Zero config means: no per-type C# Wrap whitelist or member-level Wrap project. Editor works out of the box; for Il2Cpp Player run ZLua/Generate/All once (generates C++ stubs, not xLua-style C# Wrap).

Quick start · Guides


2. More complete: almost anything callable in C# is callable

The goal is not “export a few hot-path APIs”, but standard, complete C#↔Lua interop, including but not limited to:

CategoryCapabilities
Typesclass / struct / interface / enum / nullable
MembersStatic and instance: fields, properties, methods
AdvancedGeneric classes, generic methods, delegates, arrays (incl. multi-dim)
Language detailsMethod overloads, ref / out / in, Event (add_ / remove_)

Semantics are contracted by the Spec; both ends (Mono Editor / Il2Cpp Player) have matching Lua-visible behavior.

Compatibility matrix · Features


3. Faster: not “theoretically a bit quicker”

On Il2Cpp Win64 Release, four-way aligned benchmarks (public repo zlua-benchmark; notes in PERFORMANCE):

MetricResult (avg ratio vs zlua; >1 = slower)
Lua→C# (231 cases)xLua ≈ 2.57×; toLua ≈ 3.52×; SLua ≈ 7.68×
C#→Lua (54 cases)xLua ≈ 1.59×; toLua ≈ 3.27×; SLua ≈ 14.9×
Lead vs xLua~98.6% of cases (281/285)
Common fields / properties~3.5–4× (vs xLua)

Full report: comparison_20260728_121554.md.

Root cause is direct: drop libxlua round-trips + huge C# Wrap, and finish marshal + methodPointer call once in C++.

tip

Even fast interop needs profiling first. If the script boundary is only 2% of frame time, 5× interop saves ~1.6%. ZLua fits combat formulas, UI, and many small per-frame calls as boundary hotspots.

Performance


4. Less & faster GC

Default strategy targets hot paths:

StrategyMeaning
Reference typesDefault object table / userdata — avoid pointless boxing and temporary object[]
structWhether or not fields include references, default 0 GC Marshal paths (ByVal / ByObj etc. — see Spec)
OpaqueValuelightuserdata temporary handles: more flexible low-allocation strategy within a sync call chain
enumDefault integer; no forced boxed userdata

For write-back use Opaque / ByVal userdata; bare numbers do not write back (aligned with C# ref semantics — see ref/out/in).

GC · Lifetime Spec


5. Tiny wrapper / bridge: an order of magnitude smaller, down to 0

SolutionTypical size model
xLua / toLua / SLuaPer-type / per-member Wrap; size grows roughly linearly with exported members
ZLua (Il2Cpp)Merge same signatures into efficient C++ stubs (ReducedType reuse)

So while still accessing almost all C# types, fields, properties, and methods:

  • Bridge code size is typically an order of magnitude+ smaller than traditional schemes
  • Supports 0 bridge functions; even then, interop can beat “generate huge Wrap” paths

Editor (Mono) uses Expression Emit, not shipped in the Player package; Player size is driven by C++ stubs.

Bridge & size


6. More Unity and Lua versions

DimensionZLua
Lua5.1 – 5.5, LuaJIT (Settings default lua-5.5.0; LuaJIT Il2Cpp shipping Android / iOS only)
Unity2021.3, 2022.3, Unity 6 (6000.0 / 6000.3 / 6000.5)
EngineTuanjie Engine

More versions means less risk of being stuck on one Lua/Unity combo. Full matrix: Compatibility.

Supported versions & platforms


7. More active maintenance

ZLua is maintained by a full-time professional team:

  • More responsive bug handling
  • Faster feature and Spec iteration
  • Docs, benchmarks, and in-package Docs evolve together

Treat Lua interop as long-term infrastructure, not a “stale third-party plugin”.


When not to choose ZLua

Honest boundaries matter too:

SituationSuggestion
Unwilling to maintain libil2cpp integrationPlugin-shaped xLua / toLua is lighter
Hard dependency on xLua Hotfix pipelinesKeep using xLua
Large xLua asset base, no short-term migration budgetRead Migrate from xLua first

Next steps

  1. 5-minute Quick start + zlua-demo
  2. Performance · GC · BRIDGE
  3. Features
  4. Spec overview

Further reading

DocContent
Design overviewGetFunction and bidirectional bridging
Dual runtimeMono / Il2Cpp split
GlossaryOpaque / ByVal / stub, etc.
Il2Cpp implPlayer module map