解决单个虚幻脚本虚拟机处理多个LUA状态机
前几回初步实现了在LUA层中新建UClass,让C++自动调用Lua函数,但因为虚幻是单个脚本状态机,不方便处理多个LUA状态机,本文尝试初步解决该问题。
参考UnLua插件后暂时搁置在LUA中新建UClass的方式,改用Hook的形式修改先修的UClass里面的函数实现C++自动调用LUA函数。
这里的hook仅仅实在脚本层面的API拦截,并不在C++层面;通过修改UFunction的NativeFunc实现调用LUA函数。
让单个虚幻脚本虚拟机处理多个LUA状态机,需要一个TMap记录每个GameInstance关联的LUA状态机,这个在调用LUA函数时,
根据当前UObject所属的GameInstance去调用对应的LUA状态机;这对于所有Actor类型自然毫无问题,但对于某些与GameInstance无关的类就没法处理,
比如UEngine类,后续再想想更合适方式。
首先HOOk就是用一个LUA表里面的函数代替虚幻脚本函数,实现如下:
int FastLuaHelper::HookUFunction(lua_State* InL)
{
static bool bIsBindToReset = false;
if (bIsBindToReset == false)
{
FastLuaUnrealWrapper::OnLuaUnrealReset.AddLambda([InL](lua_State* InLua)
{
if(InL != InLUA) return;
UnhookAllUFunction(InLua);
});
bIsBindToReset = true;
}
luaL_newmetatable(InL, "HookUE");
int32 HookTableIndex = lua_gettop(InL);
UClass* Cls = FLuaObjectWrapper::FetchObject(InL, 1, false)->GetClass();
FString ClsName = Cls->GetName();
const char* ClsName_C = TCHAR_TO_UTF8(*ClsName);
{
lua_getglobal(InL, "require");
lua_pushstring(InL, ClsName_C);
int32 status = lua_pcall(InL, 1, 1, 0); /* call 'require(name)' */
if (status != LUA_OK)
{
FString ErrStr = UTF8_TO_TCHAR(lua_tostring(InL, -1));
UE_LOG(LogTemp, Warning, TEXT("%s"), *ErrStr);
}
}
if (!lua_istable(InL, -1))
{
lua_pushnil(InL);
return 1;
}
lua_pushnil(InL);
while (lua_next(InL, -2))
{
FString FuncName = lua_tostring(InL, -2);
UFunction* FoundFunc = Cls->FindFunctionByName(*FuncName, EIncludeSuperFlag::ExcludeSuper);
if (FoundFunc)
{
FoundFunc->FunctionFlags |= FUNC_Native;
FoundFunc->SetNativeFunc(FastLuaHelper::CallLuaFunction);
}
else
{
//不要修改父类的方法,这并不是期望的结果;修改当前对象所属类的方法即可
UFunction* FoundSuperFunc = Cls->FindFunctionByName(*FuncName, EIncludeSuperFlag::IncludeSuper);
if (FoundSuperFunc)
{
FoundFunc = Cast<UFunction>(StaticDuplicateObject(FoundSuperFunc, Cls, *FuncName, RF_Public | RF_Transient));
FoundFunc->FunctionFlags |= FUNC_Native;
FoundFunc->SetNativeFunc(FastLuaHelper::CallLuaFunction);
Cls->AddFunctionToFunctionMap(FoundFunc, *FuncName);
FoundFunc->Bind();
FoundFunc->StaticLink(true);
}
}
if (FoundFunc)
{
//记录到HookUE表格中,方便结束时恢复原状,这里直接使用UFunction指针关联LUA函数;
//相比UnLua插件的方式,在调用LUA时可略微减少查表次数
lua_rawsetp(InL, HookTableIndex, FoundFunc);
}
else
{
lua_pop(InL, 1);
}
}
lua_pushboolean(InL, 1);
return 1;
}
//重启LUA时解除Hook,否则重启LUA后。UClass里面任然是上次hook的LUA函数
static void UnhookAllUFunction(lua_State* InL)
{
if (InL == nullptr)
{
return;
}
luaL_getmetatable(InL, "HookUE");
if (lua_istable(InL, -1) == false)
{
return;
}
lua_pushnil(InL);
while (lua_next(InL, -2))
{
UFunction* Func = (UFunction*)lua_touserdata(InL, -2);
if (Func)
{
Func->SetNativeFunc(nullptr);
}
if (Func && Func->HasAnyFlags(RF_Transient))
{
//这里只是粗略处理一下,若要精准恢复原样需要额外的数据记录其他信息,先略过
Func->GetOwnerClass()->RemoveFunctionFromFunctionMap(Func);
Func->MarkPendingKill();
lua_pushnil(InL);
lua_rawsetp(InL, -4, Func);
}
lua_pop(InL, 1);
}
}
void FastLuaHelper::CallLuaFunction(UObject* Context, FFrame& TheStack, RESULT_DECL)
{
UClass* TmpClass = Context->GetClass();
//对于非Actor类型,这一步很可能会失败
UGameInstance* GI = UGameplayStatics::GetGameInstance(Context);
lua_State* LuaState = *FastLuaUnrealWrapper::LuaStateMap.Find(GI);
if (LuaState == nullptr)
{
return;
}
int32 tp = lua_gettop(LuaState);
luaL_getmetatable(LuaState, "HookUE");
if (lua_istable(LuaState, -1) == false)
{
return;
}
lua_rawgetp(LuaState, -1, TheStack.Node);
if (lua_isfunction(LuaState, -1))
{
int32 ParamsNum = 0;
FProperty* ReturnParam = nullptr;
//store param from UE script VM stack
FStructOnScope FuncTmpMem(TheStack.Node);
//push self
FLuaObjectWrapper::PushObject(LuaState, Context);
++ParamsNum;
for (TFieldIterator<FProperty> It(TheStack.Node); It; ++It)
{
//get function return Param
FProperty* CurrentParam = *It;
void* LocalValue = CurrentParam->ContainerPtrToValuePtr<void>(FuncTmpMem.GetStructMemory());
TheStack.StepCompiledIn<FProperty>(LocalValue);
if (CurrentParam->HasAnyPropertyFlags(CPF_ReturnParm))
{
ReturnParam = CurrentParam;
}
else
{
//set params for lua function
FastLuaHelper::PushProperty(LuaState, CurrentParam, FuncTmpMem.GetStructMemory(), 0);
++ParamsNum;
}
}
//call lua function
int32 CallRet = lua_pcall(LuaState, ParamsNum, ReturnParam ? 1 : 0, 0);
if (CallRet)
{
UE_LOG(LogTemp, Warning, TEXT("%s"), UTF8_TO_TCHAR(lua_tostring(LuaState, -1)));
}
if (ReturnParam)
{
//get function return Value, in common
FastLuaHelper::FetchProperty(LuaState, ReturnParam, FuncTmpMem.GetStructMemory(), -1);
}
}
lua_settop(LuaState, tp);
}
测试用法
--BP_RPGCharacter_C.lua
BP_RPGCharacter_C = BP_RPGCharacter_C or {}
function BP_RPGCharacter_C:ReceiveBeginPlay()
print(1)
end
function BP_RPGCharacter_C:ReceiveTick(InDeltaTime)
end
function BP_RPGCharacter_C:ReceiveEndPlay(InReason)
print(3)
end
return BP_RPGCharacter_C
--Main.lua
local PlayerCtrl = GameplayStatics:GetPlayerController(G_GameInstance, 0)
local Pawn = PlayerCtrl:K2_GetPawn()
Unreal.LuaHookUFunction(Pawn)