UE4 Unlua源码解析10 - Lua怎么替换BlueprintImplementableEvent或BlueprintNativeEvent的方法实现的

Posted 珞珈大胖强TURBO

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UE4 Unlua源码解析10 - Lua怎么替换BlueprintImplementableEvent或BlueprintNativeEvent的方法实现的相关的知识,希望对你有一定的参考价值。

Lua怎么替换BlueprintImplementableEvent或BlueprintNativeEvent的方法实现的


时机发生在UUnLuaManager中绑定UObject和Lua Object的时候,具体到函数是BindInternal

648-649行是拿到所有的Lua方法,并且存储到LuaFunctions里,650-651行拿到所有的UE的所有BlueprintEvent和RepNotify方法,存起来,然后最重要的函数OverrideFunctions中,其实就是本小节的答案所在,在这个函数里用Lua的函数覆盖BlueprintImplementableEventBlueprintNativeEvent的方法

/**
 * Override candidate UFunctions
 */
void UUnLuaManager::OverrideFunctions(const TSet<FName> &LuaFunctions, TMap<FName, UFunction*> &UEFunctions, UClass *OuterClass, bool bCheckFuncNetMode)

    for (const FName &LuaFuncName : LuaFunctions)
    
        UFunction **Func = UEFunctions.Find(LuaFuncName);
        if (Func)
        
            UFunction *Function = *Func;
            OverrideFunction(Function, OuterClass, LuaFuncName);
        
    

对于所有的LuaFunc,看能否找到同名的UEFunction,如果找到调用OverrideFunction

/**
 * Override a UFunction
 */
void UUnLuaManager::OverrideFunction(UFunction *TemplateFunction, UClass *OuterClass, FName NewFuncName)

    if (TemplateFunction->GetOuter() != OuterClass)
    
//#if UE_BUILD_SHIPPING || UE_BUILD_TEST
        if (TemplateFunction->Script.Num() > 0 && TemplateFunction->Script[0] == EX_CallLua)
        
#if ENABLE_CALL_OVERRIDDEN_FUNCTION
            TemplateFunction = GReflectionRegistry.FindOverriddenFunction(TemplateFunction);
#else
            TemplateFunction = New2TemplateFunctions.FindChecked(TemplateFunction);
#endif
        
//#endif
        AddFunction(TemplateFunction, OuterClass, NewFuncName);     // add a duplicated UFunction to child UClass
    
    else
    
        ReplaceFunction(TemplateFunction, OuterClass);              // replace thunk function and insert opcodes
    

如果方法是父类的方法,就给孩子添加覆盖函数,如果是自己的方法,就用Lua方法覆盖,也就是走到ReplaceFunction

/**
 * Replace thunk function and insert opcodes
 */
void UUnLuaManager::ReplaceFunction(UFunction *TemplateFunction, UClass *OuterClass)

###1
    FNativeFuncPtr *NativePtr = CachedNatives.Find(TemplateFunction);
    if (!NativePtr)
    
#if ENABLE_CALL_OVERRIDDEN_FUNCTION
        FName NewFuncName(*FString::Printf(TEXT("%s%s"), *TemplateFunction->GetName(), TEXT("Copy")));
        if (TemplateFunction->HasAnyFunctionFlags(FUNC_Native))
        
            // call this before duplicate UFunction that has FUNC_Native to eliminate "Failed to bind native function" warnings.
            OuterClass->AddNativeFunction(*NewFuncName.ToString(), TemplateFunction->GetNativeFunc());
        
###1
###2
        UFunction *NewFunc = DuplicateUFunction(TemplateFunction, OuterClass, NewFuncName);
        GReflectionRegistry.AddOverriddenFunction(TemplateFunction, NewFunc);
#endif
        CachedNatives.Add(TemplateFunction, TemplateFunction->GetNativeFunc());
        if (!TemplateFunction->HasAnyFunctionFlags(FUNC_Native) && TemplateFunction->Script.Num() > 0)
        
            CachedScripts.Add(TemplateFunction, TemplateFunction->Script);
            TemplateFunction->Script.Empty(3);
        
        OverrideUFunction(TemplateFunction, (FNativeFuncPtr)&FLuaInvoker::execCallLua, GReflectionRegistry.RegisterFunction(TemplateFunction));
###2
    

###1 将原UFucntion做了一个备份,将其命名为FunctionName+Copy

