Skip to main content

Mono Implementation Notes

Source root: Packages/com.code-philosophy.zlua/Runtime/Mono/ Lua-visible semantics: ../spec/ — this doc covers Editor Mono layout, init, and differences from Il2Cpp.

1. Background and goals

The old Runtime/Mono was backed up to _archive/Mono-pre-rewrite-20260723/ and rewritten from scratch. Goal: Editor development matches Il2Cpp Player interaction behavior, with directories readable against Il2Cpp zlua/ modules.

Mono member indexing uniformly uses Lua three-table closures (see metatable/INDEXER-MONO.md.

2. Directory mapping

Runtime/Mono/ ←→ Il2Cpp zlua/
├── Lvm/ ←→ lvm/
├── Mt/ ←→ mt/
├── Marshaling/ ←→ marshal/ (must not depend on Mt; avoid System.Marshal clash)
├── Bridge/ ←→ bridge/
├── Emit/ ←→ generated/ (runtime Expression gen, not build-time stubs)
├── Utils/ ←→ utils/
└── DelegateImpl/ ← (avoid System.Delegate name clash)

Namespaces: ZLua.Lvm / ZLua.Mt / ZLua.Marshaling / ZLua.Bridge / ZLua.Emit / ZLua.Utils / ZLua.DelegateImpl.

Entry type name kept: LuaMonoAppDomain remains in root namespace ZLua for Common reflection; Il2Cpp counterpart is LuaAppDomain.

3. Locked decisions

#DecisionMono landing
D1Delete old Mono; full rewriteCurrent Runtime/Mono/ is Phase 0–4
D2Lua three-table indexer; no C# Dispatch*Mt/TypeMemberLuaIndexer.cs
D3Cannot Expression-specialize → explicit bind-time errorEmit/EmitException.cs
D4C#→Lua unified GetFunction + Delegate bridge; delete legacy object[] slow pathDelegateImpl/, Marshaling/DelegateMarshal.cs
D5No Event-specific support; add_Xxx/remove_Xxx are ordinary methodsMetaBinding does not scan Event subtables
D6Il2Cpp reuses stubs; Mono Emits one bridge per memberEmit/ vs Il2Cpp generated/
D7MetaBinding hot path has no methodId / MethodMarshalCtx upvalueEmit closures bind MethodInfo/FieldInfo directly
D8Directories PascalCase; generated/Emit/See §2

4. Initialization order

4.1 AppDomain level (Lvm/LuaMonoAppDomain.cs)

First LuaAppDomain.Initialize(moduleLoader):

StepCallIl2Cpp counterpart
1new LuaEnv()Main body of LuaEnv::Initialize
1aluaL_openlibs + RegisterPrintFirst half of RegisterLibs
1bObjectRegistry.InitializeSame
1cStructRegistry.InitializeSame
1dMetaTableCache.InitializeSame
1eTypeMemberLuaIndexer.EnsureLoadedMono-only: load indexer factory
2SetModuleLoader(moduleLoader)LuaLoader::SetModuleLoader
3LoadBuiltinGlobals()RegisterGlobals
4AssemblyRegistry.EnsureCSharpRoot()AssemblyRegistry::InitializeCSharpRoot
5ZLuaLib.RegisterGlobals(_luaEnv)ZLuaLib::RegisterGlobals
6EnsureBuiltinZLuaLib()Embed zlualib.lua equivalent
7DelegateBridges.Warmup()Warmup for DelegateBridge::Initialize

Repeat Initialize: Calling Initialize again when a main lua_State already exists → throws (use LuaAppDomain.Reset; no longer supports “swap loader only”).

Reset: Host schedules; LuaFramePump applies at EndOfFrame: internal teardown (same as Shutdown) + Initialize again per the table above. Public API has no Shutdown.

Internal Shutdown / Dispose: ProcessPendingRefReleasesLuaEnv.Dispose() (reverse-order Registry / MetaTableCache release; see LuaEnv.cs).

Il2Cpp additionally loads stub tables early at AppDomain level (MethodBridge::Initialize, etc.); Mono bridges are written into the three tables by Emit at type bind (Phase 3).

5. Phase progress and module landing

PhaseStatusHighlightsMain files
0Backup, skeletonZLua.asmdef
1Lvm chain, ObjectRegistry, cast/box, struct ByVal/ByObjLvm/LuaEnv.cs, Marshaling/*
2TypeRegistry*, SMT/IMT, three-table indexer, MetaBinding scanMt/TypeRegistry*.cs, Mt/MetaBinding.cs, Mt/TypeMemberLuaIndexer.cs
3Emit into three tables; ctor/__call; arity overload MVPEmit/*, MemberTableEmitter.Fill
4Delegate Emit, most of ZLuaLib, GetFunction Delegate bridge, delete legacyDelegateImpl/, Marshaling/DelegateMarshal.cs
5✅ impl / ⏳ acceptanceNYI fill-in, Vector delegate catalog, MethodClosureTag, LuaAlias, Pointer/params; manifest pending Editor greenEmit/*, Marshaling/PointerMarshal, ZLuaLib

Phase 5: register_method / make_generic_method / opaque get/set implemented; open generic methods write throw stubs + tags; Unity Vector delegate catalog added; [LuaAlias] matches Il2Cpp (alias replaces default key); Pointer lightuserdata + DynamicMethod; TypedReference/decimal/ByRefLike rejected at call time; params T[] marshals as array.

6. Key differences from Il2Cpp

TopicIl2CppMono
Member indexC Dispatch* + MetaBinding / TypeRegistryLua function __index/__newindex + three-table upvalues
Lua→C# bridgeBuild-time C++ stubs; MethodBridge::ResolveMethodInvoker by signatureBind-time Expression.Compile()lua_pushcfunction; does not share ReducedType stubs
MetaBinding contextMethodMarshalCtx + registry ref closureSpecialized closures have no runtime methodId upvalue
C#→LuaGetFunction + Delegate bridgeSame
Legacy object[] pathN/ADeleted RunLuaFunc(object[]) and similar slow paths
EventNo dedicated MetaKind::EventNo Event subtable; add_*/remove_* go into methodTable
GC / performanceIl2Cpp GC + slot pinSlot strong refs + weak cache; non-blittable ByVal uses boxed companion

Semantic acceptance follows ../spec/; different implementation paths are allowed, but miss/strict, overload, cast, struct, etc. must match Il2Cpp.

7. Acceptance criteria

After implementation:

  • Editor Mono: Tests/Lua/manifest.lua all green
  • Match Il2Cpp: type façade, zlua.cast, virtuals, overload, struct, delegate, arrays
  • No Event-specific API; add_/remove_ only as ordinary methods
  • No object[] legacy slow path; no ReducedType shared bridge
  • MetaBinding hot path has no methodId / MarshalCtx upvalue
  • Hot path has no massive GC / Method.Invoke fallback