Migrating from toLua (tolua#) to ZLua
Feature background: compare/FEATURES.md toLua / tolua# is characterized by pre-generated Wrap, LuaState, and globally exported classes; ZLua by lazy binding, the CSharp root table, and an Il2Cpp-embedded bridge. Type-path adaptor (optional): Spec 12 · tolua adaptor · Migration index
1. Concept mapping
| toLua / tolua# | ZLua |
|---|---|
LuaState / ToLua | LuaAppDomain |
*.Wrap.cs export classes | No Wrap; EnsureBinding writes three tables |
CustomSettings.cs export list | No access-control whitelist; public lazy Bind (adaptor list is migration-only) |
Global / namespace UnityEngine.GameObject | Native: CSharp[asm][full]; or adaptor hangs _G.UnityEngine.GameObject |
LuaFunction / LuaTable | GetFunction, implicit param marshal, require module tables (adaptor does not cover) |
ToLua.Push / manual bind | Automatic marshal (spec/marshal) |
out multi-return | StructUserData (Type(...)) / copy semantics (depends on signature) |
| Binder registration | CSharp lazy load + Codegen (Il2Cpp) |
2. Step-by-step migration
Step 1: Remove toLua generated artifacts
- Delete
Source/Generate/or all*Wrap.cs/*Binder.csin the project. - Remove toLua
#ifmacros,LuaClient,LuaStatesingletons. - Uninstall tolua# plugin dlls (if separate native).
Step 2: Initialization
Before (tolua#):
LuaState lua = new LuaState();
lua.Start();
LuaBinder.Bind(lua);
lua.DoFile("Main.lua");
After (ZLua):
LuaAppDomain.Initialize(LoadModule);
// bootstrap + require 由 ZLua 与宿主 loader 负责
Step 3: Types and calls (pick one)
3A. Use tolua adaptor (less rewrite; recommended transition)
In package: ZLua~/adaptors/tolua/ExportTypes.cs + shared ZLua~/adaptors/adaptor.lua. Contract: Spec 12.
- In a project that still has toLua, copy
ExportTypes.cs→ Editor, menuZLua/ExportTypes - Generate
tolua_export_types.luafromCustomSettings.customTypeList(export_nameis a namespace path likeUnityEngine.GameObject) - Put the list and
adaptor.luawhere the ZLua project canrequirethem - Entry:
local export_types = require "tolua_export_types"
local adaptor = require "adaptor"
adaptor.init(export_types)
-- 此后 UnityEngine.GameObject 等可用(清单内;冲突会 fail-fast)
Note: Consistent with runtime BeginModule, defaults hang a namespace chain, not “global short names only”. SetNameSpace(null) / SetLibName are reflected in export_name. Member calls still follow ZLua semantics.
3B. Rewrite to native CSharp
toLua often uses flat globals; native ZLua requires assembly + full name.
Before:
local go = GameObject.Find("Player")
local demo = Demo.New()
demo:SetValue(10)
local x = demo.x
After:
local GameObject = CSharp['UnityEngine.CoreModule']['UnityEngine.GameObject']
local Demo = CSharp['Assembly-CSharp']['Demo']
local go = GameObject.Find("Player")
local demo = Demo() -- 构造:Type(),非 Demo.New()(除非静态方法名如此)
demo:SetValue(10)
local x = demo.x
Note: toLua’s Type.New() is usually Type() in ZLua (type-table __call → constructor). If C# only has a static factory, keep Demo.New().
Step 4: Custom loader
Before: LuaState.AddSearchPath / custom LuaFileUtils
After: LuaAppDomain.Initialize(moduleLoader) resolves uniformly:
Func<string, object> loader = module =>
{
var path = Path.Combine(projectRoot, "Lua", module.Replace('.', '/') + ".lua");
return File.ReadAllText(path);
};
Player path rules: TESTING.md §5.
Step 5: C# calling Lua
Before:
LuaFunction func = lua.GetFunction("Update");
func.Call(Time.deltaTime);
func.Dispose();
After:
static readonly Action<float> LuaUpdate =
LuaAppDomain.GetFunction<Action<float>>("game", "Update");
Step 6: Delegates and tolua events
toLua projects often use LuaFunction.ToDelegate / long-lived LuaFunction:
Before:
LuaFunction lf = lua.GetFunction("onClick");
Button.onClick.AddListener(lf.ToDelegate<Action>());
After (recommended: Lua passes function to C#):
public static void SetClickHandler(Action cb) { button.onClick.AddListener(() => cb()); }
ui:SetClickHandler(function() print("click") end)
After (C# actively fetches a Lua function then calls): use GetFunction<Action>/GetFunction<Func<…>>, or GetFunction<Delegate> + zlua.to_delegate. See Function and Delegate, from-xlua Step 5.
static readonly Func<Action> GetOnClick =
LuaAppDomain.GetFunction<Func<Action>>("ui", "get_on_click");
button.onClick.AddListener(GetOnClick());
Step 7: Value types and out
toLua often uses multi-return out:
Before:
local ok, result = luaObj:TryParse(str)
After:
-- struct out:Type(...) 构造 StructUserData
local outPoint = Point2D()
local ok = obj:TryGetPoint(outPoint)
-- 基元 out:裸实参走拷贝/default 分支;须 observable 写回时用 struct 形参或 C# 多返回值
Structs default to ByVal userdata; observable ref/out write-back needs Type(...) StructUserData; boxed cases use zlua.box.
Step 8: Unity engine APIs
toLua pre-exports many UnityEngine.* Wrap. When migrating:
- Change to
CSharp[assembly][typeFullName]as actually used (assembly names from.asmdef/ Inspector). - Do not rewrite every engine API at once; migrate by module.
- Il2Cpp Player needs the assembly in Codegen inputs.
3. Common pitfalls
| Pitfall | Notes |
|---|---|
| Depend on global class names | Demo undefined → tolua adaptor (Namespace.Demo), CSharp[...]['Demo'], or local alias |
Demo.New() vs Demo() | Prefer Type() for ZLua construction |
| Link errors after deleting Wrap | C# still references LuaInterface types → delete those too |
tolua #if UNITY_EDITOR dual logic | Merge to one Lua set for both ZLua ends |
| Export list as security boundary | Make APIs non-public |
Strong LuaTable dependency | Use Lua module return table + require |
| Performance assumptions | toLua like xLua goes through Wrap; ZLua Player path differs — see compare/PERFORMANCE.md |
4. Before / After examples
4.1 Module organization
Before (toLua + globals):
-- Main.lua
UpdateBeat = UpdateBeat or {}
function UpdateBeat.OnUpdate(dt)
-- ...
end
After (ZLua module):
-- game.lua
local M = {}
function M.OnUpdate(dt)
-- ...
end
return M
static readonly Action<float> OnUpdate =
LuaAppDomain.GetFunction<Action<float>>("game", "OnUpdate");
4.2 Arrays
Before:
local arr = System.Array.CreateInstance(typeof(int), 10)
After:
local arr = zlua.new_szarray_by_element_type(zlua.types.int32, 10)
-- 或
local intArrType = zlua.make_szarray_type(zlua.types.int32)
local arr2 = zlua.new_szarray_by_szarray_type(intArrType, 10)
4.3 Clean Wrap references (C#)
Before:
DemoWrap.Register(L);
After: delete; auto-Bind on first CSharp[...]['Demo'] access.
5. CustomSettings migration map
| toLua CustomSettings | ZLua |
|---|---|
customTypeList | Delete; hide with internal as needed |
staticClassList | Not needed; statics on type table |
dynamicList | Not needed |
outList | Follow C# signature + marshal Spec |
sealedList | No counterpart; inheritance rules in type system |
6. Acceptance
- No leftover
LuaInterface/ToLua/*Wrap -
Tests/Luacovers critical former toLua APIs - Editor + Il2Cpp Player manifest all green
- After removing toLua, package/startup has no lib conflicts
- (If using adaptor) after
tolua_export_types+adaptor.init, namespace paths likeUnityEngine.GameObjectwork; conflicts/missing types have clear errors
Related
| Doc | Contents |
|---|---|
| migration/ | Shared checklist and adaptor overview |
| spec/12-MIGRATION-ADAPTORS.md | tolua adaptor contract |
| from-xlua.md | xLua comparison (more detail on C#→Lua) |
| spec/05-LIB.md | zlua.* API |
| TESTING.md | Regression testing |