用LUA协程处理Unreal中的LatentAction

在UnLua插件中可以直接使用协程编写蓝图中LatentAction逻辑,实现延迟执行线性逻辑。
典型用例:在蓝图中可以调用delay函数,不过,仅能在事件图表使用,因为整个事件图标是作为一个蓝图函数处理的,在Delay或者说是LatentAction位置就记录节点ID,待计时器触发,继续执行图表函数的对于节点。
在LUA中可以新建一个LUA线程执行LatentAction及其后续逻辑,虽然不如蓝图表达上那么自然,但也不太复杂。
主要实现如下:

static FName LatentPropName = FName("LatentInfo");

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 = nullptr;

		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++);
                                if (Prop->GetFName() == LatentPropName)
				{
					LatentProp = (FStructProperty*)Prop;
				}
			}
		}

                //重新纠正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
原文地址:https://www.cnblogs.com/rpg3d/p/12744096.html