Using LUA coroutine to handle LatentAction in Unreal

In the UnLua plug-in, you can directly use the coroutine to write the LatentAction logic in the blueprint to realize the delayed execution of linear logic.
Typical use case: The delay function can be called in the blueprint, but it can only be used in the event chart, because the entire event icon is handled as a blueprint function, and the node ID is recorded at the Delay or LatentAction position, and the timer is triggered to continue For nodes that perform chart functions.
In LUA, you can create a new LUA thread to execute LatentAction and its subsequent logic. Although it is not as natural as the blueprint, it is not too complicated.
The main implementation is as follows:


//简单判断函数是否需要协程,比较粗略,实际使用应该包含FLatentActionInfo的子类
static FStructProperty* GetLatentActionProperty(class UFunction* InFunction)
{
	for (TFieldIterator<FProperty> It(InFunction); It; ++It)
	{
		FStructProperty* Prop = CastField<FStructProperty>(*It);
		if (Prop && Prop->Struct == FLatentActionInfo::StaticStruct())
		{
			return Prop;
		}
	}
	return nullptr;
}

int32 FastLuaHelper::CallUnrealFunction(lua_State* InL)
{
	//SCOPE_CYCLE_COUNTER(STAT_LuaCallBP);
	UFunction* Func = (UFunction*)lua_touserdata(InL, lua_upvalueindex(1));
	FLuaObjectWrapper* Wrapper = (FLuaObjectWrapper*)lua_touserdata(InL, 1);
	UObject* Obj = nullptr;

	if (Wrapper && Wrapper->WrapperType == ELuaWrapperType::Object)
	{
		Obj = Wrapper->GetObject();
	}
	int32 StackTop = 2;
	if (Obj == nullptr)
	{
		lua_pushnil(InL);
		return 1;
	}

	if (Func->NumParms < 1)
	{
		Obj->ProcessEvent(Func, nullptr);
		return 0;
	}
	else
	{
		FStructOnScope FuncParam(Func);
		FProperty* ReturnProp = nullptr;

		FStructProperty* LatentProp = GetLatentActionProperty(Func);

		for (TFieldIterator<FProperty> It(Func); It; ++It)
		{
			FProperty* Prop = *It;
			if (Prop->HasAnyPropertyFlags(CPF_ReturnParm))
			{
				ReturnProp = Prop;
			}
			else
			{
				FastLuaHelper::FetchProperty(InL, Prop, FuncParam.GetStructMemory(), StackTop++);
			}
		}

                //重新纠正latent参数
		if (LatentProp)
		{
			if (lua_pushthread(InL) == 1)
			{
				UE_LOG(LogTemp, Warning, TEXT("never use latent in main thread!"));
				return 0;
			}

			FLatentActionInfo LatentInfo;
                        //新建一个代理,等触发以后再删除
			ULuaLatentActionWrapper* LatentWrapper = NewObject<ULuaLatentActionWrapper>(GetTransientPackage());
			LatentWrapper->AddToRoot();
			LatentInfo.CallbackTarget = LatentWrapper;
			LatentWrapper->MainThread = InL->l_G->mainthread;
			LatentWrapper->WorkerThread = InL;
			LatentInfo.ExecutionFunction = LatentWrapper->GetWrapperFunctionName();
			//记录当前线程到注册表,触发以后再移除
			LatentInfo.Linkage = luaL_ref(InL, LUA_REGISTRYINDEX);
			LatentInfo.UUID = GetTypeHash(FGuid::NewGuid());

			LatentProp->CopySingleValue(LatentProp->ContainerPtrToValuePtr<void>(FuncParam.GetStructMemory()), &LatentInfo);
		}

		Obj->ProcessEvent(Func, FuncParam.GetStructMemory());

		int32 ReturnNum = 0;
		if (ReturnProp)
		{
			FastLuaHelper::PushProperty(InL, ReturnProp, FuncParam.GetStructMemory());
			++ReturnNum;
		}

		if (Func->HasAnyFunctionFlags(FUNC_HasOutParms))
		{
			for (TFieldIterator<FProperty> It(Func); It; ++It)
			{
				FProperty* Prop = *It;
				if (Prop->HasAnyPropertyFlags(CPF_OutParm) && !Prop->HasAnyPropertyFlags(CPF_ConstParm))
				{
					FastLuaHelper::PushProperty(InL, *It, FuncParam.GetStructMemory());
					++ReturnNum;
				}
			}
		}
		if (LatentProp == nullptr)
		{
			return ReturnNum;
		}
		else
		{
                        //目前是在子线程工作,调用完UFunction要让出,回到主线程
			return lua_yield(InL, ReturnNum);
		}
	}

}
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "LuaLatentActionWrapper.generated.h"


struct lua_State;
class FastLuaUnrealWrapper;

/**
 * 
 */
UCLASS()
class FASTLUASCRIPT_API ULuaLatentActionWrapper : public UObject
{
	GENERATED_BODY()
public:

    UFUNCTION()
        void TestFunction(int32 InParam);

    static FName GetWrapperFunctionName() { return FName(TEXT("TestFunction")); }

    lua_State* MainThread = nullptr;
    lua_State* WorkerThread = nullptr;
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "LuaLatentActionWrapper.h"
#include "lua/lua.hpp"
#include "FastLuaUnrealWrapper.h"

void ULuaLatentActionWrapper::TestFunction(int32 InParam)
{
	int32 nres = 0;
        //从主线程切换到工作线程继续执行
	int32 Result = lua_resume(WorkerThread, MainThread, 0, &nres);
        //工作线程结束了就移除,实际使用中要处理异常
	if (Result == LUA_OK)
	{
		luaL_unref(MainThread, LUA_REGISTRYINDEX, InParam);
	}

	this->RemoveFromRoot();
	this->MarkPendingKill();
}
--用法示例
function Main()

	print = Unreal.PrintLog
	print(("----Lua Ram: %.2fMB----"):format(collectgarbage("count") / 1024))
	G_Timer:SetTimer('MainDelayInit', 1, 0.1, DelayInit, nil)

end


function DelayInit()
	
	local co = coroutine.create(
		function()
			KismetSystemLibrary:Delay(GameInstance, 2.0)
			print(222)
		end

	)

	coroutine.resume(co)

	print(111)

	--编辑器日志界面先打印了111,2秒后打印了222
end

Guess you like

Origin www.cnblogs.com/rpg3d/p/12744096.html