解决单个虚幻脚本虚拟机处理多个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)
原文地址:https://www.cnblogs.com/rpg3d/p/12707206.html