用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