###2 Unlua全局数据结构GReflectionRegistry存起来原UFunction与新方法的映射,如果UFunction绑定的是蓝图脚本,会缓存字节码,然后调用全局方法OverrideUFunction真正开始覆盖方法

/**
 * 1. Replace thunk function
 * 2. Insert special opcodes if necessary
 */
void OverrideUFunction(UFunction *Function, FNativeFuncPtr NativeFunc, void *Userdata, bool bInsertOpcodes)

    if (!Function->HasAnyFunctionFlags(FUNC_Net) || Function->HasAnyFunctionFlags(FUNC_Native))
    
        Function->SetNativeFunc(NativeFunc);
    

    if (Function->Script.Num() < 1)
    
#if UE_BUILD_SHIPPING || UE_BUILD_TEST
        if (bInsertOpcodes)
        
            Function->Script.Add(EX_CallLua);
            int32 Index = Function->Script.AddZeroed(sizeof(Userdata));
            FMemory::Memcpy(Function->Script.GetData() + Index, &Userdata, sizeof(Userdata));
            Function->Script.Add(EX_Return);
            Function->Script.Add(EX_Nothing);
        
        else
        
            int32 Index = Function->Script.AddZeroed(sizeof(Userdata));
            FMemory::Memcpy(Function->Script.GetData() + Index, &Userdata, sizeof(Userdata));
        
#else
        Function->Script.Add(EX_CallLua);
        Function->Script.Add(EX_Return);
        Function->Script.Add(EX_Nothing);
#endif
    

UFunction的Script存储的是蓝图字节码,如果不是SHIPPING,加入三个字节码EX_CallLua,EX_Return,EX_Nothing,如果是SHIPPING,多加了一个Userdata,也就是Function的FunctionDesc数据

可以看一下啊EX_CallLua的实现,是一个宏实现

/**
 * Custom thunk function to call Lua function
 */
DEFINE_FUNCTION(FLuaInvoker::execCallLua)

    bool bUnpackParams = false;
    UFunction *Func = Stack.Node;
    FFunctionDesc *FuncDesc = nullptr;
    if (Stack.CurrentNativeFunction)
    
        if (Func != Stack.CurrentNativeFunction)
        
            Func = Stack.CurrentNativeFunction;
#if UE_BUILD_SHIPPING || UE_BUILD_TEST
            FMemory::Memcpy(&FuncDesc, &Stack.CurrentNativeFunction->Script[1], sizeof(FuncDesc));
#endif
            bUnpackParams = true;
        
        else
        
            if (Func->GetNativeFunc() == (FNativeFuncPtr)&FLuaInvoker::execCallLua)
            
                check(*Stack.Code == EX_CallLua);
                Stack.SkipCode(1);      // skip EX_CallLua only when called from native func
            
        
    

    //!!!Fix!!!
    //find desc from classdesc
#if UE_BUILD_SHIPPING || UE_BUILD_TEST
    if (!FuncDesc)
    
        FMemory::Memcpy(&FuncDesc, Stack.Code, sizeof(FuncDesc));
        Stack.SkipCode(sizeof(FuncDesc));       // skip 'FFunctionDesc' pointer
    
#else
    FuncDesc = GReflectionRegistry.RegisterFunction(Func);
#endif

    bool bRpcCall = false;
#if SUPPORTS_RPC_CALL
    AActor *Actor = Cast<AActor>(Stack.Object);
    if (!Actor)
    
        UActorComponent *ActorComponent = Cast<UActorComponent>(Stack.Object);
        if (ActorComponent)
        
            Actor = ActorComponent->GetOwner();
        
    
    if (Actor)
    
        /*ENetMode NetMode = Actor->GetNetMode();
        if ((Func->HasAnyFunctionFlags(FUNC_NetClient | FUNC_NetMulticast) && NetMode == NM_Client) || (Func->HasAnyFunctionFlags(FUNC_NetServer | FUNC_NetMulticast) && (NetMode == NM_DedicatedServer || NetMode == NM_ListenServer)))
        
            bRpcCall = true;
        */

        int32 Callspace = Actor->GetFunctionCallspace(Func, nullptr);
        bRpcCall = Callspace & FunctionCallspace::Remote;
    
