前言 Unlua绑定UE底层原理,正在更新
图表分析 图表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 graph TB subgraph Lua层["🎯 Lua 层"] LS[Lua Scripts] LM[Lua Modules] LT[Lua Tables] end subgraph 绑定层["🔗 绑定层"] SE["静态导出<br/>📌 编译期"] DB["动态绑定<br/>⚡ 运行时"] RB["反射绑定<br/>🔄 按需加载"] end subgraph 注册层["📋 注册层"] RS[Registry System] CR[Class Registry] OR[Object Registry] FR[Function Registry] end subgraph 核心层["⚙️ 核心层"] LE["LuaEnv<br/>Lua VM 管理器"] GC[GC 管理] REF[对象引用] MEM[内存分配] end LS --> SE LM --> DB LT --> RB SE --> RS DB --> RS RB --> RS RS --> CR RS --> OR RS --> FR CR --> LE OR --> LE FR --> LE LE --> GC LE --> REF LE --> MEM classDef luaLayer fill:#e1f5ff,stroke:#01579b,stroke-width:2px classDef bindLayer fill:#fff3e0,stroke:#e65100,stroke-width:2px classDef regLayer fill:#f3e5f5,stroke:#4a148c,stroke-width:2px classDef coreLayer fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px class LS,LM,LT luaLayer class SE,DB,RB bindLayer class RS,CR,OR,FR regLayer class LE,GC,REF,MEM coreLayer
Lua2CPP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 actor:SetActorLocation(FVector(100, 200, 300)) ┌──────────────────────────────────────────────────────┐ │ 1. Lua 函数查找 │ │ actor:SetActorLocation │ │ └─► actor 的 metatable.__index │ │ └─► Class_Index │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 2. 获取 FFunctionDesc │ │ GetField(L) │ │ └─► ClassDesc->RegisterField("SetActorLocation")│ │ └─► 创建 FFunctionDesc (首次) 或缓存 │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 3. 推送 Closure │ │ PushField(L, Function) │ │ └─► lua_pushcclosure(L, Class_CallUFunction, 1) │ │ └─► 缓存到 metatable │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 4. 执行 Closure │ │ Class_CallUFunction(L) │ │ └─► FFunctionDesc::CallUE(L, NumParams) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 5. 参数转换 │ │ PreCall(L, NumParams, Params) │ │ ├─► FVector::WriteValue(L, Params) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 6. 调用 UE 函数 │ │ Object->ProcessEvent(Function, Params) │ │ └─► AActor::SetActorLocation(FVector) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 7. 返回值处理 │ │ PostCall(L, Params) │ │ └─► 无返回值,返回 0 │ └──────────────────────────────────────────────────────┘
Lua 调用 → 2. UnLua 拦截 → 3. 参数打包 → 4. 反射调用 → 5. 结果返回
CPP2Lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 蓝图节点: Event BeginPlay ┌──────────────────────────────────────────────────────┐ │ 1. 蓝图虚拟机执行 │ │ UK2Node_Event::Execute() │ │ └─► UObject::ProcessEvent(BeginPlay, nullptr) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 2. 检查 Lua 覆写 │ │ FLuaOverrider::ProcessEvent() │ │ ├─► 查找 ObjectRefs[this] │ │ │ └─► LuaTableRef (Lua 实例表引用) │ │ │ │ │ ├─► lua_rawgeti(L, LUA_REGISTRYINDEX, Ref) │ │ │ └─► 获取 INSTANCE │ │ │ │ │ └─► lua_getfield(L, -1, "ReceiveBeginPlay") │ │ └─► 查找 Lua 函数 │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 3. 准备 Lua 调用 │ │ FFunctionDesc::CallLua(L, FunctionRef, SelfRef, Stack)│ │ ├─► lua_rawgeti(L, LUA_REGISTRYINDEX, FunctionRef)│ │ └─► lua_rawgeti(L, LUA_REGISTRYINDEX, SelfRef) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 4. 参数转换 │ │ for (Property: Function->Properties) │ │ Property->ReadValue(L, Params, false) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 5. 执行 Lua 函数 │ │ lua_pcall(L, NumParams, NumResults, ErrorHandler) │ │ └─► 执行 MyActor.lua:ReceiveBeginPlay(self) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 6. 返回值处理 │ │ if (HasReturnValue) │ │ ReturnProperty->WriteValue(L, RetAddress, -1) │ └────────────┬─────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 7. Out 参数回写 │ │ for (OutProperty: OutProperties) │ │ OutProperty->WriteValue(L, Params) │ └──────────────────────────────────────────────────────┘
绑定机制 基于这篇文章,我来详细介绍UnLua的绑定机制实现原理。UnLua的绑定机制主要分为静态导出和动态绑定两种方式,它们共同构成了完整的C++/Lua交互体系。
一、整体架构设计
UnLua采用四层架构实现绑定机制:
⚙️ 核心层 → 📋 注册层 → 🔗 绑定层 → 🎯 Lua层
核心组件职责: • FClassRegistry:管理UStruct到Lua Metatable的映射
• FObjectRegistry:管理UObject实例到Lua Userdata的映射
• FLuaEnv:Lua虚拟机管理和环境隔离
• FFunctionDesc:UFunction描述符和调用分发
二、静态导出机制(编译期绑定)
核心原理
静态导出利用C++全局构造函数在程序启动前完成类型注册,零运行时开销。
实现机制
宏定义展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 BEGIN_EXPORT_CLASS (FMyMathLib) ADD_STATIC_FUNCTION (Add) END_EXPORT_CLASS ()struct FExportedFMyMathLibHelper { static struct FExportedFMyMathLib : public UnLua::FExportedClass<FMyMathLib> { FExportedFMyMathLib () : FExportedClass ("FMyMathLib" ) { AddFunction ("Add" , &FMyMathLib::Add); } } Exported; }; FExportedFMyMathLibHelper::FExportedFMyMathLib FExportedFMyMathLibHelper::Exported;
全局构造函数触发流程
1 2 3 4 5 6 7 8 9 10 11 12 13 操作系统加载程序 ↓ 加载所有动态库(DLL/SO) ↓ 初始化全局变量区 ↓ 执行全局对象构造函数(按编译顺序) ↓ FExportedFMyMathLib::FExportedFMyMathLib() → ExportClass(this) ↓ 注册到全局容器GExportedClasses ↓ 程序启动后FLuaEnv::Initialize()遍历容器注册到Lua
Lua注册完整流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void FExportedClass::Register (lua_State* L) { luaL_newmetatable (L, ClassName.Get ()); if (!SuperClassName.IsEmpty ()) { lua_pushstring (L, "Super" ); luaL_getmetatable (L, TCHAR_TO_UTF8 (*SuperClassName)); lua_rawset (L, -3 ); } lua_pushstring (L, "__index" ); lua_pushvalue (L, -2 ); lua_pushcclosure (L, UnLua::Index, 1 ); lua_rawset (L, -3 ); lua_getglobal (L, "UE" ); lua_pushstring (L, ClassName.Get ()); lua_pushvalue (L, -3 ); lua_rawset (L, -3 ); }
函数调用流程
– Lua调用 local result = UE.FMyMathLib.Add(10, 20)
– 执行过程: – 1. UE[“FMyMathLib”][“Add”] → 触发__index,返回Closure – 2. Closure(10, 20) → InvokeFunction – 3. lua_upvalueindex(1) → 获取FExportedFunction* – 4. FExportedFunction::Invoke → 调用FMyMathLib::Add – 5. 返回30
三、动态绑定机制(运行时绑定)
工作流程
1️⃣ Actor实例化 → 2️⃣ 加载Lua模块 → 3️⃣ 创建绑定实例 → 4️⃣ 函数覆写准备
绑定触发机制(按优先级)
优先级 绑定方式 触发条件 配置位置
1 IUnLuaInterface 类实现接口 C++代码
2 UnLuaBind组件 添加组件 蓝图/实例
3 全局配置 匹配类名规则 Project Settings
4 手动绑定 调用API Lua代码
对象绑定完整流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int FObjectRegistry::Bind (UObject* Object) { lua_getfield (L, LUA_REGISTRYINDEX, "UnLua_ObjectMap" ); lua_pushlightuserdata (L, Object); lua_newtable (L); PushObjectCore (L, Object); lua_pushstring (L, "Object" ); lua_pushvalue (L, -2 ); lua_rawset (L, -4 ); int32 ClassBoundRef = Env->GetManager ()->GetBoundRef (Class); lua_rawgeti (L, LUA_REGISTRYINDEX, ClassBoundRef); lua_getmetatable (L, -2 ); lua_setmetatable (L, -2 ); lua_setmetatable (L, -3 ); lua_pushvalue (L, -1 ); const auto Ref = luaL_ref (L, LUA_REGISTRYINDEX); ObjectRefs.Add (Object, Ref); lua_rawset (L, -3 ); return Ref; }
元表继承链结构
1 2 3 4 5 6 7 8 9 10 11 12 INSTANCE { # 对象实例表 Object = <Userdata>, # 绑定的UObject metatable = MODULE { # Lua模块表 ReceiveBeginPlay = function (...) end , metatable = METATABLE_UOBJECT { # C++反射元表 __index = Class_Index, __newindex = Class_NewIndex, __gc = UObject_Delete, ... } } }
四、函数覆写原理
核心机制:ProcessEvent Hook
UnLua通过拦截UObject::ProcessEvent实现函数覆写: // Hook流程 原始调用:AActor::BeginPlay → UFunction::Invoke → UObject::ProcessEvent Hook后:AActor::BeginPlay → UFunction::Invoke → FLuaOverrider::ProcessEvent
覆写检测流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool FLuaOverrides::IsOverridden (UFunction* Function) const { if (LuaFunctions.Contains (Function->GetFName ())) { return true ; } UClass* SuperClass = Function->GetOwnerClass ()->GetSuperClass (); if (SuperClass) { return IsOverriddenInClass (SuperClass, Function); } return false ; }
Lua调用C++函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void FFunctionDesc::CallLua (lua_State* L, lua_Integer FunctionRef, lua_Integer SelfRef, FFrame& Stack, RESULT_DECL) { lua_rawgeti (L, LUA_REGISTRYINDEX, FunctionRef); lua_rawgeti (L, LUA_REGISTRYINDEX, SelfRef); for (int i = 0 ; i < Properties.Num (); ++i) { if (!Properties[i]->IsReturnParameter ()) { Properties[i]->ReadValue_InContainer (L, Params, false ); } } if (lua_pcall (L, NumParams, NumResults, ErrorHandler) != LUA_OK) { const char * ErrorMsg = lua_tostring (L, -1 ); UE_LOG (LogUnLua, Error, TEXT ("Lua Error: %s" ), UTF8_TO_TCHAR (ErrorMsg)); lua_pop (L, 1 ); return ; } if (HasReturnProperty ()) { RetProp->WriteValue (L, RetAddress, -NumResults); } }
五、两种绑定机制对比
特性
静态导出
动态绑定
注册时机
编译期(程序启动前)
运行时(对象创建时)
类型安全
编译期检查
运行时检查
调用开销
~1.5μs(直接调用)
~2.5μs(反射查询)
支持反射
否(不依赖UE反射)
是(基于UE反射)
热更新
否(需重新编译)
是(可替换Lua文件)
适用场景
数学库、工具函数、第三方库封装
Actor/Component、蓝图类、需要热更新的逻辑
六、关键数据结构
注册表(Registry)使用
1 2 3 4 5 6 7 8 9 // Lua registry内部结构(概念模型) LUA_REGISTRYINDEX = { ["_G" ] = _G , // Lua全局环境 ["_LOADED" ] = package .loaded , // 已加载模块 ["UnLua_ObjectMap" ] = {...}, // UnLua对象缓存 ["UObject" ] = {...}, // 命名元表 [1 ] = {...}, // luaL_ref创建的引用 [2 ] = function () ... end , // 引用ID=2 }
引用系统管理
1 2 3 4 5 6 7 8 9 lua_newtable (L); int ref = luaL_ref (L, LUA_REGISTRYINDEX); lua_rawgeti (L, LUA_REGISTRYINDEX, ref); luaL_unref (L, LUA_REGISTRYINDEX, ref);
七、性能优化要点
缓存策略:频繁访问的元表和函数引用进行缓存
批量操作:减少Lua栈操作次数,使用批量参数传递
引用计数:合理使用luaL_ref/luaL_unref管理对象生命周期
避免GC压力:使用light userdata存储指针,减少内存分配
总结
UnLua的绑定机制通过静态导出和动态绑定两种方式,实现了C++与Lua的高效交互:
静态导出适用于性能敏感、类型固定的场景,通过编译期模板元编程实现零开销绑定
动态绑定适用于需要灵活性、热更新的场景,通过运行时反射和元表继承实现
双层注册架构(ClassRegistry + ObjectRegistry)分离了类型注册和实例绑定
函数覆写通过Hook ProcessEvent实现,支持Lua函数覆盖C++/蓝图函数
完整的GC管理通过双重引用系统确保内存安全
这种设计既保证了高性能,又提供了足够的灵活性,是UnLua能够在大型游戏项目中广泛应用的关键原因。
四个核心技术点 基于您提供的文档内容,这篇文章对UnLua框架的四个核心技术点——静态导出、动态绑定、元表和GC管理——进行了深入剖析。以下是这四部分的核心内容总结:
1. 静态导出
核心思想 :在编译期 完成绑定,实现零运行时开销 。它不依赖UE的运行时反射系统。
实现机制 :利用C++的全局对象构造函数 在main()函数执行前自动触发注册逻辑。通过模板元编程和特定的宏(如BEGIN_EXPORT_CLASS),将C++类型、函数的信息和调用入口注册到Lua环境中。
关键流程 :
程序启动时,所有通过宏定义的导出类会执行其全局构造函数,将自身注册到一个全局容器中。
Lua虚拟机初始化时(FLuaEnv::Initialize),会遍历这个容器,为每个导出的类创建对应的Lua命名元表,并将函数包装为Lua闭包注册进去。
特点与场景 :性能极高(~1.5μs),类型安全,但不支持热更新。适合 导出无状态的工具类、数学库、第三方C++库等。
2. 动态绑定
核心思想 :在运行时 根据UE的反射信息动态地将Lua模块绑定到UObject实例上,支持热更新。
实现机制 :基于UE的反射系统,在对象实例化时触发绑定流程。核心是FObjectRegistry::Bind函数,它会为每个UObject实例创建一个Lua端的INSTANCE表,并与对应的Lua模块、C++元表关联,形成多层查找链。
关键流程 :
Actor等对象实例化后,通过接口、组件或配置触发绑定。
加载对应的Lua模块文件,执行后得到一个模块表(MODULE)。
创建INSTANCE表,内部通过Userdata引用C++对象,并将其元表设置为MODULE。
MODULE的元表又指向C++反射生成的UObject元表,从而建立起INSTANCE -> MODULE -> C++反射的继承链。
特点与场景 :灵活,支持热更,但有一定调用开销(~2.5μs)。适合 绑定Actor、Component等需要热更新逻辑的蓝图或C++类。
3. 元表
核心作用 :Lua中实现面向对象和操作符重载的基石 ,在UnLua中扮演了连接Lua与C++的“桥接协议”角色。
关键元方法 :
__index: 当访问INSTANCE的一个字段(如函数名)时触发。UnLua的Class_Index函数会沿着INSTANCE -> MODULE -> C++元表这条链查找,找到后返回一个可调用闭包,并将其缓存 以提升后续访问性能。
__newindex: 当向INSTANCE写入属性时触发,用于将Lua端的赋值操作映射到C++对象属性的Set函数。
__gc: 当Lua端的Userdata被垃圾回收时触发,用于通知UnLua的C++端解除对该UObject的自动引用,允许UE GC回收。
实现要点 :UnLua通过luaL_newmetatable为每个C++类型创建了全局唯一的命名元表 ,存储在Lua注册表中,确保了类型系统的一致性。
4. GC管理
核心挑战 :需要协调Lua GC 和UE GC 两套独立的垃圾回收系统,防止对象被任意一方提前回收或产生悬空指针。
双重GC协同机制 :
防UE回收 :当Lua通过PushObjectCore引用一个UObject时,会将其加入AutoObjectReference集合。这个集合会阻止UE GC回收这些被Lua引用的对象。
防悬空指针 :
二级指针 :Lua Userdata内部存储的是指向UObject指针的指针(void**),而非指针本身。
释放标记 :当UObject被UE销毁时,UnLua会收到通知,并将Userdata内的二级指针指向一个特殊的0xDEAD地址作为“已释放”标记。
访问安全 :每次通过Userdata访问C++对象前,都会检查指针是否为0xDEAD。如果是,则抛出Lua错误,从而安全地阻止了访问已释放内存。
Lua GC回调 :当Lua端的Userdata被回收时,其__gc元方法会调用UObject_Delete,从AutoObjectReference集合中移除对应UObject,允许UE GC最终回收它。
总结 这四个部分紧密协作,构成了UnLua高性能绑定的基础:静态导出与动态绑定 提供了两种互补的接入方式;元表 是实现属性、函数透明访问的协议层;而GC管理 则是确保整个系统内存安全、稳定运行的守护者。它们共同使得在Unreal Engine中使用Lua脚本开发既高效又安全。
代码样例 EveLuaManager.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #pragma once #include "CoreMinimal.h" extern "C" {#include "lua.h" #include "lualib.h" #include "lauxlib.h" } class EveLuaManager { public : EveLuaManager (); ~EveLuaManager (); bool Initialize () ; bool ExecuteLuaScript (const char * Script) ; void Shutdown () ; lua_State* GetLuaState () const { return LuaState; } static int Lua_Print (lua_State* L) ; static int Lua_CallMemberFunction (lua_State* L) ; static int Lua_SpawnActor (lua_State* L) ; private : lua_State* LuaState; };
EveLuaManager.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 #include "EveLuaManager.h" #include "EveLuaActor.h" #include "Kismet/GameplayStatics.h" EveLuaManager::EveLuaManager () : LuaState (nullptr ) { } EveLuaManager::~EveLuaManager () { Shutdown (); } bool EveLuaManager::Initialize () { LuaState = luaL_newstate (); if (!LuaState) { return false ; } luaL_openlibs (LuaState); lua_register (LuaState, "Print" , Lua_Print); lua_register (LuaState, "CallMemberFunction" , Lua_CallMemberFunction); lua_register (LuaState, "SpawnActor" , Lua_SpawnActor); return true ; } bool EveLuaManager::ExecuteLuaScript (const char * Script) { if (!LuaState) { return false ; } int result = luaL_dostring (LuaState, Script); if (result != LUA_OK) { const char * errorMsg = lua_tostring (LuaState, -1 ); UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] Lua error: %s" ), UTF8_TO_TCHAR (errorMsg)); lua_pop (LuaState, 1 ); return false ; } return true ; } void EveLuaManager::Shutdown () { if (LuaState) { lua_close (LuaState); LuaState = nullptr ; } } int EveLuaManager::Lua_Print (lua_State* L) { int n = lua_gettop (L); FString Output; for (int i = 1 ; i <= n; i++) { if (i > 1 ) Output += " " ; if (lua_isstring (L, i)) { const char * str = lua_tostring (L, i); Output += UTF8_TO_TCHAR (str); } else if (lua_isnumber (L, i)) { lua_Number num = lua_tonumber (L, i); Output += FString::SanitizeFloat (num); } else if (lua_isboolean (L, i)) { Output += lua_toboolean (L, i) ? "true" : "false" ; } else { Output += lua_typename (L, lua_type (L, i)); } } UE_LOG (LogTemp, Log, TEXT ("%s" ), *Output); return 0 ; } int EveLuaManager::Lua_CallMemberFunction (lua_State* L) { void * Object = lua_touserdata (L, 1 ); if (!Object) { UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] Invalid object pointer passed to CallMemberFunction" )); lua_pushnil (L); return 1 ; } const char * FunctionNameStr = lua_tostring (L, 2 ); int NumParams = lua_gettop (L) - 2 ; AActor* Actor = static_cast <AActor*>(Object); if (!Actor) { UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] Failed to cast object pointer to AActor" )); lua_pushnil (L); return 1 ; } UClass* Class = Actor->GetClass (); UFunction* Function = Class->FindFunctionByName (FName (FunctionNameStr)); if (!Function) { UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] Function %s not found in class %s" ), UTF8_TO_TCHAR (FunctionNameStr), *Class->GetName ()); lua_pushnil (L); return 1 ; } void * Params = FMemory::Malloc (Function->ParmsSize, Function->GetMinAlignment ()); FMemory::Memzero (Params, Function->ParmsSize); int Index = 0 ; for (TFieldIterator<FProperty> It (Function); It; ++It) { FProperty* Param = *It; if (Param == Function->GetReturnProperty ()) { continue ; } if (Param->IsA (FIntProperty::StaticClass ()) && Index < NumParams) { int32 Value = lua_tointeger (L, Index + 3 ); *Param->ContainerPtrToValuePtr <int32>(Params) = Value; UE_LOG (LogTemp, Log, TEXT ("[Eve-Log-Cpp] Param %d: %d" ), Index, *Param->ContainerPtrToValuePtr <int32>(Params)); Index++; } } UE_LOG (LogTemp, Log, TEXT ("[Eve-Log-Cpp] Calling Function: %s, NumParams: %d" ), *Function->GetName (), NumParams); Actor->ProcessEvent (Function, Params); FProperty* ReturnProp = Function->GetReturnProperty (); if (ReturnProp) { if (ReturnProp->IsA (FIntProperty::StaticClass ())) { int32 ReturnValue = *ReturnProp->ContainerPtrToValuePtr <int32>(Params); lua_pushinteger (L, ReturnValue); } else { UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] Unsupported return type: %s" ), *ReturnProp->GetClass ()->GetName ()); lua_pushnil (L); } } else { lua_pushnil (L); } FMemory::Free (Params); return 1 ; } int EveLuaManager::Lua_SpawnActor (lua_State* L) { UWorld* World = GWorld; if (!World) { UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] World is null" )); lua_pushnil (L); return 1 ; } AActor* NewActor = World->SpawnActor <AEveLuaActor>(); if (!NewActor) { UE_LOG (LogTemp, Error, TEXT ("[Eve-Log-Cpp] Failed to spawn AEveLuaActor" )); lua_pushnil (L); return 1 ; } lua_pushlightuserdata (L, NewActor); return 1 ; }
EveLua.lua 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Print("[Eve-Log-Lua] Hello from Lua!" ) local actor = SpawnActor()if actor then local result = CallMemberFunction(actor, "Add" , 2 , 3 ) if result then Print("[Eve-Log-Lua] Add result: " .. result) else Print("[Eve-Log-Lua] CallMemberFunction returned nil." ) end else Print("[Eve-Log-Lua] Failed to spawn actor." ) end
GMP泄漏 Lua层的FGMPCallback 实现,将U对象进行了AddReferencedObject操作
如果没有成对remove,会导致U对象(尤其是 UserWidget)无法被GC,造成内存泄露
其实根治方法也有(最新代码已按这个修改)
可以转成weak指针
GMP dispatch时,如果IsStale 为true,则不执行,并标记GMPSignal 自释放及清理队列
参考 (99+ 封私信 / 80 条消息) UnLua原理详解 - 知乎
[UE5 UnLua 脚本方案原理 | Yuerer’s Blog](https://www.yuerer.com/UE5 UnLua 脚本方案原理/)
Unlua代码分析 | FixCode Blog