#endif

    bool bSuccess = FuncDesc->CallLua(Context, Stack, (void*)RESULT_PARAM, bRpcCall, bUnpackParams);
    if (!bSuccess && bUnpackParams)
    
        FMemMark Mark(FMemStack::Get());
        void *Params = New<uint8>(FMemStack::Get(), Func->ParmsSize, 16);
        for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm) == CPF_Parm; ++It)
        
            Stack.Step(Stack.Object, It->ContainerPtrToValuePtr<uint8>(Params));
        
        Stack.SkipCode(1);          // skip EX_EndFunctionParms
    

这里主要是拿到FunctionDesc,然后通过FunctionDesc调用CallLua,CallLua才是核心代码

/**
 * Call Lua function that overrides this UFunction
 */
bool FFunctionDesc::CallLua(UObject *Context, FFrame &Stack, void *RetValueAddress, bool bRpcCall, bool bUnpackParams)

    // push Lua function to the stack
    bool bSuccess = false;
    lua_State *L = *GLuaCxt;
    if (FunctionRef != INDEX_NONE)
    
        bSuccess = PushFunction(L, Context, FunctionRef);
    
    else
       
        // support rpc in standlone mode
        bRpcCall = Function->HasAnyFunctionFlags(FUNC_Net);
        FunctionRef = PushFunction(L, Context, bRpcCall ? TCHAR_TO_UTF8(*FString::Printf(TEXT("%s_RPC"), *FuncName)) : TCHAR_TO_UTF8(*FuncName));
        bSuccess = FunctionRef != INDEX_NONE;
    

    if (bSuccess)
    
        if (bUnpackParams)
        
            void* Params = nullptr;
#if ENABLE_PERSISTENT_PARAM_BUFFER
            if (!bHasDelegateParams)
            
                Params = Buffer;
            
#endif      
            if (!Params)
            
                Params = Function->ParmsSize > 0 ? FMemory::Malloc(Function->ParmsSize, 16) : nullptr;
            

            for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & CPF_Parm) == CPF_Parm; ++It)
            
                Stack.Step(Stack.Object, It->ContainerPtrToValuePtr<uint8>(Params));
            
            check(Stack.PeekCode() == EX_EndFunctionParms);
            Stack.SkipCode(1);          // skip EX_EndFunctionParms

            bSuccess = CallLuaInternal(L, Params, Stack.OutParms, RetValueAddress);             // call Lua function...

            if (Params)
            
#if ENABLE_PERSISTENT_PARAM_BUFFER
                if (bHasDelegateParams)
#endif
                FMemory::Free(Params);
            

        
        else
        
            bSuccess = CallLuaInternal(L, Stack.Locals, Stack.OutParms, RetValueAddress);       // call Lua function...
        
    

    return bSuccess;

  1. 找到绑定UObejct对象实例的Lua Module,在Lua Module中找到与FFucntionDesc的同名Lua函数,找到了函数压入到Lua栈中。
  2. 将Object入栈
  3. 将反射调用的上下文环境FFrame中的参数内存,按照参数顺序从内存中读取参数,参数转成Lua,然后将函数参数按顺序压入栈中。
  4. 通过lua_pcall调用Lua函数
  5. lua函数调用完成后会把返回值push到lua栈中,Unlua需要以一定顺序取出这些返回值,并设置到正确的C++属性上。lua函数支持多返回值,C++函数只能有一个返回值,但可以使用引用参数实现多返回值效果,因此UnLua支持lua函数直接返回多值给C++。
  6. 将调用后的栈中返回值,回写到反射调用的参数内存中。这里对于Lua中的基础类型不支持引用,以多返回值形式实现。

以上这个过程其实与上篇中所提到的Lua反射调用UFunction的操作,正好是一个逆操作。其原理是一样的。

以上是关于UE4 Unlua源码解析10 - Lua怎么替换BlueprintImplementableEvent或BlueprintNativeEvent的方法实现的的主要内容,如果未能解决你的问题,请参考以下文章

UE4 Unlua源码解析10 - Lua怎么替换BlueprintImplementableEvent或BlueprintNativeEvent的方法实现的

UE4 Unlua源码解析12 - Lua与UE4的混合GC

UE4 Unlua源码解析12 - Lua与UE4的混合GC

UE4 Unlua源码解析12 - Lua与UE4的混合GC

UE4 Unlua源码解析1 - 读源码的前置知识

UE4 Unlua源码解析1 - 读源码的前置知识